ERC20 Rebase机制是在ERC20协议基础之上衍生的,用来对代币持有者做激励分红,这里将以ethereum-credit-guild项目中设计的ERC20RebaseDistributor
合约为基础,讲解Rebase机制的设计与实现思路。
在ecg中,ERC20RebaseDistributor
合约是作为底层的creditToken,类比到其他借贷协议,creditToken等同于compound中的cToken,是lender的质押凭证,也是生息资产,针对不同的抵押物都有一个绑定的creditToken,质押者可以通过持有creditToken来累积收益。
在ERC20RebaseDistributor
合约中并不是默认所有holder都持有生息资产,而是将参与rebase和非rebase的分开,显然分红只针对参与rebase的holder展开,如图所示,ERC20Rebase由rebasingSupply和nonRebasingSupply两部分构成
这里我们总结出下面的恒等式:
totalSupply() == nonRebasingSupply() + rebasingSupply()
sum of balanceOf(x) == totalSupply()
接下来,再分析分红机制,在ecg协议中,存在一个合约来汇总单个creditToken的收益,并按照比例把一部分token转为对参与rebase的holder分红,这里的分红逻辑也很直接,每个参与rebase的用户根据当前余额来瓜分分红额度即可。
至此,我们的ERC20Rebase基础设计已经明确,需要提供enter/exit rebase方法和分红distribute方法,具体的代码如下所示(注:这里只实现关键逻辑,仍有缺漏):
rebasingAccounts(array)
rebasingAccount(mapping)
来跟踪参与rebase的地址rebasingSupply
来记录所有参与rebase的供应量enterRebase
函数:将该地址标记为参与rebase,累加rebase供应量exitRebase
函数:取消参与rebase的标记,扣减rebase供应量distribute
函数:先将分红数额销毁,再按照比例mint给所有参与rebase的地址 function enterRebase() external {
require(!rebasingAccount[msg.sender], "SimpleERC20Rebase: already rebasing");
uint256 balance = balanceOf(msg.sender);
rebasingAccount[msg.sender] = true;
rebasingSupply += balance;
rebasingAccounts.push(msg.sender);
}
function exitRebase() external {
require(rebasingAccount[msg.sender], "SimpleERC20Rebase: not rebasing");
uint256 balance = balanceOf(msg.sender);
rebasingAccount[msg.sender] = false;
rebasingSupply -= balance;
for (uint256 i = 0; i < rebasingAccounts.length; i++) {
if (rebasingAccounts[i] == msg.sender) {
rebasingAccounts[i] = rebasingAccounts[rebasingAccounts.length - 1];
rebasingAccounts.pop();
break;
}
}
}
function distribute(uint256 amount) external {
require(balanceOf(msg.sender)>=amount, "SimpleERC20Rebase: not enough");
_burn(msg.sender, amount);
for (uint256 i = 0; i < rebasingAccounts.length; i++) {
uint256 delta = amount * balanceOf(rebasingAccounts[i]) / rebasingSupply;
_mint(rebasingAccounts[i], delta);
}
rebasingSupply += amount;
}
function mint(address user, uint256 amount) external {
return _mint(user, amount);
}
最基础的rebase分红机制已经实现了,回顾上面的代码,存在非常关键的问题:如果参与rebase的地址很多,每次分红都会有大量的mint操作,成本过高,系统无法拓展
// 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 {
mapping(address => bool) internal rebasingAccount;
address[] internal rebasingAccounts;
uint256 public rebasingSupply;
constructor(
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) {}
function nonRebasingSupply() public view returns (uint256) {
return totalSupply() - rebasingSupply;
}
function enterRebase() external {
require(!rebasingAccount[msg.sender], "SimpleERC20Rebase: already rebasing");
uint256 balance = balanceOf(msg.sender);
rebasingAccount[msg.sender] = true;
rebasingSupply += balance;
rebasingAccounts.push(msg.sender);
}
function exitRebase() external {
require(rebasingAccount[msg.sender], "SimpleERC20Rebase: not rebasing");
uint256 balance = balanceOf(msg.sender);
rebasingAccount[msg.sender] = false;
rebasingSupply -= balance;
for (uint256 i = 0; i < rebasingAccounts.length; i++) {
if (rebasingAccounts[i] == msg.sender) {
rebasingAccounts[i] = rebasingAccounts[rebasingAccounts.length - 1];
rebasingAccounts.pop();
break;
}
}
}
function distribute(uint256 amount) external {
require(balanceOf(msg.sender)>=amount, "SimpleERC20Rebase: not enough");
_burn(msg.sender, amount);
for (uint256 i = 0; i < rebasingAccounts.length; i++) {
uint256 delta = amount * balanceOf(rebasingAccounts[i]) / rebasingSupply;
_mint(rebasingAccounts[i], delta);
}
rebasingSupply += amount;
}
function mint(address user, uint256 amount) external {
return _mint(user, amount);
}
}