banner
zach

zach

github
twitter
medium

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 兩部分構成

image

這裡我們總結出下面的恆等式:

  • 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);
    }
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。