ERC20 Rebase的设计与实现-1 基础框架

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

背景

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 的地址

Copy
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 操作，成本过高，系统无法拓展

完整代码

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 {

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);
}
}