Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-1973: 可扩展的奖励

Authors Lee Raj (@lerajk), Qin Jian (@qinjian)
Created 2019-04-01

简述

一个可增发的代币奖励接口,每个区块铸造“n”个代币,这些代币在 DAPP 生态系统中平均分配给“m”个参与者。

摘要

可增发的代币奖励接口允许 DApp 构建一个代币经济,其中代币奖励在活跃参与者之间平均分配。代币基于每个区块进行铸造,并且是可配置的(例如,每个区块 10.2356 个代币,每个区块 0.1 个代币,每个区块 1350 个代币),并且铸造函数可以由任何活跃参与者启动。分发给每个参与者的代币奖励取决于网络中的参与者数量。一开始,当网络流量较低时,每个参与者的代币奖励很高,但随着网络规模的扩大,代币奖励会动态减少。

动机

由于区块 gas 限制,通过推送系统将代币分发给大量参与者会失败。随着网络中的参与者数量增长到数万个,在推送系统中跟踪参与者的可迭代注册表及其相应的奖励变得难以管理。例如,循环访问 5000 个地址以分配 0.0000001 个奖励代币是非常低效的。此外,这些交易中的 gas 费用很高,需要由 DApp 开发人员或相关公司承担,从而导致中心化问题。

需要一个拉取系统来保持应用程序完全去中心化并避免区块 gas 限制问题。但是,尚未提出标准解决方案来通过拉取系统向数万名参与者分发可扩展的奖励。这就是我们通过 TPP、round mask、participant mask 等概念提出的这个 EIP。

规范

定义

生态系统中每个参与者的代币数量或 TPP (每个参与者的代币):TPP = (要铸造的代币数量 / 活跃参与者的总数)

roundMask:代币合约中 TPP 随时间的累积快照。例如,transactionOne = 铸造 10 个代币,有 100 个可用参与者 (TPP = 10 / 100),transactionTwo = 铸造 12 个代币,有 95 个参与者 (TPP = 12 / 95)

roundMask = (10/100) + (12/95)

participantMask:用于跟踪 msg.sender(参与者)随时间的奖励。当 msg.sender 加入或离开生态系统时,player mask 会更新

participantMask = 之前的 roundMask 或 (当前的 roundMask - TPP)

msg.sender 的奖励:roundMask - participantMask

例如,假设总共有 6 个交易(智能合约触发器或函数调用)到位,并且每个交易铸造 10 个现有参与者(分母)和 20 个代币(分子)。在第 2 个交易时,第 11 个参与者加入网络并在第 5 个交易之前退出,第 11 个参与者的余额如下:

t1 roundMask = (20/10)
t2 roundMask = (20/10) + (20/11)
t3 roundMask = (20/10) + (20/11) + (20/11)
t4 roundMask = (20/10) + (20/11) + (20/11) + (20/11)
t5 roundMask = (20/10) + (20/11) + (20/11) + (20/11)+ (20/10)
t6 roundMask = (20/10) + (20/11) + (20/11) + (20/11)+ (20/10) + (20/10)

6 个交易中释放的代币总数 = 60 个代币

由于参与者在 t2 加入并在 t5 之前离开,因此参与者应获得 t2 和 t4 之间的奖励。当参与者在 t2 加入时,’participantMask = (20/10)’,当参与者在 t5 之前离开时,累积的应得奖励代币为:

msg.sender 的奖励:[t4 roundMask = (20/10) + (20/11)+ (20/11) + (20/11)] - [participantMask = (20/10)] = [rewards = (20/11)+ (20/11) + (20/11)]

当同一参与者稍后(t27 或 t35)加入生态系统时,会给出新的“participantMask”,用于计算参与者退出时新的应得奖励代币。此过程会为每个参与者动态地继续。

tokensPerBlock:每个区块将释放的代币数量

blockFreezeInterval:需要经过的区块数量,直到下一次铸造。例如,如果设置为 50,并且在区块“b”铸造了“n”个代币,则直到“b + 50”个区块过去后,才会铸造下一个“n”个代币

lastMintedBlockNumber:上次铸造“n”个代币的区块号

totalParticipants:DApp 网络中的参与者总数

tokencontractAddress:将铸造代币的合约地址,默认为 address(this)


pragma solidity ^0.5.2;

import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol";

