2019-02-02 23:04:55 +07:00
pragma solidity ^ 0 . 5 . 0 ;
/*
Future Goals :
- remove admins necessity
- encourage contributors to allocate
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
* /
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 ;
}
address public token ; // token contract
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-02-02 23:04:55 +07:00
// Open Functions ----------------------------------------------------------------------------------------
// Split amount over each contributor in registry, anyone can contribute.
function allocate ( uint256 _amount ) external {
// Locals
uint256 individualAmount ;
Contributor memory cAllocator = contributors [ msg . sender ] ;
// Requirements
require ( cAllocator . addr == msg . sender ) ; // is sender a Contributor?
2019-02-03 14:33:49 +07:00
require ( ERC20Token ( token ) . transferFrom ( msg . sender , address ( this ) , _amount ) ) ;
2019-02-02 23:04:55 +07:00
// Body
// cAllocator.inPot = true;
individualAmount = _amount / registry . length ;
for ( uint256 i = 0 ; i < registry . length ; i ++ ) {
contributors [ registry [ i ] ] . allocation += individualAmount ;
}
}
2019-02-08 23:08:02 +07:00
// Getter for Dynamic Array Length
function registryLength ( ) external returns ( uint256 ) {
return registry . length ;
}
2019-02-02 23:04:55 +07:00
// Contributor Functions -------------------------------------------------------------------------------
// 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;
ERC20Token ( token ) . transferFrom ( address ( this ) , cReceiver . addr , r ) ;
}
// Allow Contributors to award allocated tokens to other Contributors
function award ( address _contributor , uint256 _amount , string calldata _praise ) external {
// Locals
Contributor storage cSender = contributors [ msg . sender ] ;
Contributor storage cReceiver = contributors [ _contributor ] ;
// Requirements
require ( cSender . addr == msg . sender ) ; // Ensure Contributors both exist, and isn't the same address
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
}
// Admin Functions -------------------------------------------------------------------------------------
// Add Contributor to Registry
function addContributor ( address _contributor ) public {
// Requirements
require ( admins [ msg . sender ] ) ;
require ( registry . length + 1 <= maxContributors ) ;
2019-02-08 23:08:02 +07:00
require ( contributors [ _contributor ] . addr == address ( 0 ) ) ;
2019-02-02 23:04:55 +07:00
// Body
Contributor storage c = contributors [ _contributor ] ;
c . addr = _contributor ;
registry . push ( _contributor ) ;
}
2019-02-03 14:33:49 +07:00
// Add Multiple Contributors to the Registry in one tx
2019-02-02 23:04:55 +07:00
function addContributors ( address [ ] calldata _newContributors ) external {
// Requirements
require ( registry . length + _newContributors . length <= maxContributors ) ;
// Body
for ( uint256 i = 0 ; i < _newContributors . length ; i ++ ) {
addContributor ( _newContributors [ i ] ) ;
}
}
// Remove Contributor from Registry
// Note: Should not be easy to remove multiple contributors in one tx
function removeContributor ( address _contributor ) external {
// Requirements
require ( admins [ msg . sender ] ) ;
// Body
// Find id of contributor address
uint256 idx = 0 ;
for ( uint256 i = 0 ; i < registry . length ; i ++ ) { // should never be longer than maxContributors, see addContributor
if ( registry [ i ] == _contributor ) {
idx = i ;
break ;
}
}
address c = registry [ idx ] ;
// Swap & Pop!
registry [ idx ] = registry [ registry . length - 1 ] ;
registry . pop ( ) ;
delete contributors [ c ] ; // TODO check if this works
}
// Implictly sets a finite limit to registry length
function setMaxContributors ( uint256 _maxContributors ) external {
// Requirements
require ( admins [ msg . sender ] ) ;
require ( _maxContributors > registry . length ) ; // have to removeContributor first
// Body
maxContributors = _maxContributors ;
}
// Zero-out allocations for contributors, minimum once a week, if allocation still exists, add to burn
function forfeitAllocations ( ) public {
// Requirements
require ( admins [ msg . sender ] ) ;
require ( block . timestamp >= lastForfeit + 1 weeks ) ; // prevents multiple admins accidently calling too quickly.
// Body
lastForfeit = block . timestamp ;
for ( uint256 i = 0 ; i < registry . length ; i ++ ) { // should never be longer than maxContributors, see addContributor
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
}
}
// Owner Functions -------------------------------------------------------------------------------------
// Set Admin flag for address to true
function addAdmin ( address _admin ) public {
// Requirements
require ( msg . sender == owner ) ;
// Body
admins [ _admin ] = true ;
}
// Set Admin flag for address to false
function removeAdmin ( address _admin ) public {
// Requirements
require ( msg . sender == owner ) ;
// Body
delete admins [ _admin ] ;
}
// Change owner address, ideally to a management contract or multisig
function changeOwner ( address payable _owner ) external {
// Requirements
require ( msg . sender == owner ) ;
// Body
removeAdmin ( owner ) ;
addAdmin ( _owner ) ;
owner = _owner ;
}
// Change Token address
// WARN: call escape first, or escape(token);
function changeToken ( address _token ) external {
// Locals
uint256 r ;
// Requirements
require ( msg . sender == owner ) ;
// 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 ] ] ;
r = c . received ;
c . received = 0 ;
c . allocation = 0 ;
// WARN: Should totalReceived and totalForfeited be zeroed-out?
2019-02-08 23:21:53 +07:00
ERC20Token ( token ) . transferFrom ( address ( this ) , c . addr , r ) ; // Transfer any owed tokens to contributor
2019-02-02 23:04:55 +07:00
}
lastForfeit = block . timestamp ;
token = _token ;
}
// Failsafe, Owner can escape hatch all Tokens and ETH from Contract.
function escape ( ) public {
// Requirements
require ( msg . sender == owner ) ;
// Body
ERC20Token ( token ) . transferFrom ( address ( this ) , owner , ERC20Token ( token ) . balanceOf ( address ( this ) ) ) ;
address ( owner ) . transfer ( address ( this ) . balance ) ;
}
// 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
function escape ( address _token ) external {
// Requirements
require ( msg . sender == owner ) ;
// Body
ERC20Token ( _token ) . transferFrom ( address ( this ) , owner , ERC20Token ( _token ) . balanceOf ( address ( this ) ) ) ;
escape ( ) ;
}
// Set Token address and initial maxContributors
constructor ( address _token , uint256 _maxContributors ) public {
// Body
owner = msg . sender ;
addAdmin ( owner ) ;
lastForfeit = block . timestamp ;
token = _token ;
maxContributors = _maxContributors ;
}
// function() public { throw; } // TODO Probably not needed?
}