Guides

Understanding Risk of Rebase Tokens Through Smart Contract Analysis

In this article, I will dig into the smart contracts of a few projects with such functionalities in order to understand how the rebase function in Solidity smart contract works, as well…

Understanding Risk of Rebase Tokens Through Smart Contract Analysis Hero Image

In this article, I will dig into the smart contracts of a few projects with such functionalities in order to understand how the rebase function in Solidity smart contract works, as well as some of the potential risks of these projects. 4 tokens – Ampleforth (AMPL), Antiample (XAMP), Tokens of Babel (TOB) and RMPL (RMPL) will be the subject of interest for this research.

Ampleforth (AMPL)

Ampleforth is first token that started it all and made rebase popular enough that new tokens are now copying and innovating on top of it.

Let’s take a look in the Smart Contract of Ampleforth.

AMPL Contract: https://etherscan.io/address/0xd46ba6d942050d489dbd938a2c909a5d5039a161#code

Since the contract is a proxy, the real contract is here: https://etherscan.io/address/0xf9e6a96229140195777a1c3ab47c9bf16f055406#code

/**
 * @dev Notifies Fragments contract about a new rebase cycle.
 * @param supplyDelta The number of new fragment tokens to add into circulation via expansion.
 * @return The total number of fragments after the supply adjustment.
 */
function rebase(uint256 epoch, int256 supplyDelta)
    external
    onlyMonetaryPolicy
    returns (uint256)
{
    if (supplyDelta == 0) {
        emit LogRebase(epoch, _totalSupply);
        return _totalSupply;
    }

    if (supplyDelta < 0) {
        _totalSupply = _totalSupply.sub(uint256(supplyDelta.abs()));
    } else {
        _totalSupply = _totalSupply.add(uint256(supplyDelta));
    }

    if (_totalSupply > MAX_SUPPLY) {
        _totalSupply = MAX_SUPPLY;
    }

    _gonsPerFragment = TOTAL_GONS.div(_totalSupply);

    // From this point forward, _gonsPerFragment is taken as the source of truth.
    // We recalculate a new _totalSupply to be in agreement with the _gonsPerFragment
    // conversion rate.
    // This means our applied supplyDelta can deviate from the requested supplyDelta,
    // but this deviation is guaranteed to be < (_totalSupply^2)/(TOTAL_GONS - _totalSupply).
    //
    // In the case of _totalSupply <= MAX_UINT128 (our current supply cap), this
    // deviation is guaranteed to be < 1, so we can omit this step. If the supply cap is
    // ever increased, it must be re-included.
    // _totalSupply = TOTAL_GONS.div(_gonsPerFragment)

    emit LogRebase(epoch, _totalSupply);
    return _totalSupply;
}

How rebase works?

Basically, Ampleforth’s smart contract changes the code of balanceOf method. They just need to make a contract call to rebase in order to change the total ownership that you have in the token. In this case, to call the rebase method, all you need is just epoch which is date time for logging and supplyDelta which is to add or remove supply of the token.

As you can see onlyMonetaryPolicy role can call this rebase function.

// Used for authentication
address public monetaryPolicy;
/**
 * @param monetaryPolicy_ The address of the monetary policy contract to use for authentication.
 */
modifier onlyMonetaryPolicy() {
    require(msg.sender == monetaryPolicy);
    _;
}

In the contract data, it seems like the address is set to 0x0 which is not the case when you use readAsProxy feature on Etherscan on the main proxy contract.

https://etherscan.io/address/0xd46ba6d942050d489dbd938a2c909a5d5039a161#readProxyContract

So the monetoryPolicy address is set to where in this case, it is an oracle smart contract called Rebaser.

By using an oracle like Chainlink, Ampleforth can create a trustless manner of rebasing. This article does not cover the part of oracle code.

AntiAmple (XAMP)

The AntiAmple token also rebases, but it only allow burning of the supply.

XAMP contract: https://etherscan.io/address/0xf911a7ec46a2c6fa49193212fe4a2a9b95851c27#code

function rebase(uint256 epoch, uint256 supplyDelta)
    external
    onlyOwner
    returns (uint256)
{
    if (supplyDelta == 0) {
        emit LogRebase(epoch, _totalSupply);
        return _totalSupply;
    }

     _totalSupply = _totalSupply.sub(supplyDelta);

    if (_totalSupply > MAX_SUPPLY) {
        _totalSupply = MAX_SUPPLY;
    }

    _gonsPerFragment = TOTAL_GONS.div(_totalSupply);

    emit LogRebase(epoch, _totalSupply);
    return _totalSupply;
}

For AntiAmple, the rebase part of the code is almost similar as Ampleforth’s rebase.

How it works is that, it only has negative rebase. The line _totalSupply = _totalSupply.sub(supplyDelta); shows that the supplyDelta is an unsigned integer, the total supply can only be reduce, never increase.

