/ EIPS / eip-1973.md
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/