This post aims to explain how Ampleforth’s rebase operation is implemented. While the whitepaper provides an overview of the different components involved, it misses some implementation details.
I hope the post helps community members to gain a better understanding of the underlying details. Please comment if things are unclear, missing, or faulty.
The rebase operation is an atomically executed adjustment to AMPL’s supply and user balances according to a discretional rule set.
To initiate the rebase a user needs to call the
rebase function in the
Orchestrator contract. The intention behind the
Orchestrator is to coordinate AMPL’s supply change with external contracts. This becomes important later in step 7.
It is also worth mentioning that the
rebase function fails if its called by a contract, meaning only EOAs (Externally Owned Contracts) can initiate a rebase. This makes it harder to atomically sandwich-trade AMPL’s supply adjustment.
Ampleforth’s monetary policy is rules-based, i.e. supply adjustments are done algorithmically. These rules are implemented inside the
The first rule enforced is that the
rebase is only allowed to happen once during a reoccurring time window. Currently, the time window is set to 20 minutes and opens every 24 hours.
In order to compute AMPL’s supply delta, the policy needs to know AMPL’s current target and market rate. These values are fetched from two different instances of the same
MedianOracle is a contract in which a set of authorized
providers are allowed to push
reports. Each report becomes only valid after some time passed (the
reportDelaySec) and invalid again after some more time (the
Upon request, the
MedianOracle returns the median of the averages of valid reports for each provider.
Currently, the value pushed to the
MonetaryPolicy queries as the target rate is the 2019 CPI value. For the market rate
MedianOracle, AMPL’s VWAP (Volume Weighted Average Price) of the last 24 hours is pushed by the providers.
For more info about the oracle’s providers, see for example this discussion.
Having AMPL’s target and market rate, the
MonetaryPolicy computes how much AMPL’s supply has to change. This
supplyDelta can be either positive (expansion) or negative (contraction).
For more info about the mathematical details, see the AIP-5 discussion.
rebase function receives the computed
supplyDelta as an argument. The function must be only callable by the
To update all user balances at once, the AMPL token uses an internal, fixed (i.e. non-rebasing) user balance and “simulates” an external user balance (the AMPL balance in your wallet) by multiplying the fixed balance with some number called
Changing AMPL’s supply, that is the sum of all external user balances (isn’t it? ), only involves updating the
rebase scalar with which the external balances are calculated.
This implementation ensures the supply adjustment is always independent of the number of user balances!
Furthermore, it is guaranteed that a supply adjustment is always non-dilutive. (This is an interesting property, meaning even the
MonetaryPolicy can not introduce any Cantillon effect)
Now comes the job of the
Two of the transactions currently included are:
- Adjusting Balancer’s AMPL-USDC smart pool ratio. For more info see this post.
- Call UniswapV2’s
syncfunction for AMPL pools to force the Uniswap pools to update its cached balance of AMPL tokens inside the pool. Without calling this function, Uniswap’s
x * y = kproperty would operate on an outdated AMPL balance.
If any of the function calls fail, the whole rebase operation is reverted. Furthermore, every transaction added to the
Orchestrator increases the gas costs to execute a rebase operation. It should also be noted that the transactions must not depend on each other because the order in which they are executed is random.
One policy of the ForthDAO should therefore be to not blow the
Orchestrator with transactions but rather integrate external contracts by other methods, e.g. wrapped AMPL.