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 に相当し、貸し手の担保証明書であり、利息を生む資産です。異なる担保物にはそれぞれバインドされた creditToken があり、担保者は creditToken を保有することで利益を蓄積することができます。

機能分析#

ERC20RebaseDistributorコントラクトでは、すべてのホルダーが自動的に利息を保有しているわけではありません。代わりに、rebase に参加するホルダーと非 rebase に参加するホルダーを分けています。明らかに、配当は rebase に参加するホルダーにのみ適用されます。以下の図に示すように、ERC20Rebase は rebasingSupply と nonRebasingSupply の 2 つの部分から構成されています。

image

ここで、次の恒等式をまとめます:

  • totalSupply() == nonRebasingSupply() + rebasingSupply()
  • balanceOf(x)の合計 == totalSupply()

次に、配当メカニズムを分析します。ecg プロトコルでは、個々の creditToken の利益を集計し、一部のトークンを rebase に参加するホルダーに配当として転送します。配当のロジックも非常に直接的であり、各 rebase 参加ユーザーは現在の残高に基づいて配当の割り当てを受けることができます。

これで、ERC20Rebase の基本的な設計が明確になりました。enter/exit rebase メソッドと配当の distribute メソッドを提供する必要があります。具体的なコードは以下の通りです(注:ここでは主要なロジックのみを実装しており、抜け漏れがあります):

  • rebasingAccounts(array)rebasingAccount(mapping)を定義して、rebase に参加するアドレスを追跡します。
  • rebasingSupplyを定義して、すべての rebase 参加者の供給量を記録します。
  • enterRebase関数:このアドレスを rebase に参加したとマークし、rebase 供給量を累積します。
  • exitRebase関数:rebase からの参加をキャンセルし、rebase 供給量を減らします。
  • distribute関数:配当額をまず破棄し、その後、比率に従ってすべての rebase 参加アドレスに mint します。
    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);
    }
}
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。