Custom aAMPLToken
An implementation of a custom aToken for AMPL. Includes tests, modifications for deployment scripts, and audit report.
Relevant AIP: https://github.com/aave/aip/pull/37
AMPL total supply and balances change once every 24 hours (at 2am UTC) based on market price. The supply change factor is the same for all holders. Because of that, lending and borrowing AMPL behaves differently from fixed supply tokens.
The desired behavior is for the borrow amount (loan) to stay the same after a rebase. The unborrowed tokens deposited in the Aave protocol still get exposed to rebase just like any other contract wallet. The aAMPL token is modified from generic AToken to achieve that behavior.
The easiest way to see the changes in the new tokens is to compare them to the original ones. Diff AAmplToken.sol, AmplStableDebtToken.sol and AmplVariableDebtToken.sol in contracts/protocol/tokenization/ampl with the corresponding generic contracts.
AAmplToken
The AMPL AToken functionally behaves similarly to every other aToken. It always maintains a 1:1 peg with the underlying AMPL. It should guarantee the following to always be true.
Example Scenarios
https://colab.research.google.com/drive/1a4zd7UL-U5Xrme9b0X5B39nJzjrFwfG1?usp=sharing
1) At any time, user can deposit x AMPLs to mint x aAMPLs.
Total aAMPL supply increases by exactly x.
2) At any time, user can burn x aAMPLs for x AMPLs.
Total aAMPL supply decreases by exactly x.
3) At any time, userA can transfer x aAMPLs to userB.
userA's aAMPL balance reduces by X.
userB's aAMPL balance increases by X.
Total aAMPL supply exactly remains same.
4) When AMPL's supply rebases, only the 'unborrowed' aAMPL should rebase.
=> Say there are 1000 aAMPL, and 200 AMPL is lent out. AMPL expands by 10%.
The new aAMPL supply should be 1080 aAMPL.
=> Say there are 1000 aAMPL, and 200 AMPL is lent out. AMPL contracts by 10%.
The new aAMPL supply should be 920 aAMPL.
5) When AMPL's supply rebases, only the part of the balance of a user proportional to the available liquidity ('unborrowed') should rebase.
=> Say a user has deposited 1000 AMPL and receives 1000 aAMPL, and
20% of the total underlying AMPL is lent out. AMPL expands by 10%.
The new aAMPL user balance should be 1080 aAMPL.
=> Say a user has deposited 1000 AMPL and receives 1000 aAMPL, and
20% of the total underlying AMPL is lent out. AMPL contracts by 10%.
The new aAMPL supply should be 920 aAMPL.
Implementation
We implement a custom AToken, StableDebtToken and VariableDebtToken for AMPL. The StableDebtToken and VariableDebtToken implementations make no functional changes to the generic implementation. When the debt tokens are mint and burnt, it performs some additional book-keeping to keep track of the totalScaledAMPL (fixed AMPL denomination, ie Gons) borrowed at any-time. The new getAMPLBorrowData method returns the totalScaledAMPL and totalScaledSupply which are used for AAmplToken math.
ATokenMath
Generic aToken Math
Generic AToken has a private balance and public balance. The public balance returned by balanceOf is multiplied by an interest rate factor.
_balances[u] and _totalSupply hold the balance and total supply in storage.
AToken(u) = ATokenInternal(u) . I
AToken(supply) = ATokenInternal(supply) . I
ATokenInternal(u) = _balances[u]
ATokenInternal(supply) = _totalSupply
For example, a user deposits 100 DAI to get 100 aDAI and accrues 5 DAI interest.
ADaiInternal(user) = 100
ADai(user) = 100 * 1.05 = 105
aAMPL math
The aAMPL works the same way but the AAmplInternal balance is multiplied by another factor, which accounts for the underlying AMPL rebasing and the amount of AMPL unborrowed.
AAmpl(u) = AAmplInternal(u) . I
AAmpl(supply) = AAmplInternal(supply) . I
# AMPL_SCALAR is inferred from the AMPL ERC-20 contract
AAmplInternal(supply) = (totalScaledAMPLDeposited - totalScaledAMPLBorrowed)/AMPL_SCALAR + totalAMPLBorrowed
aAMPL_Scalar() = AAmplInternal(supply) / _totalSupply
AAmplInternal(u) = aAMPL_Scalar() * _balances[u]