eip-1973.md
1 --- 2 eip: 1973 3 title: Scalable Rewards 4 author: Lee Raj (@lerajk), Qin Jian (@qinjian) 5 type: Standards Track 6 category: ERC 7 status: Draft 8 created: 2019-04-01 9 --- 10 11 ## Simple Summary 12 13 A mintable token rewards interface that mints 'n' tokens per block which are distributed equally among the 'm' participants in the DAPP's ecosystem. 14 15 ## Abstract 16 17 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. 18 19 20 ## Motivation 21 22 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. 23 24 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. 25 26 ## Specification 27 28 ### Definitions 29 30 `token amount per participant in the ecosytem or TPP (token per participant)`: TPP = (token amount to mint / total active participants) 31 32 `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 ) 33 34 roundMask = (10/100) + (12/95) 35 36 `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 37 38 participantMask = previous roundMask OR (current roundMask - TPP) 39 40 `rewards for msg.sender`: roundMask - participantMask 41 42 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: 43 44 ``` 45 t1 roundMask = (20/10) 46 t2 roundMask = (20/10) + (20/11) 47 t3 roundMask = (20/10) + (20/11) + (20/11) 48 t4 roundMask = (20/10) + (20/11) + (20/11) + (20/11) 49 t5 roundMask = (20/10) + (20/11) + (20/11) + (20/11)+ (20/10) 50 t6 roundMask = (20/10) + (20/11) + (20/11) + (20/11)+ (20/10) + (20/10) 51 ``` 52 53 Total tokens released in 6 transactions = 60 tokens 54 55 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 : 56 57 rewards for msg.sender: `[t4 roundMask = (20/10) + (20/11)+ (20/11) + (20/11)] - [participantMask = (20/10)] = [rewards = (20/11)+ (20/11) + (20/11)]` 58 59 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. 60 61 `tokensPerBlock`: the amount of tokens that will be released per block 62 63 `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 64 65 `lastMintedBlockNumber`: the block number on which last 'n' tokens were minted 66 67 `totalParticipants` : the total number of participants in the DApp network 68 69 `tokencontractAddress` : the contract address to which tokens will be minted, default is address(this) 70 71 ```solidity 72 73 pragma solidity ^0.5.2; 74 75 import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; 76 import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; 77 78 contract Rewards is ERC20Mintable, ERC20Detailed { 79 80 using SafeMath for uint256; 81 82 uint256 public roundMask; 83 uint256 public lastMintedBlockNumber; 84 uint256 public totalParticipants = 0; 85 uint256 public tokensPerBlock; 86 uint256 public blockFreezeInterval; 87 address public tokencontractAddress = address(this); 88 mapping(address => uint256) public participantMask; 89 90 /** 91 * @dev constructor, initializes variables. 92 * @param _tokensPerBlock The amount of token that will be released per block, entered in wei format (E.g. 1000000000000000000) 93 * @param _blockFreezeInterval The amount of blocks that need to pass (E.g. 1, 10, 100) before more tokens are brought into the ecosystem. 94 */ 95 constructor(uint256 _tokensPerBlock, uint256 _blockFreezeInterval) public ERC20Detailed("Simple Token", "SIM", 18){ 96 lastMintedBlockNumber = block.number; 97 tokensPerBlock = _tokensPerBlock; 98 blockFreezeInterval = _blockFreezeInterval; 99 } 100 101 /** 102 * @dev Modifier to check if msg.sender is whitelisted as a minter. 103 */ 104 modifier isAuthorized() { 105 require(isMinter(msg.sender)); 106 _; 107 } 108 109 /** 110 * @dev Function to add participants in the network. 111 * @param _minter The address that will be able to mint tokens. 112 * @return A boolean that indicates if the operation was successful. 113 */ 114 function addMinters(address _minter) external returns (bool) { 115 _addMinter(_minter); 116 totalParticipants = totalParticipants.add(1); 117 updateParticipantMask(_minter); 118 return true; 119 } 120 121 122 /** 123 * @dev Function to remove participants in the network. 124 * @param _minter The address that will be unable to mint tokens. 125 * @return A boolean that indicates if the operation was successful. 126 */ 127 function removeMinters(address _minter) external returns (bool) { 128 totalParticipants = totalParticipants.sub(1); 129 _removeMinter(_minter); 130 return true; 131 } 132 133 134 /** 135 * @dev Function to introduce new tokens in the network. 136 * @return A boolean that indicates if the operation was successful. 137 */ 138 function trigger() external isAuthorized returns (bool) { 139 bool res = readyToMint(); 140 if(res == false) { 141 return false; 142 } else { 143 mintTokens(); 144 return true; 145 } 146 } 147 148 /** 149 * @dev Function to withdraw rewarded tokens by a participant. 150 * @return A boolean that indicates if the operation was successful. 151 */ 152 function withdraw() external isAuthorized returns (bool) { 153 uint256 amount = calculateRewards(); 154 require(amount >0); 155 ERC20(tokencontractAddress).transfer(msg.sender, amount); 156 } 157 158 /** 159 * @dev Function to check if new tokens are ready to be minted. 160 * @return A boolean that indicates if the operation was successful. 161 */ 162 function readyToMint() public view returns (bool) { 163 uint256 currentBlockNumber = block.number; 164 uint256 lastBlockNumber = lastMintedBlockNumber; 165 if(currentBlockNumber > lastBlockNumber + blockFreezeInterval) { 166 return true; 167 } else { 168 return false; 169 } 170 } 171 172 /** 173 * @dev Function to calculate current rewards for a participant. 174 * @return A uint that returns the calculated rewards amount. 175 */ 176 function calculateRewards() private returns (uint256) { 177 uint256 playerMask = participantMask[msg.sender]; 178 uint256 rewards = roundMask.sub(playerMask); 179 updateParticipantMask(msg.sender); 180 return rewards; 181 } 182 183 /** 184 * @dev Function to mint new tokens into the economy. 185 * @return A boolean that indicates if the operation was successful. 186 */ 187 function mintTokens() private returns (bool) { 188 uint256 currentBlockNumber = block.number; 189 uint256 tokenReleaseAmount = (currentBlockNumber.sub(lastMintedBlockNumber)).mul(tokensPerBlock); 190 lastMintedBlockNumber = currentBlockNumber; 191 mint(tokencontractAddress, tokenReleaseAmount); 192 calculateTPP(tokenReleaseAmount); 193 return true; 194 } 195 196 /** 197 * @dev Function to calculate TPP (token amount per participant). 198 * @return A boolean that indicates if the operation was successful. 199 */ 200 function calculateTPP(uint256 tokens) private returns (bool) { 201 uint256 tpp = tokens.div(totalParticipants); 202 updateRoundMask(tpp); 203 return true; 204 } 205 206 /** 207 * @dev Function to update round mask. 208 * @return A boolean that indicates if the operation was successful. 209 */ 210 function updateRoundMask(uint256 tpp) private returns (bool) { 211 roundMask = roundMask.add(tpp); 212 return true; 213 } 214 215 /** 216 * @dev Function to update participant mask (store the previous round mask) 217 * @return A boolean that indicates if the operation was successful. 218 */ 219 function updateParticipantMask(address participant) private returns (bool) { 220 uint256 previousRoundMask = roundMask; 221 participantMask[participant] = previousRoundMask; 222 return true; 223 } 224 225 } 226 ``` 227 228 ## Rationale 229 230 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. 231 232 ## Backwards Compatibility 233 234 Not Applicable. 235 236 ## Test Cases 237 238 WIP, will be added. 239 240 ## Implementation 241 242 WIP, a proper implementation will be added later.A sample example is below: 243 244 `etherscan rewards contract` : https://ropsten.etherscan.io/address/0x8b0abfc541ab7558857816a67e186221adf887bc#tokentxns 245 246 `Step 1` : deploy Rewards contract with the following parameters_tokensPerBlock = 1e18, _blockFreezeInterval = 1 247 248 `Step 2` : add Alice(0x123) and Bob(0x456) as minters, addMinters(address _minter) 249 250 `Step 3` : call trigger() from Alice / Bob's account. 65 blocks are passed, hence 65 SIM tokens are minted. The RM is 32500000000000000000 251 252 `Step 4` : Alice withdraws and receives 32.5 SIM tokens (65 tokens / 2 participants) and her PM = 32500000000000000000 253 254 `Step 5` : add Satoshi(0x321) and Vitalik(0x654) as minters, addMinters(address _minter) 255 256 `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 257 258 `Step 7` : Alice withdraws and receives 25.25 SIM tokens (101 tokens / 4 participants) and her PM = 57750000000000000000 259 260 `Step 8` : Bob withdraws and receives 57.75 SIM tokens ((65 tokens / 2 participants) + (101 tokens / 4 participants)). Bob's PM = 57750000000000000000 261 262 ## Copyright 263 264 Copyright and related rights waived via CC0. 265 266 ## References 267 268 1. Scalable Reward Distribution on the Ethereum Blockchain by Bogdan Batog, Lucian Boca and Nick Johnson 269 270 2. Fomo3d DApp, https://fomo3d.hostedwiki.co/