contract Rewards is ERC20Mintable, ERC20Detailed {

using SafeMath for uint256;

uint256 public roundMask;
uint256 public lastMintedBlockNumber;
uint256 public totalParticipants = 0;
uint256 public tokensPerBlock; 
uint256 public blockFreezeInterval; 
address public tokencontractAddress = address(this);
mapping(address => uint256) public participantMask; 

/**
 * @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) public ERC20Detailed("Simple Token", "SIM", 18){ 
lastMintedBlockNumber = block.number;
tokensPerBlock = _tokensPerBlock;
blockFreezeInterval = _blockFreezeInterval;
}

/**
 * @dev Modifier to check if msg.sender is whitelisted as a minter. 
 */
modifier isAuthorized() {
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.
 */
function addMinters(address _minter) external returns (bool) {
_addMinter(_minter);
totalParticipants = totalParticipants.add(1);
updateParticipantMask(_minter);
return true;
}


/**
 * @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.
 */
function removeMinters(address _minter) external returns (bool) {
totalParticipants = totalParticipants.sub(1);
_removeMinter(_minter); 
return true;
}


/**
 * @dev Function to introduce new tokens in the network. 
 * @return A boolean that indicates if the operation was successful.
 */
function trigger() external isAuthorized returns (bool) {
bool res = readyToMint();
if(res == false) {
return false;
} else {
mintTokens();
return true;
}
}

/**
 * @dev Function to withdraw rewarded tokens by a participant. 
 * @return A boolean that indicates if the operation was successful.
 */
function withdraw() external isAuthorized returns (bool) {
uint256 amount = 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.
 */
function readyToMint() public view returns (bool) {
uint256 currentBlockNumber = block.number;
uint256 lastBlockNumber = lastMintedBlockNumber;
if(currentBlockNumber > lastBlockNumber + blockFreezeInterval) { 
return true;
} else {
return false;
}
}

/**
 * @dev Function to calculate current rewards for a participant. 
 * @return A uint that returns the calculated rewards amount.
 */
function calculateRewards() private returns (uint256) {
uint256 playerMask = participantMask[msg.sender];
uint256 rewards = roundMask.sub(playerMask);
updateParticipantMask(msg.sender);
return rewards;
}

/**
 * @dev Function to mint new tokens into the economy. 
 * @return A boolean that indicates if the operation was successful.
 */
function mintTokens() private returns (bool) {
uint256 currentBlockNumber = block.number;
uint256 tokenReleaseAmount = (currentBlockNumber.sub(lastMintedBlockNumber)).mul(tokensPerBlock);
lastMintedBlockNumber = currentBlockNumber;
mint(tokencontractAddress, tokenReleaseAmount);
calculateTPP(tokenReleaseAmount);
return true;
}

 /**
* @dev Function to calculate TPP (token amount per participant).
* @return A boolean that indicates if the operation was successful.
*/
function calculateTPP(uint256 tokens) private returns (bool) {
uint256 tpp = tokens.div(totalParticipants);
updateRoundMask(tpp);
return true;
}

 /**
* @dev Function to update round mask. 
* @return A boolean that indicates if the operation was successful.
*/
function updateRoundMask(uint256 tpp) private returns (bool) {
roundMask = roundMask.add(tpp);
return true;
}

 /**
* @dev Function to update participant mask (store the previous round mask)
* @return A boolean that indicates if the operation was successful.
*/
function updateParticipantMask(address participant) private returns (bool) {
uint256 previousRoundMask = roundMask;
participantMask[participant] = previousRoundMask;
return true;
}

}

理由

目前,没有可扩展的奖励分配机制的标准。为了在 DApp 中创建可持续的加密经济环境,激励措施起着重要作用。但是,如果没有一种可扩展的方式来向数万名参与者分配奖励,大多数 DApp 都缺乏良好的激励结构。那些具有可持续加密经济环境的应用程序严重依赖集中式服务器或一组选择性节点来触发智能合约。但是,为了保持应用程序真正去中心化,奖励分配机制必须依赖于活跃的参与者本身,并随着参与者数量的增长而扩展。这就是此 EIP 旨在完成的目标。

向后兼容性

不适用。

测试用例

WIP,稍后添加。

实现

WIP,稍后将添加正确的实现。一个示例见下文:

etherscan rewards contract : https://ropsten.etherscan.io/address/0x8b0abfc541ab7558857816a67e186221adf887bc#tokentxns

步骤 1:使用以下参数部署 Rewards 合约:_tokensPerBlock = 1e18, _blockFreezeInterval = 1

步骤 2:将 Alice(0x123) 和 Bob(0x456) 添加为 minter,addMinters(address _minter)

步骤 3:从 Alice / Bob 的帐户调用 trigger()。经过 65 个区块,因此铸造了 65 个 SIM 代币。RM 为 32500000000000000000

步骤 4:Alice 提取并收到 32.5 个 SIM 代币(65 个代币 / 2 个参与者),她的 PM = 32500000000000000000

步骤 5:将 Satoshi(0x321) 和 Vitalik(0x654) 添加为 minter,addMinters(address _minter)

步骤 6:从 Alice / Bob’s / Satoshi / Vitalik 帐户调用 trigger()。经过 101 个区块,因此铸造了 101 个 SIM 代币。RM 为 57750000000000000000

步骤 7:Alice 提取并收到 25.25 个 SIM 代币(101 个代币 / 4 个参与者),她的 PM = 57750000000000000000

步骤 8:Bob 提取并收到 57.75 个 SIM 代币((65 个代币 / 2 个参与者)+(101 个代币 / 4 个参与者))。Bob 的 PM = 57750000000000000000

版权

在 CC0 协议下放弃版权及相关权利。

参考

  1. Bogdan Batog、Lucian Boca 和 Nick Johnson 在以太坊区块链上的可扩展奖励分配

  2. Fomo3d DApp, https://fomo3d.hostedwiki.co/

Citation

Please cite this document as:

Lee Raj (@lerajk), Qin Jian (@qinjian), "ERC-1973: 可扩展的奖励 [DRAFT]," Ethereum Improvement Proposals, no. 1973, April 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1973.