This rebase method is accessible via a modifier called onlyOwner.

address public _owner;

modifier onlyOwner() {
    require(_owner == msg.sender, "Ownable: caller is not the owner");
    _;
}

If you read the contract https://etherscan.io/address/0xf911a7ec46a2c6fa49193212fe4a2a9b95851c27#readContract

The contract owner is actually a Rebaser Oracle also

Tokens of Babel (TOB)

Tokens of Babel was also created by the Antiample team.

Contract Code: https://etherscan.io/address/0x7777770f8a6632ff043c8833310e245eba9209e6#code

function rebase(uint256 epoch, uint256 supplyDelta)
    external
    onlyOwner
    returns (uint256)
{
    if (supplyDelta == 0) {
        emit LogRebase(epoch, _totalSupply);
        return _totalSupply;
    }

     _totalSupply = _totalSupply.sub(supplyDelta);

    if (_totalSupply > MAX_SUPPLY) {
        _totalSupply = MAX_SUPPLY;
    }

    _gonsPerFragment = TOTAL_GONS.div(_totalSupply);

    emit LogRebase(epoch, _totalSupply);
    return _totalSupply;
}

The code is similar to AntiAmple, similar onlyOwner modifier, but here’s a small difference.

At the point of writing, when you check the owner of the contract, it is actually still owned by the owner.

I asked in TOB telegram group about this issue.

Well, it make sense that building the oracle with logic require a lot of testing and development time, so in order to ship it first, it used an offchain mechanism (maybe a script) to do a rebase.

Refer to end of the article, where I share the possible vulnerability of off-chain Owner key.

RMPL

RMPL is a random rebase version of Ampleforth.

Contract address: https://etherscan.io/address/0xe17f017475a709de58e976081eb916081ff4c9d5#code

struct Transaction {
        bool enabled;
        address destination;
        bytes data;
    }

/**
 * @dev Notifies Fragments contract about a new rebase cycle.
 * @param supplyDelta The number of new fragment tokens to add into circulation via expansion.
 * @return The total number of fragments after the supply adjustment.
 */
function rebase(int256 supplyDelta)
    external
    onlyOwner
    returns (uint256)
{

	_epoch = _epoch.add(1);

  if (supplyDelta == 0) {
      emit LogRebase(_epoch, _totalSupply);
      return _totalSupply;
  }

  if (supplyDelta < 0) {
      _totalSupply = _totalSupply.sub(uint256(supplyDelta.abs()));
  } else {
      _totalSupply = _totalSupply.add(uint256(supplyDelta));
  }

  if (_totalSupply > MAX_SUPPLY) {
      _totalSupply = MAX_SUPPLY;
  }

  _gonsPerFragment = TOTAL_GONS.div(_totalSupply);

	// From this point forward, _gonsPerFragment is taken as the source of truth.
  // We recalculate a new _totalSupply to be in agreement with the _gonsPerFragment
  // conversion rate.
  // This means our applied supplyDelta can deviate from the requested supplyDelta,
  // but this deviation is guaranteed to be < (_totalSupply^2)/(TOTAL_GONS - _totalSupply).
  //
  // In the case of _totalSupply <= MAX_UINT128 (our current supply cap), this
  // deviation is guaranteed to be < 1, so we can omit this step. If the supply cap is
  // ever increased, it must be re-included.
  // _totalSupply = TOTAL_GONS.div(_gonsPerFragment)

	emit LogRebase(_epoch, _totalSupply);

	for (uint i = 0; i  Once onchain random rebasing solution has been finalized, contract owner will be locked to ensure no party has control and the implementation is completely self governed.

Similar to TOB, Currently the `rebase` function are still being triggered off chain.

Owner key address:

## Final Thoughts

AntiAmple and Ampleforth had a rebase mechanism using an Oracle. Since the analysis of Oracle is out of scope of today's article, we will be focusing on the `rebase` function

At the moment of writing, Holding TOB and RMPL will exposed risk to the Owner key, where the owner key can call `rebase` with any amount of `supplyDelta`. So instead of using an on-chain oracle to get the price, they use offchain method and update the contract via owner key.

**Why is this important?**

If this part of the owner address is not executed by a smart contract oracle (which can be verified), the owner address have the power to provide `supplyDelta` value in whatever parameters that owner wants.

**What would happen?**

The owner can hold a certain amount of TOB, and then provide an arbitrary high value of `supplyDelta` to the rebase function. And then, with that the owner can dump the token in the next block.

So until the Owner key is changed to Oracle based, this `rebase` method are exposed to the risk.

At CoinGecko, we wanted inform all our users regarding on this kind of risk when investing to the coin. That is why we added a notice for these coin that are exposed to the risk of offchain rebase.