xChar
·5 months ago

转载于个人博客 https://www.hackdefi.xyz/posts/erc20-rebase-3/

实际例子

我们以实际例子展开,假设A、B均开启rebase,且初始balance和share均为100,C未开启rebase,balance为200

此时的状态如下,可见是满足等式条件的:

  • totalSupply = 100 + 100 + 200 = 400
  • sharePrice = 1
  • rebasingSupply = (100 + 100) * 1 = 200
  • nonRebasingSupply = 200

那么C调用distribute来贡献自己的200 token,状态变化为:

  • totalSupply = 100 + 100 - 200 = 0
  • sharePrice = 1 + (200/200) = 2
  • rebasingSupply = (100 + 100) * 2 = 400
  • nonRebasingSupply = 0

此时显然等式不再成立,为了保证等式成立,totalSupply应该是400而不是0,回到第一个版本,我们每次调用distribute时都会通过_mint来修改每个参与rebase用户的balance,实际上系统是增发了,因为前面进行了销毁操作,因此保持了平衡

那么在第二版中,前面也进行了销毁,但是却没有增发,只是更新sharePrice,那么就算用户账面上的balance增加了,但是实际来领取时,系统总量是不够的。因此,这里差的400就是需要增发的量,同理我们不需要按比例增发给每个用户,而是记录在一个全局变量中,当用户退出rebase时再mint出相应的数额

补偿铸造

首先定义全局的unminted变量:

uint256 public unminted;

unminted需要在distribute时增加,在exit时减少:

    function _exitRebase(address user)  internal {
        uint256 shares = rebasingAccount[user].nShares;
        rebasingAccount[user].isRebasing = false;
        rebasingAccount[user].nShares = 0;
        totalShares -= shares;
        uint256 balance = share2Balance(shares);
        uint256 rawBalance = ERC20.balanceOf(user);
        if (balance > rawBalance) {
            uint256 delta = balance - rawBalance;
            ERC20._mint(user, delta);
            unminted -= delta;
        }
        emit RebaseExit(user, shares, block.timestamp);
    }

    function distribute(uint256 amount) external {
        require(balanceOf(msg.sender)>=amount, "SimpleERC20Rebase: not enough");
        _burn(msg.sender, amount);
        sharePrice += amount*1e30 / totalShares;
        unminted += amount;
    }

修改后的代码如下,我们新增了unminted变量,并在distribute时累加,在exit时增发相应数目token给用户

至此,ERC20Rebase的核心框架已经实现了,不过代码仅供参考,只关注了核心逻辑的实现,对比ecg中的ERC20RebaseDistributor合约,我们仍欠缺很关键的部分:线性释放,分红的token不是一次性反馈给holder,而是在一定的周期内线性增加。

由于添加了时间的维度,相应的也会衍生出许多问题:如果分红周期内share总量发生变化,如何保证公平分发?

Code

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.13;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract SimpleERC20Rebase is ERC20 {
    event RebaseEnter(address indexed account, uint256 indexed shares, uint256 indexed timestamp);
    event RebaseExit(address indexed account, uint256 indexed shares, uint256 indexed timestamp);
    
    struct RebasingState {
        bool isRebasing;
        uint256 nShares;
    }
    mapping(address => RebasingState) internal rebasingAccount;
    uint256 public totalShares;
    uint256 public unminted;
    uint256 public sharePrice = 1e30;

    constructor(
        string memory _name,
        string memory _symbol
    ) ERC20(_name, _symbol) {}

    function rebasingSupply() public view returns (uint256) {
        return share2Balance(totalShares);
    }

    function nonRebasingSupply() public view returns (uint256) {
        return totalSupply() - rebasingSupply();
    }

    function share2Balance(uint256 shares) view public returns (uint256) {
        return shares * sharePrice / 1e30;
    }

    function balance2Share(uint256 balance) view public returns (uint256) {
        return balance * 1e30 / sharePrice ;
    }

    function enterRebase() external {
        require(!rebasingAccount[msg.sender].isRebasing, "SimpleERC20Rebase: already rebasing");
        _enterRebase(msg.sender);
    }

    function _enterRebase(address user) internal {
        uint256 balance = balanceOf(user);
        uint256 shares = balance2Share(balance);
        rebasingAccount[user].isRebasing = true;
        rebasingAccount[user].nShares = shares;
        totalShares += shares;
        emit RebaseEnter(user, shares, block.timestamp);
    }

    function exitRebase()  external {
        require(rebasingAccount[msg.sender].isRebasing, "SimpleERC20Rebase: not rebasing");
        _exitRebase(msg.sender);
    }

    function _exitRebase(address user)  internal {
        uint256 shares = rebasingAccount[user].nShares;
        rebasingAccount[user].isRebasing = false;
        rebasingAccount[user].nShares = 0;
        totalShares -= shares;
        uint256 balance = share2Balance(shares);
        uint256 rawBalance = ERC20.balanceOf(user);
        if (balance > rawBalance) {
            uint256 delta = balance - rawBalance;
            ERC20._mint(user, delta);
            unminted -= delta;
        }
        emit RebaseExit(user, shares, block.timestamp);
    }

    function distribute(uint256 amount) external {
        require(balanceOf(msg.sender)>=amount, "SimpleERC20Rebase: not enough");
        _burn(msg.sender, amount);
        sharePrice += amount*1e30 / totalShares;
        unminted += amount;
    }

    function totalSupply() public view override returns (uint256) {
        return super.totalSupply() + unminted;
    }

    function balanceOf(address account) public view override returns (uint256) {
        uint256 rawBalance = ERC20.balanceOf(account);
        if (rebasingAccount[account].isRebasing) {
            return share2Balance(rebasingAccount[account].nShares);
        } else {
            return rawBalance;
        }
    }

    function mint(address user, uint256 amount) external {
        bool isRebasing = rebasingAccount[user].isRebasing;
        if (isRebasing) {
            _exitRebase(user);
        }
        ERC20._mint(user, amount);
        if (isRebasing) {
            _enterRebase(user);
        }
    }

    function transfer(address to, uint256 amount)  public virtual override returns (bool) {
        bool isFromRebasing = rebasingAccount[msg.sender].isRebasing;
        bool isToRebasing = rebasingAccount[to].isRebasing;
        if (isFromRebasing) {
            _exitRebase(msg.sender);
        }
        if (isToRebasing && to != msg.sender) {
            _exitRebase(to);
        }
        bool result = ERC20.transfer(to, amount);
        if (isFromRebasing) {
            _enterRebase(msg.sender);
        }
        if (isToRebasing && to != msg.sender) {
            _enterRebase(to);
        }
        return result;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        bool isFromRebasing = rebasingAccount[from].isRebasing;
        bool isToRebasing = rebasingAccount[to].isRebasing;
        if (isFromRebasing) {
            _exitRebase(from);
        }
        if (isToRebasing && to != from) {
            _exitRebase(to);
        }
        bool result = ERC20.transfer(to, amount);
        if (isFromRebasing) {
            _enterRebase(from);
        }
        if (isToRebasing && to != from) {
            _enterRebase(to);
        }
        return result;
    }
}
Loading comments...