Commit d2f8682b authored by kevinflo's avatar kevinflo

initial public commit

parents
.idea
temp.sql
.vscode
notes.txt
node_modules
ethereum/build
secrets.json
wallet-key.json
scrap
.next
.DS_Store
\ No newline at end of file
{
"singleQuote": true
}
MIT License
Copyright (c) 2018 Kevin Florenzano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"DPOSTS": {
"RINKEBY": "0x33D430BC40141917F4389CC340dC3DEFA3df8526",
"RINKEBY2": "0xA323C7A77c8778C96A2b89706331173fD306A0DC",
"RINKEBY3": "0x6830D2D3F83295056A6600D45c3c6C72E0987478",
"RINKEBY4": "0xcba065DCD2b7A8460bF3882d2586D75dE9E92E0f",
"RINKEBY5": "0xC18A50F422a5e224E8A9aAAF21AE8C4489E3Be40",
"RINKEBY6": "0xec42474bC2Fc05cdddEfA876d17737183FF4e4Fb"
}
}
const path = require('path');
const solc = require('solc');
const fs = require('fs-extra');
const buildPath = path.resolve(__dirname, 'build');
fs.removeSync(buildPath);
const dpostsPath = path.resolve(__dirname, 'contracts', 'Dposts.sol');
const source = fs.readFileSync(dpostsPath, 'utf8');
const output = solc.compile(source, 1).contracts;
fs.ensureDirSync(buildPath);
for (let contract in output) {
fs.outputJsonSync(
path.resolve(buildPath, contract.replace(':', '') + '.json'),
output[contract]
);
}
// module.exports = {
// Dposts: contracts[':Dposts'],
// Board: contracts[':Board']
// };
pragma solidity ^0.4.18;
contract Ownable {
address public owner;
function Ownable() public {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function transferOwnership(address _newOwner) onlyOwner public {
owner = _newOwner;
}
}
contract Mortal is Ownable {
function terminate() public onlyOwner {
selfdestruct(msg.sender);
}
}
contract YearAware {
uint public currentYear;
// map of years to total collected pot for that year
// note that the keys will actually be years since epoch
mapping(uint => uint) public yearToTaxPot;
function determineBlockYear() public view returns (uint) {
return block.timestamp / 31536000;
}
function addPaidTaxToYearPot(uint taxPaid) public payable {
yearToTaxPot[currentYear] += taxPaid;
}
}
contract Dposts is Mortal, YearAware {
address[] public boards;
mapping(address => uint) public boardToBoardIndex;
uint public lastCurrentTax;
// fallback payable function
function() public payable{}
// all taxation is volunatary
// enough is an amount in ether the platform is capped at making per year
// it starts at 100 ether the first year
// and increases a little under 3% compound interest per year
// it is recalculated once per year at the start of the year
// and used to cap the tax pot for the duration of that year
// my dad had the idea for this capping when we were talking about how to fix some incentive problems
// and he read a book called enough and thought it would be cool to actually call the variable that
uint enough;
// this is just that in wei
uint currentYearMaxTaxPot;
function createBoard(
bytes32 name,
bool allowPosterEditing,
bool allowOwnerModeration,
bool allowBoardTipping,
bool allowTaxation,
bool allowPostTipping,
bool allowComments,
bool allowCommentTipping,
uint fee
) public returns (address) {
address owner = msg.sender;
address board = new Board(
this,
name,
allowPosterEditing,
allowOwnerModeration,
allowBoardTipping,
allowTaxation,
allowPostTipping,
allowComments,
allowCommentTipping,
fee,
owner
);
uint index = boards.push(board) - 1;
boardToBoardIndex[board] = index;
emit BoardCreated(name, index, board);
return board;
}
event BoardCreated(
bytes32 name,
uint index,
address _board
);
function getBoardsLength() public view returns (uint) {
return boards.length;
}
function determineYearMaxTaxPot(uint yearsSinceStart) private pure returns (uint) {
// This is a really inefficient and inaccurate implementation of compound interest
// Whatever, close enough. solidity makes some things confuzzling...
require(yearsSinceStart > 0);
uint yearMaxTaxPot = 100;
for (uint i = 0; i < yearsSinceStart; i++) {
yearMaxTaxPot += (yearMaxTaxPot * 3 / 100);
}
return yearMaxTaxPot;
}
function determineTax() public returns (uint) {
uint blockYear = determineBlockYear();
if (blockYear != currentYear) {
currentYear = blockYear;
enough = determineYearMaxTaxPot(blockYear);
// I don't know why it won't let me use the ether keyword for this
currentYearMaxTaxPot = enough * 1000000000000000000;
}
// hard max single year amount is first person to pay tax that year
// current tax amount is single year max minus current pot divided
// by something that results in a reasonable max tip (first tipper that year)
lastCurrentTax = (currentYearMaxTaxPot - yearToTaxPot[currentYear]) / 400000;
return lastCurrentTax;
}
function getTaxPotForYear(uint year) public view returns (uint){
return yearToTaxPot[year];
}
function getBoardNameAtIndex(uint index) public view returns (bytes32){
return Board(boards[index]).name();
}
function getBoardUrlAtIndex(uint index) public view returns (bytes32){
return Board(boards[index]).url();
}
}
contract Board is Mortal, YearAware {
address public platform;
bool public allowPosterEditing;
bool public allowOwnerModeration;
bool public allowBoardTipping;
bool public allowTaxation;
bool public allowPostTipping;
bool public allowComments;
bool public allowCommentTipping;
bool public readOnly;
uint public fee;
bytes32 public url;
bytes32 public name;
Post[] public posts;
mapping(address => uint[]) public posterToPostIndexes;
Comment[] public comments;
mapping(uint => uint[]) public postIndexToCommentIndexes;
uint public totalPaidTax;
// fallback payable function
function() public payable{}
function Board(
address _platform,
bytes32 _name,
bool _allowPosterEditing,
bool _allowOwnerModeration,
bool _allowBoardTipping,
bool _allowTaxation,
bool _allowPostTipping,
bool _allowComments,
bool _allowCommentTipping,
uint _fee,
address _owner
) public
{
platform = _platform;
name = _name;
allowPosterEditing = _allowPosterEditing;
allowOwnerModeration = _allowOwnerModeration;
allowBoardTipping = _allowBoardTipping;
allowTaxation = _allowTaxation;
allowPostTipping = _allowPostTipping;
allowComments = _allowComments;
allowCommentTipping = _allowCommentTipping;
fee = _fee;
// this overrides the value from ownable
// because otherwise it's set to the dposts contract address
owner = _owner;
}
struct Post {
uint index;
address poster;
bytes32 title;
string body;
bytes32 link;
uint boardTip;
uint tax;
uint blockNumber;
uint createdAt;
bool visible;
bool hiddenByOwner;
uint tipTotal;
}
event PostCreated(
uint index,
bytes32 title,
string body,
bytes32 link,
uint boardTip,
uint tax
);
event PostEdited(
uint index,
bytes32 title,
string body,
bytes32 link,
bool visible
);
struct Comment {
uint index;
uint postIndex;
address poster;
address commenter;
string body;
uint tipTotal;
uint blockNumber;
uint createdAt;
}
event CommentCreated(
uint index,
uint postIndex,
address poster,
address commenter,
string body
);
// tipping always optional even when enabled at board level
function createPost (
bytes32 title,
string body,
bytes32 link,
uint boardTip,
uint taxSubmitted
) public payable returns (uint)
{
require(readOnly != true);
if (!allowBoardTipping){
require(boardTip == 0);
}
uint currentPlatformTax;
if (taxSubmitted > 0){
currentPlatformTax = getCurrentPlatformTax();
require(allowTaxation);
require(taxSubmitted >= currentPlatformTax);
}
require(msg.value - boardTip - currentPlatformTax >= fee);
if (boardTip > 0) {
require(msg.sender != owner);
owner.transfer(boardTip);
}
if (fee > 0) {
owner.transfer(fee);
}
if (taxSubmitted > 0) {
platform.transfer(currentPlatformTax);
Dposts(platform).addPaidTaxToYearPot(currentPlatformTax);
uint blockYear = determineBlockYear();
if (blockYear != currentYear) {
currentYear = blockYear;
addPaidTaxToYearPot(currentPlatformTax);
addPaidTaxToTotalPaidTax(currentPlatformTax);
}
Dposts(platform).determineTax();
}
if (msg.value > 0){
// since the tax is a moving target it is quite likely the amount sent
// will be greater than the actual required tax by the time the platform gets it.
// because of this, createPost automatically kicks back the leftovers
uint leftovers = msg.value - boardTip - currentPlatformTax - fee;
if (leftovers > 0){
msg.sender.transfer(leftovers);
}
}
uint index = posts.length;
Post memory newPost = Post({
index: index,
poster: msg.sender,
title: title,
body: body,
link: link,
boardTip: boardTip,
tax: currentPlatformTax,
blockNumber: block.number,
createdAt: block.timestamp,
visible: true,
hiddenByOwner: false,
tipTotal: 0
});
posts.push(newPost);
posterToPostIndexes[msg.sender].push(index);
emit PostCreated(
index,
title,
body,
link,
boardTip,
currentPlatformTax
);
return index;
}
function addPaidTaxToTotalPaidTax(uint paidTax) private {
totalPaidTax += paidTax;
}
function getPostCommentsLength(uint postIndex) public view returns (uint){
require(postIndex < posts.length);
return postIndexToCommentIndexes[postIndex].length;
}
function getCommentIndexByPostCommentsIndex(uint postIndex, uint postCommentsIndex) public view returns (uint){
require (postIndex < posts.length);
uint[] storage postComments = postIndexToCommentIndexes[postIndex];
require(postCommentsIndex < postComments.length);
return postComments[postCommentsIndex];
}
function createComment(
uint postIndex,
string body
) public returns (uint) {
require(readOnly != true);
uint index = comments.length;
require(postIndex < posts.length);
Post storage _post = posts[index];
Comment memory newComment = Comment({
index: index,
postIndex: postIndex,
poster: _post.poster,
commenter: msg.sender,
body: body,
tipTotal: 0,
blockNumber: block.number,
createdAt: block.timestamp
});
comments.push(newComment);
postIndexToCommentIndexes[postIndex].push(index);
emit CommentCreated(
index,
postIndex,
_post.poster,
msg.sender,
body
);
return index;
}
modifier onlyEditablePost(uint index){
require(allowPosterEditing);
require(index < posts.length);
Post storage _post = posts[index];
require(_post.poster == msg.sender);
_;
}
function editPostTitle (
uint index,
bytes32 title
) public onlyEditablePost(index) {
require(readOnly != true);
Post storage _post = posts[index];
require(msg.sender == _post.poster);
_post.title = title;
emitPostEditedEvent(_post);
}
function editPostBody (
uint index,
string body
) public onlyEditablePost(index) {
require(readOnly != true);
Post storage _post = posts[index];
require(msg.sender == _post.poster);
_post.body = body;
emitPostEditedEvent(_post);
}
function editPostLink (
uint index,
bytes32 link
) public onlyEditablePost(index) {
require(readOnly != true);
Post storage _post = posts[index];
require(msg.sender == _post.poster);
_post.link = link;
emitPostEditedEvent(_post);
}
function editPostVisibility(uint index, bool visible) public {
require(index < posts.length);
Post storage _post = posts[index];
bool senderIsPoster = msg.sender == _post.poster;
bool senderIsOwner = msg.sender == owner;
// Board owner having previously hid the post beats poster trying to show it again
require((senderIsPoster && !_post.hiddenByOwner) || (senderIsOwner && allowOwnerModeration));
_post.visible = visible;
emitPostEditedEvent(_post);
}
function emitPostEditedEvent(Post _post) internal {
emit PostEdited(
_post.index,
_post.title,
_post.body,
_post.link,
_post.visible
);
}
function getCurrentPlatformTax() public returns (uint){
return Dposts(platform).determineTax();
}
function getPostsLength() public view returns (uint) {
return posts.length;
}
event PosterTipped(
uint postIndex,
uint tip,
uint tipTotal,
address tipper,
address poster
);
event CommenterTipped(
uint commentIndex,
uint tip,
uint tipTotal,
address tipper,
address poster
);
function tipPoster(uint postIndex) public payable returns (uint) {
require(msg.value > 0);
require(postIndex < posts.length);
Post storage _post = posts[postIndex];
require(msg.sender != _post.poster);
_post.poster.transfer(msg.value);
_post.tipTotal += msg.value;
emit PosterTipped(
postIndex,
msg.value,
_post.tipTotal,
msg.sender,
_post.poster
);
return _post.tipTotal;
}
function tipCommenter(uint commentIndex) public payable returns (uint) {
require(msg.value > 0);
require(commentIndex < comments.length);
Comment storage _comment = comments[commentIndex];
require(msg.sender != _comment.commenter);
_comment.poster.transfer(msg.value);
_comment.tipTotal += msg.value;