A mintable token rewards interface that mints ‘n’ tokens per block which are distributed equally among the ‘m’ participants in the DAPP’s ecosystem.
Abstract
The mintable token rewards interface allows DApps to build a token economy where token rewards are distributed equally among the active participants. The tokens are minted based on per block basis that are configurable (E.g. 10.2356 tokens per block, 0.1 token per block, 1350 tokens per block) and the mint function can be initiated by any active participant. The token rewards distributed to each participant is dependent on the number of participants in the network. At the beginning, when the network has low volume, the tokens rewards per participant is high but as the network scales the token rewards decreases dynamically.
## Motivation
Distributing tokens through a push system to a large amount of participants fails due to block gas limit. As the number of participants in the network grow to tens of thousands, keeping track of the iterable registry of participants and their corresponding rewards in a push system becomes unmanagable. E.g. Looping through 5000 addresses to distribute 0.0000001 reward tokens is highly inefficient. Furthermore, the gas fees in these transactions are high and needs to be undertaken by the DApp developer or the respective company, leading to centralization concerns.
A pull system is required to keep the application completely decentralized and to avoid the block gas limit problem. However, no standard solution has been proposed to distribute scalable rewards to tens of thousands participants with a pull system. This is what we propose with this EIP through concepts like TPP, round mask, participant mask.
Specification
Definitions
token amount per participant in the ecosytem or TPP (token per participant): TPP = (token amount to mint / total active participants)
roundMask: the cumulative snapshot of TPP over time for the token contract. E.g. transactionOne = 10 tokens are minted with 100 available participants (TPP = 10 / 100) , transactionTwo = 12 tokens are minted with 95 participants (TPP = 12 / 95 )
roundMask = (10/100) + (12/95)
participantMask: is used to keep track of a msg.sender (participant) rewards over time. When a msg.sender joins or leaves the ecosystem, the player mask is updated
participantMask = previous roundMask OR (current roundMask - TPP)
rewards for msg.sender: roundMask - participantMask
E.g. Let’s assume a total of 6 transactions (smart contract triggers or functions calls) are in place with 10 existing participants (denominator) and 20 tokens (numerator) are minted per transaction. At 2nd transaction, the 11th participant joins the network and exits before 5th transaction, the 11th participant’s balance is as follows:
Total tokens released in 6 transactions = 60 tokens
As the participant joins at t2 and leaves before t5, the participant deserves the rewards between t2 and t4. When the participant joins at t2, the ‘participantMask = (20/10)’, when the participant leaves before t5, the cumulative deserved reward tokens are :
When the same participant joins the ecosystem at a later point (t27 or t35), a new ‘participantMask’ is given that is used to calculate the new deserved reward tokens when the participant exits. This process continues dynamically for each participant.
tokensPerBlock: the amount of tokens that will be released per block
blockFreezeInterval: the number of blocks that need to pass until the next mint. E.g. if set to 50 and ‘n’ tokens were minted at block ‘b’, the next ‘n’ tokens won’t be minted until ‘b + 50’ blocks have passed
lastMintedBlockNumber: the block number on which last ‘n’ tokens were minted
totalParticipants : the total number of participants in the DApp network
tokencontractAddress : the contract address to which tokens will be minted, default is address(this)
pragmasolidity^0.5.2;import"openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol";import"openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol";contractRewardsisERC20Mintable,ERC20Detailed{usingSafeMathforuint256;uint256publicroundMask;uint256publiclastMintedBlockNumber;uint256publictotalParticipants=0;uint256publictokensPerBlock;uint256publicblockFreezeInterval;addresspublictokencontractAddress=address(this);mapping(address=>uint256)publicparticipantMask;/**
* @dev constructor, initializes variables.
* @param _tokensPerBlock The amount of token that will be released per block, entered in wei format (E.g. 1000000000000000000)
* @param _blockFreezeInterval The amount of blocks that need to pass (E.g. 1, 10, 100) before more tokens are brought into the ecosystem.
*/constructor(uint256_tokensPerBlock,uint256_blockFreezeInterval)publicERC20Detailed("Simple Token","SIM",18){lastMintedBlockNumber=block.number;tokensPerBlock=_tokensPerBlock;blockFreezeInterval=_blockFreezeInterval;}/**
* @dev Modifier to check if msg.sender is whitelisted as a minter.
*/modifierisAuthorized(){require(isMinter(msg.sender));_;}/**
* @dev Function to add participants in the network.
* @param _minter The address that will be able to mint tokens.
* @return A boolean that indicates if the operation was successful.
*/functionaddMinters(address_minter)externalreturns(bool){_addMinter(_minter);totalParticipants=totalParticipants.add(1);updateParticipantMask(_minter);returntrue;}/**
* @dev Function to remove participants in the network.
* @param _minter The address that will be unable to mint tokens.
* @return A boolean that indicates if the operation was successful.
*/functionremoveMinters(address_minter)externalreturns(bool){totalParticipants=totalParticipants.sub(1);_removeMinter(_minter);returntrue;}/**
* @dev Function to introduce new tokens in the network.
* @return A boolean that indicates if the operation was successful.
*/functiontrigger()externalisAuthorizedreturns(bool){boolres=readyToMint();if(res==false){returnfalse;}else{mintTokens();returntrue;}}/**
* @dev Function to withdraw rewarded tokens by a participant.
* @return A boolean that indicates if the operation was successful.
*/functionwithdraw()externalisAuthorizedreturns(bool){uint256amount=calculateRewards();require(amount>0);ERC20(tokencontractAddress).transfer(msg.sender,amount);}/**
* @dev Function to check if new tokens are ready to be minted.
* @return A boolean that indicates if the operation was successful.
*/functionreadyToMint()publicviewreturns(bool){uint256currentBlockNumber=block.number;uint256lastBlockNumber=lastMintedBlockNumber;if(currentBlockNumber>lastBlockNumber+blockFreezeInterval){returntrue;}else{returnfalse;}}/**
* @dev Function to calculate current rewards for a participant.
* @return A uint that returns the calculated rewards amount.
*/functioncalculateRewards()privatereturns(uint256){uint256playerMask=participantMask[msg.sender];uint256rewards=roundMask.sub(playerMask);updateParticipantMask(msg.sender);returnrewards;}/**
* @dev Function to mint new tokens into the economy.
* @return A boolean that indicates if the operation was successful.
*/functionmintTokens()privatereturns(bool){uint256currentBlockNumber=block.number;uint256tokenReleaseAmount=(currentBlockNumber.sub(lastMintedBlockNumber)).mul(tokensPerBlock);lastMintedBlockNumber=currentBlockNumber;mint(tokencontractAddress,tokenReleaseAmount);calculateTPP(tokenReleaseAmount);returntrue;}/**
* @dev Function to calculate TPP (token amount per participant).
* @return A boolean that indicates if the operation was successful.
*/functioncalculateTPP(uint256tokens)privatereturns(bool){uint256tpp=tokens.div(totalParticipants);updateRoundMask(tpp);returntrue;}/**
* @dev Function to update round mask.
* @return A boolean that indicates if the operation was successful.
*/functionupdateRoundMask(uint256tpp)privatereturns(bool){roundMask=roundMask.add(tpp);returntrue;}/**
* @dev Function to update participant mask (store the previous round mask)
* @return A boolean that indicates if the operation was successful.
*/functionupdateParticipantMask(addressparticipant)privatereturns(bool){uint256previousRoundMask=roundMask;participantMask[participant]=previousRoundMask;returntrue;}}
Rationale
Currently, there is no standard for a scalable reward distribution mechanism. In order to create a sustainable cryptoeconomic environment within DAPPs, incentives play a large role. However, without a scalable way to distribute rewards to tens of thousands of participants, most DAPPs lack a good incentive structure. The ones with a sustainable cryptoeconomic environment depend heavily on centralized servers or a group of selective nodes to trigger the smart contracts. But, in order to keep an application truly decentralized, the reward distribution mechanism must depend on the active participants itself and scale as the number of participants grow. This is what this EIP intends to accomplish.
Backwards Compatibility
Not Applicable.
Test Cases
WIP, will be added.
Implementation
WIP, a proper implementation will be added later.A sample example is below:
Step 1 : deploy Rewards contract with the following parameters_tokensPerBlock = 1e18, _blockFreezeInterval = 1
Step 2 : add Alice(0x123) and Bob(0x456) as minters, addMinters(address _minter)
Step 3 : call trigger() from Alice / Bob’s account. 65 blocks are passed, hence 65 SIM tokens are minted. The RM is 32500000000000000000
Step 4 : Alice withdraws and receives 32.5 SIM tokens (65 tokens / 2 participants) and her PM = 32500000000000000000
Step 5 : add Satoshi(0x321) and Vitalik(0x654) as minters, addMinters(address _minter)
Step 6 : call trigger() from Alice / Bob’s / Satoshi / Vitalik account. 101 blocks are passed, hence 101 SIM tokens are minted. The RM is 57750000000000000000
Step 7 : Alice withdraws and receives 25.25 SIM tokens (101 tokens / 4 participants) and her PM = 57750000000000000000