在ERC20合约中如何实现分红的自动计算和发放

技术博客1年前 (2023)更新 Dexnav
0

ERC20合约中如何实现分红的自动计算和发放?

本文将介绍如何编写一个基于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;
// 用于记录所有账户的分红比例,是一个对所有账户都适用的变量
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;
}

智能合约在处理分红时面临着一些限制,其中最显著的是gas成本的高昂。当持有token的人数超过百万级别时,循环分发分红所需的gas量将会非常大。

每个账户的分红状态可以分为以下三种:

  1. 已发放:分红已经发放到该账户。
  2. 已认定但未发放:分红被认定属于该账户,但尚未发放。
  3. 未认定:分红尚未被认定,但有足够的信息可用于认定。

所有账户的新分红都从“未认定”状态开始。一旦账户参与转账或提款,合约会将该账户应有的分红归集到已认定但未发放状态。提款时,状态将变更为“已发放”。

合约使用以下变量来记录每个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比例。

 

 

© 版权声明

相关文章

暂无评论

暂无评论...