ERC20 Rebase的设计与实现-3 补偿铸造

转载于个人博客 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 变量：

Copy
uint256 public unminted;

unminted 需要在 distribute 时增加，在 exit 时减少：

Copy
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

Copy
// 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;
}
}