2019-02-02 23:04:55 +07:00
pragma solidity ^ 0 . 5 . 0 ;
/*
Future Goals :
- remove admins necessity
- encourage contributors to allocate
2019-02-12 17:48:09 +07:00
- needs incentive for someone to call forfeit
- read from previous versions of the script
2019-02-02 23:04:55 +07:00
2019-02-02 23:07:28 +07:00
DApp :
2019-02-02 23:04:55 +07:00
- show tokens to allocate
- allocate token to person with praise
- leaderboard , showing amount totalReceived and totalForfeited and amount , praises https : //codepen.io/lewismcarey/pen/GJZVoG
- allows you to send SNT to meritocracy
- add / remove contributor
- add / remove adminstrator
2019-02-10 13:43:35 +07:00
Extension :
- Command :
- above command = display allocation , received , withdraw button , allocate button ? ( might be better in dapp )
- / kudos 500 " <person> " " <praise> "
2019-02-02 23:04:55 +07:00
* /
import " token/ERC20Token.sol " ;
contract Meritocracy {
struct Status {
address author ;
string praise ;
uint256 amount ;
uint256 time ; // block.timestamp
}
struct Contributor {
address addr ;
uint256 allocation ; // Amount they can send to other contributors, and amount they forfeit, when forfeit just zero this out and leave Token in contract, Owner can use escape to receive it back
uint256 totalForfeited ; // Allocations they've burnt, can be used to show non-active players.
uint256 totalReceived ;
uint256 received ; // Ignore amounts in Status struct, and use this as source of truth, can withdraw at any time
2019-02-02 23:10:51 +07:00
// bool inPot; // Require Contributor WARN: commented because there's some edge cases not dealt with
2019-02-02 23:04:55 +07:00
Status [ ] status ;
}
2019-02-10 13:43:35 +07:00
ERC20Token public token ; // token contract
2019-02-02 23:04:55 +07:00
address payable public owner ; // contract owner
uint256 public lastForfeit ; // timestamp to block admins calling forfeitAllocations too quickly
address [ ] public registry ; // array of contributor addresses
uint256 public maxContributors ; // Dynamic finite limit on registry.
2019-02-08 23:08:02 +07:00
mapping ( address => bool ) public admins ;
mapping ( address => Contributor ) public contributors ;
2019-04-10 16:21:15 -04:00
string public contributorListIPFSHash ;
2019-02-02 23:04:55 +07:00
2019-02-12 17:48:09 +07:00
Meritocracy public previousMeritocracy ; // Reference and read from previous contract
2019-02-17 14:03:45 +07:00
// Events -----------------------------------------------------------------------------------------------
event ContributorAdded ( address _contributor ) ;
event ContributorRemoved ( address _contributor ) ;
event ContributorWithdrew ( address _contributor ) ;
event ContributorTransaction ( address _cSender , address _cReceiver ) ;
event AdminAdded ( address _admin ) ;
event AdminRemoved ( address _admin ) ;
event AllocationsForfeited ( ) ;
event OwnerChanged ( address _owner ) ;
event TokenChanged ( address _token ) ;
event MaxContributorsChanged ( uint256 _maxContributors ) ;
event EscapeHatchTriggered ( address _executor ) ;
2019-02-10 14:01:03 +07:00
// Modifiers --------------------------------------------------------------------------------------------
2019-02-10 13:43:35 +07:00
// Functions only Owner can call
2019-02-17 14:03:45 +07:00
modifier onlyOwner {
2019-02-10 13:43:35 +07:00
require ( msg . sender == owner ) ;
_ ;
}
// Functions only Admin can call
2019-02-17 14:03:45 +07:00
modifier onlyAdmin {
2019-02-10 13:43:35 +07:00
require ( admins [ msg . sender ] ) ;
_ ;
}
2019-02-10 14:01:03 +07:00
// Open Functions --------------------------------------------------------------------------------------
2019-02-02 23:04:55 +07:00
2019-02-10 13:43:35 +07:00
// Split amount over each contributor in registry, any contributor can allocate? TODO maybe relax this restriction, so anyone can allocate tokens
2019-02-02 23:04:55 +07:00
function allocate ( uint256 _amount ) external {
// Locals
2019-04-10 16:21:15 -04:00
2019-02-10 13:43:35 +07:00
// Contributor memory cAllocator = contributors[msg.sender];
2019-02-02 23:04:55 +07:00
// Requirements
2019-02-10 13:43:35 +07:00
// require(cAllocator.addr != address(0)); // is sender a Contributor? TODO maybe relax this restriction.
2019-03-11 15:35:21 -04:00
uint256 individualAmount = _amount / registry . length ;
// removing decimals
2019-03-22 00:27:20 +01:00
individualAmount = ( individualAmount / 1 ether * 1 ether ) ;
2019-04-10 16:21:15 -04:00
2019-03-11 15:35:21 -04:00
uint amount = individualAmount * registry . length ;
2019-04-10 16:21:15 -04:00
2019-03-11 15:35:21 -04:00
require ( token . transferFrom ( msg . sender , address ( this ) , amount ) ) ;
2019-02-02 23:04:55 +07:00
// Body
// cAllocator.inPot = true;
for ( uint256 i = 0 ; i < registry . length ; i ++ ) {
contributors [ registry [ i ] ] . allocation += individualAmount ;
}
}
2019-02-17 14:03:45 +07:00
function getRegistry ( ) public view returns ( address [ ] memory ) {
return registry ;
2019-02-08 23:08:02 +07:00
}
2019-02-10 14:01:03 +07:00
// Contributor Functions --------------------------------------------------------------------------------
2019-02-02 23:04:55 +07:00
// Allows a contributor to withdraw their received Token, when their allocation is 0
function withdraw ( ) external {
// Locals
Contributor storage cReceiver = contributors [ msg . sender ] ;
// Requirements
require ( cReceiver . addr == msg . sender ) ; //is sender a Contributor?
require ( cReceiver . received > 0 ) ; // Contributor has received some tokens
require ( cReceiver . allocation == 0 ) ; // Contributor must allocate all Token (or have Token burnt) before they can withdraw.
// require(cReceiver.inPot); // Contributor has put some tokens into the pot
// Body
uint256 r = cReceiver . received ;
cReceiver . received = 0 ;
// cReceiver.inPot = false;
2019-03-20 10:26:55 -04:00
token . transfer ( cReceiver . addr , r ) ;
2019-02-17 14:03:45 +07:00
emit ContributorWithdrew ( cReceiver . addr ) ;
2019-02-02 23:04:55 +07:00
}
// Allow Contributors to award allocated tokens to other Contributors
2019-02-17 18:50:11 +07:00
function award ( address _contributor , uint256 _amount , string memory _praise ) public {
2019-02-02 23:04:55 +07:00
// Locals
Contributor storage cSender = contributors [ msg . sender ] ;
Contributor storage cReceiver = contributors [ _contributor ] ;
// Requirements
2019-02-12 18:03:32 +07:00
require ( _amount > 0 ) ; // Allow Non-Zero amounts only
2019-04-10 16:21:15 -04:00
require ( cSender . addr == msg . sender ) ; // Ensure Contributors both exist, and isn't the same address
2019-02-02 23:04:55 +07:00
require ( cReceiver . addr == _contributor ) ;
require ( cSender . addr != cReceiver . addr ) ; // cannot send to self
require ( cSender . allocation >= _amount ) ; // Ensure Sender has enough tokens to allocate
// Body
cSender . allocation -= _amount ; // burn is not adjusted, which is done only in forfeitAllocations
cReceiver . received += _amount ;
cReceiver . totalReceived += _amount ;
Status memory s = Status ( {
author : cSender . addr ,
praise : _praise ,
amount : _amount ,
time : block . timestamp
} ) ;
cReceiver . status . push ( s ) ; // Record the history
2019-02-17 14:03:45 +07:00
emit ContributorTransaction ( cSender . addr , cReceiver . addr ) ;
2019-02-02 23:04:55 +07:00
}
2019-03-10 09:34:12 -04:00
function getStatusLength ( address _contributor ) public view returns ( uint ) {
return contributors [ _contributor ] . status . length ;
}
function getStatus ( address _contributor , uint _index ) public view returns (
address author ,
string memory praise ,
uint256 amount ,
uint256 time
) {
author = contributors [ _contributor ] . status [ _index ] . author ;
praise = contributors [ _contributor ] . status [ _index ] . praise ;
amount = contributors [ _contributor ] . status [ _index ] . amount ;
time = contributors [ _contributor ] . status [ _index ] . time ;
}
2019-04-10 16:21:15 -04:00
// Allow Contributor to award multiple Contributors
2019-02-17 18:50:11 +07:00
function awardContributors ( address [ ] calldata _contributors , uint256 _amountEach , string calldata _praise ) external {
// Locals
Contributor storage cSender = contributors [ msg . sender ] ;
uint256 contributorsLength = _contributors . length ;
uint256 totalAmount = contributorsLength * _amountEach ;
// Requirements
require ( cSender . allocation >= totalAmount ) ;
// Body
for ( uint256 i = 0 ; i < contributorsLength ; i ++ ) {
award ( _contributors [ i ] , _amountEach , _praise ) ;
}
}
2019-02-02 23:04:55 +07:00
// Admin Functions -------------------------------------------------------------------------------------
// Add Contributor to Registry
2019-04-10 16:21:15 -04:00
function addContributor ( address _contributor , string memory _contributorListIPFSHash ) public onlyAdmin {
addContributorWithoutHash ( _contributor ) ;
// Set new IPFS hash for the list
contributorListIPFSHash = _contributorListIPFSHash ;
}
function addContributorWithoutHash ( address _contributor ) internal onlyAdmin {
2019-02-02 23:04:55 +07:00
// Requirements
2019-02-10 13:43:35 +07:00
require ( registry . length + 1 <= maxContributors ) ; // Don't go out of bounds
require ( contributors [ _contributor ] . addr == address ( 0 ) ) ; // Contributor doesn't exist
2019-02-02 23:04:55 +07:00
// Body
Contributor storage c = contributors [ _contributor ] ;
c . addr = _contributor ;
registry . push ( _contributor ) ;
2019-02-17 14:03:45 +07:00
emit ContributorAdded ( _contributor ) ;
2019-02-02 23:04:55 +07:00
}
2019-02-03 14:33:49 +07:00
// Add Multiple Contributors to the Registry in one tx
2019-04-10 16:21:15 -04:00
function addContributors ( address [ ] calldata _newContributors , string calldata _contributorListIPFSHash ) external onlyAdmin {
2019-02-10 14:01:03 +07:00
// Locals
uint256 newContributorLength = _newContributors . length ;
2019-02-02 23:04:55 +07:00
// Requirements
2019-02-10 14:01:03 +07:00
require ( registry . length + newContributorLength <= maxContributors ) ; // Don't go out of bounds
2019-02-02 23:04:55 +07:00
// Body
2019-02-10 14:01:03 +07:00
for ( uint256 i = 0 ; i < newContributorLength ; i ++ ) {
2019-04-10 16:21:15 -04:00
addContributorWithoutHash ( _newContributors [ i ] ) ;
2019-02-02 23:04:55 +07:00
}
2019-04-10 16:21:15 -04:00
// Set new IPFS hash for the list
contributorListIPFSHash = _contributorListIPFSHash ;
2019-02-02 23:04:55 +07:00
}
// Remove Contributor from Registry
// Note: Should not be easy to remove multiple contributors in one tx
2019-02-10 13:43:35 +07:00
// WARN: Changed to idx, client can do loop by enumerating registry
2019-04-10 15:42:16 -04:00
function removeContributor ( uint256 idx , string calldata _contributorListIPFSHash ) external onlyAdmin { // address _contributor
2019-02-10 14:01:03 +07:00
// Locals
2019-02-17 14:03:45 +07:00
uint256 registryLength = registry . length - 1 ;
2019-02-02 23:04:55 +07:00
// Requirements
2019-04-10 16:12:47 -04:00
require ( idx <= registryLength ) ; // idx needs to be smaller than registry.length - 1 OR maxContributors
2019-02-02 23:04:55 +07:00
// Body
address c = registry [ idx ] ;
// Swap & Pop!
2019-02-17 14:03:45 +07:00
registry [ idx ] = registry [ registryLength ] ;
2019-02-02 23:04:55 +07:00
registry . pop ( ) ;
delete contributors [ c ] ; // TODO check if this works
2019-04-10 15:42:16 -04:00
// Set new IPFS hash for the list
contributorListIPFSHash = _contributorListIPFSHash ;
2019-02-17 14:03:45 +07:00
emit ContributorRemoved ( c ) ;
2019-02-02 23:04:55 +07:00
}
// Implictly sets a finite limit to registry length
2019-02-17 14:03:45 +07:00
function setMaxContributors ( uint256 _maxContributors ) external onlyAdmin {
2019-02-02 23:04:55 +07:00
require ( _maxContributors > registry . length ) ; // have to removeContributor first
// Body
maxContributors = _maxContributors ;
2019-02-17 14:03:45 +07:00
emit MaxContributorsChanged ( maxContributors ) ;
2019-02-02 23:04:55 +07:00
}
// Zero-out allocations for contributors, minimum once a week, if allocation still exists, add to burn
2019-02-17 14:03:45 +07:00
function forfeitAllocations ( ) public onlyAdmin {
2019-02-10 14:01:03 +07:00
// Locals
2019-02-17 14:03:45 +07:00
uint256 registryLength = registry . length ;
2019-02-10 14:01:03 +07:00
// Requirements
2019-02-12 17:48:09 +07:00
require ( block . timestamp >= lastForfeit + 1 weeks ) ; // prevents admins accidently calling too quickly.
2019-02-02 23:04:55 +07:00
// Body
2019-04-10 16:21:15 -04:00
lastForfeit = block . timestamp ;
2019-02-17 14:03:45 +07:00
for ( uint256 i = 0 ; i < registryLength ; i ++ ) { // should never be longer than maxContributors, see addContributor
2019-02-02 23:04:55 +07:00
Contributor storage c = contributors [ registry [ i ] ] ;
c . totalForfeited += c . allocation ; // Shaaaaame!
c . allocation = 0 ;
// cReceiver.inPot = false; // Contributor has to put tokens into next round
}
2019-02-17 14:03:45 +07:00
emit AllocationsForfeited ( ) ;
2019-02-02 23:04:55 +07:00
}
// Owner Functions -------------------------------------------------------------------------------------
// Set Admin flag for address to true
2019-02-17 14:03:45 +07:00
function addAdmin ( address _admin ) public onlyOwner {
2019-02-02 23:04:55 +07:00
admins [ _admin ] = true ;
2019-02-17 14:03:45 +07:00
emit AdminAdded ( _admin ) ;
2019-02-02 23:04:55 +07:00
}
// Set Admin flag for address to false
2019-02-17 14:03:45 +07:00
function removeAdmin ( address _admin ) public onlyOwner {
2019-02-02 23:04:55 +07:00
delete admins [ _admin ] ;
2019-02-17 14:03:45 +07:00
emit AdminRemoved ( _admin ) ;
2019-02-02 23:04:55 +07:00
}
// Change owner address, ideally to a management contract or multisig
2019-02-17 14:03:45 +07:00
function changeOwner ( address payable _owner ) external onlyOwner {
2019-02-02 23:04:55 +07:00
// Body
removeAdmin ( owner ) ;
addAdmin ( _owner ) ;
owner = _owner ;
2019-02-17 14:03:45 +07:00
emit OwnerChanged ( owner ) ;
2019-02-02 23:04:55 +07:00
}
// Change Token address
// WARN: call escape first, or escape(token);
2019-02-17 14:03:45 +07:00
function changeToken ( address _token ) external onlyOwner {
2019-02-02 23:04:55 +07:00
// Body
// Zero-out allocation and received, send out received tokens before token switch.
for ( uint256 i = 0 ; i < registry . length ; i ++ ) {
Contributor storage c = contributors [ registry [ i ] ] ;
2019-02-10 13:43:35 +07:00
uint256 r = c . received ;
2019-02-02 23:04:55 +07:00
c . received = 0 ;
c . allocation = 0 ;
2019-04-10 16:21:15 -04:00
// WARN: Should totalReceived and totalForfeited be zeroed-out?
token . transfer ( c . addr , r ) ; // Transfer any owed tokens to contributor
2019-02-02 23:04:55 +07:00
}
lastForfeit = block . timestamp ;
2019-02-10 13:43:35 +07:00
token = ERC20Token ( _token ) ;
2019-02-17 14:03:45 +07:00
emit TokenChanged ( _token ) ;
2019-02-02 23:04:55 +07:00
}
// Failsafe, Owner can escape hatch all Tokens and ETH from Contract.
2019-02-17 14:03:45 +07:00
function escape ( ) public onlyOwner {
2019-02-02 23:04:55 +07:00
// Body
2019-03-20 10:26:55 -04:00
token . transfer ( owner , token . balanceOf ( address ( this ) ) ) ;
2019-02-10 13:43:35 +07:00
owner . transfer ( address ( this ) . balance ) ;
2019-02-17 14:03:45 +07:00
emit EscapeHatchTriggered ( msg . sender ) ;
2019-02-02 23:04:55 +07:00
}
// Overloaded failsafe function, recourse incase changeToken is called before escape and funds are in a different token
// Don't want to require in changeToken incase bad behaviour of ERC20 token
2019-02-17 14:03:45 +07:00
function escape ( address _token ) external onlyOwner {
2019-02-02 23:04:55 +07:00
// Body
2019-02-10 13:43:35 +07:00
ERC20Token t = ERC20Token ( _token ) ;
2019-03-20 10:26:55 -04:00
t . transfer ( owner , t . balanceOf ( address ( this ) ) ) ;
2019-02-02 23:04:55 +07:00
escape ( ) ;
}
2019-02-12 17:48:09 +07:00
// Housekeeping -----------------------------------------------------------------------------------------
2019-02-17 14:03:45 +07:00
// function importPreviousMeritocracyData() private onlyOwner { // onlyOwner not explicitly needed but safer than sorry, it's problem with overloaded function
2019-02-12 17:48:09 +07:00
// // if previousMeritocracy != address(0) { // TODO better truthiness test, casting?
// // // Do Stuff
// // }
// }
2019-02-10 14:01:03 +07:00
// Constructor ------------------------------------------------------------------------------------------
2019-02-12 17:48:09 +07:00
// constructor(address _token, uint256 _maxContributors, address _previousMeritocracy) public {
2019-04-10 16:21:15 -04:00
2019-02-12 17:48:09 +07:00
// }
// Set Owner, Token address, initial maxContributors
2019-04-10 16:21:15 -04:00
constructor ( address _token , uint256 _maxContributors , string memory _contributorListIPFSHash ) public {
2019-02-02 23:04:55 +07:00
// Body
owner = msg . sender ;
addAdmin ( owner ) ;
lastForfeit = block . timestamp ;
2019-02-10 13:43:35 +07:00
token = ERC20Token ( _token ) ;
2019-02-02 23:04:55 +07:00
maxContributors = _maxContributors ;
2019-04-10 16:21:15 -04:00
contributorListIPFSHash = _contributorListIPFSHash ;
2019-02-12 17:48:09 +07:00
// previousMeritocracy = Meritocracy(_previousMeritocracy);
// importPreviousMeritocracyData() TODO
2019-02-02 23:04:55 +07:00
}
2019-03-22 00:27:20 +01:00
}