banner
zach

zach

github
twitter
medium

Design and Implementation of ERC20 Rebase - Part 1: Basic Framework

Reprinted from personal blog https://www.hackdefi.xyz/posts/erc20-rebase-1/

Background#

The ERC20 Rebase mechanism is derived from the ERC20 protocol and is used to provide incentives and dividends to token holders. This article will explain the design and implementation of the Rebase mechanism based on the ERC20RebaseDistributor contract in the ethereum-credit-guild project.

In the ECG protocol, the ERC20RebaseDistributor contract serves as the underlying creditToken. Similar to other lending protocols, the creditToken is equivalent to cToken in Compound. It is the collateral certificate for lenders and also an interest-bearing asset. Each collateral has a corresponding creditToken. Pledgers can accumulate income by holding creditTokens.

Functional Analysis#

In the ERC20RebaseDistributor contract, not all holders hold interest-bearing assets by default. Instead, the holders participating in rebase and those not participating in rebase are separated. Obviously, dividends are only distributed to the holders participating in rebase. As shown in the figure, ERC20Rebase consists of rebasingSupply and nonRebasingSupply.

image

Here we summarize the following identities:

  • totalSupply() == nonRebasingSupply() + rebasingSupply()
  • sum of balanceOf(x) == totalSupply()

Next, let's analyze the dividend mechanism. In the ECG protocol, there is a contract that aggregates the income of individual creditTokens and converts a portion of the tokens into dividends for participants in rebase. The dividend logic here is also straightforward. Each participant in rebase can share the dividend based on their current balance.

So far, the basic design of our ERC20Rebase has been clarified. We need to provide enter/exit rebase methods and a dividend distribution method. The specific code is as follows (Note: only the key logic is implemented here, there are still omissions):

  • Define rebasingAccounts(array) and rebasingAccount(mapping) to track the addresses participating in rebase.
  • Define rebasingSupply to record the total supply of all participants in rebase.
  • enterRebase function: Mark the address as participating in rebase and accumulate the rebase supply.
  • exitRebase function: Cancel the participation in rebase and deduct the rebase supply.
  • distribute function: Destroy the dividend amount first, and then mint it to all participants in rebase according to the proportion.
    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);
    }

The most basic rebase dividend mechanism has been implemented. Looking back at the above code, there is a very critical issue: if there are many addresses participating in rebase, there will be a large number of mint operations for each dividend distribution, resulting in high costs and the system cannot scale.

Complete Code#

// 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);
    }
}
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.