在ERC20合约中如何实现分红的自动计算和发放
InERC20合约中如何实现分红的自动计算和发放?
本文将介绍如何编写一个基于ERC20标准的分红合约,通过持币量等比例分配以太。这篇文章将综合之前的写ERC20 token合约的内容,并提供详细的代码注释。
建议先阅读完整代码,再查看注释,如果理解了注释,就不必浪费时间看其他部分了。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract SimpleDividendToken { // 项目名称 string public name = "Simple Dividend Token"; // Token符号 string public symbol = "SDIV"; // Token小数点位数,不要改变 uint8 public decimals = 0; // 总发行量 uint256 public totalSupply = 1_000_000_000 * (uint256(10) ** decimals); // 存储每个账户的Token余额 mapping(address => uint256) public balanceOf; // 存储每个账户的分红额度 mapping(address => uint256) public dividendBalanceOf; // 用于Records所有账户的分红比例,是一个对所有账户都适用的变量 uint256 public dividendPerToken; // 存储每个持有Token账户所对应的分红比例,初始值为0,在update函数中作为temp角色 mapping(address => uint256) public dividendCreditedTo; // 存储授权使用额度 mapping(address => mapping(address => uint256)) public allowance; // Transfer事件,用来记录Token转移 event Transfer(address indexed from, address indexed to, uint256 value); // Approval事件,用来记录授权使用额度 event Approval(address indexed owner, address indexed spender, uint256 value); // 构造器,初始化总供应量,并且记录Transfer事件 constructor() { balanceOf[msg.sender] = totalSupply; emit Transfer(address(0), msg.sender, totalSupply); } // 更新账户的分红比例 function update(address account) internal { uint256 owed = dividendPerToken - dividendCreditedTo[account]; dividendBalanceOf[account] += balanceOf[account] * owed; dividendCreditedTo[account] = dividendPerToken; } // 转账函数 function transfer(address to, uint256 value) public returns (bool success) { require(balanceOf[msg.sender] >= value); update(msg.sender); update(to); balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); return true; } // 授权转账函数 function transferFrom(address from, address to, uint256 value) public returns (bool success) { require(value <= balanceOf[from]); require(value <= allowance[from][msg.sender]); update(from); update(to); balanceOf[from] -= value; balanceOf[to] += value; allowance[from][msg.sender] -= value; emit Transfer(from, to, value); return true; } // 存款函数,只接受以太 function deposit() public payable { dividendPerToken += msg.value / totalSupply; } // 取款函数 function withdraw() public { update(msg.sender); uint256 amount = dividendBalanceOf[msg.sender]; dividendBalanceOf[msg.sender] = 0; payable(msg.sender).transfer(amount); } // 授权使用额度函数 function approve(address spender, uint256 value) public returns (bool success) { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); //存款函数,只接受以太,可支付payable function deposit() public payable { //计算新的以太token比值,用账户发送的以太除以token总供应量 dividendPerToken += msg.value / totalSupply; } //取款函数,需要完成以下几件事: function withdraw() public { //更新账户当前的分红比 update(msg.sender); //当前分红取款额度,注意,这里是eth uint256 amount = dividendBalanceOf[msg.sender]; //将此账户的分红额度清零 dividendBalanceOf[msg.sender] = 0; //合约给地址打以太,可是token呢,不归还??? payable(msg.sender).transfer(amount); } //授权使用额度,输入花费方和额度 function approve(address spender, uint256 value) public returns (bool success) { allowance[msg.sender][spender] = value; //记录事件 emit Approval(msg.sender, spender, value); return true; }
Smart Contracts在处理分红时面临着一些限制,其中最显著的是gas成本的高昂。当持有token的人数超过百万级别时,循环分发分红所需的gas量将会非常大。
每个账户的分红状态可以分为以下三种:
- 已发放:分红已经发放到该账户。
- 已认定但未发放:分红被认定属于该账户,但尚未发放。
- 未认定:分红尚未被认定,但有足够的信息可用于认定。
所有账户的新分红都从“未认定”状态开始。一旦账户参与转账或提款,合约会将该账户应有的分红归集到已认定但未发放状态。提款时,状态将变更为“已发放”。
合约使用以下变量来记录每个token持有人的分红:
uint256 public dividenPerToken; mapping(address => uint256) dividendBalanceOf; mapping(address => uint256) dividendCreditTo;
说明:
- dividendPerToken:等于收集到的总以太和token的比值,举个例子,假如合约创建以后一共收集到了200个以太,然后token总量是100个,那么就是2以太/token。由于token总量固定,以太只会增长,因此这个值不会下降。
- dividendBalanceOf:此映射代表每个账户所认定但是还没发放的分红。转账以后这个变量的值会降低。
- dividendCreditedPerToken::此映射代表每个token对应的既往已经认定的总以太。
以下几个变量会被用来计算每个账户所应得的分红:
dividendPerToken:代表以太和token的比值,是所有账户都适用的变量。例如,如果合约中一共收集了200个以太,token总量是100个,那么dividendPerToken就是2以太/token。由于以太总量不断增长,这个值不会下降。 dividendBalanceOf:代表每个账户尚未发放的分红总额,处于已认定但未发放状态。转账时,该变量的值会下降。 dividendCreditedPerToken:代表每个token所对应的已认定总以太数额。 为了增强普通ERC20合约,需要进行以下更改:
转账函数:需要更新每个账户的dividendCreditedPerToken和dividendBalanceOf,以计算每个账户所应得的分红。 合约需要支持自由存取分红。 转账功能: 每次转账都需要更新每个账户的dividendCreditedTo和dividendBalanceOf,并根据dividendPerToken进行调整。
function update(address account) internal { uint256 owed = dividendPerToken - dividendCreditedTo[account]; dividendBalanceOf[account] += balanceOf[account] * owed; dividendCreditedTo[account] = dividendPerToken; }
说明:这个update函数获取一个账户目前为止的分红额度。之后,合约只需要在transfer函数中更新发送方和接收方的token地址就可以了。
function transfer(address to, uint256 value) public returns (bool success) { require(balanceOf[msg.sender] >= value); update(msg.sender); update(to); baanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); return true; } function transferFrom(address from, address to, uint256 value) public returns (bool success) { require(value <= balanceOf[from]); require(value <= allowance[from][msg.sender]) update(from); update(to); balanceOf[from] -= value; balanceOf[to] += value; allowance[from][msg.sender] -= value; emit Transfer(from, to, value); return true; }
存取
deposit函数接受以太,并且更新全局变量dividendPerToken
function deposit() public payable { dividendPerToken += msg.value / totalSupply; }
总结:这个合约中,dividendPerToken和dividendCreditedTo的区别可能较难理解。为了节省gas,分红只在最终退出时发放,其他时候只进行记账。dividendPerToken表示总以太和总Token的比例,通过每个账户拥有的token数值乘以dividendPerToken就可以计算出该账户的分红额度。由于以太一直在增加,因此在账户每次交互时需要更新其拥有的分红额度。update函数的作用就在于此,通过将最新的以太token比例赋值给dividendCreditedTo,实现记录该账户上次与合约交互时的以太token比例。因此,dividendCreditedTo表示特定账户上次与合约交互后的以太token比例。
Please specify source if reproduced在ERC20合约中如何实现分红的自动计算和发放 | Dexnav 区块链导航网