Yield AMM
Yield AMM (YAMM) pools cater to the unique requirements of principal and yield token trading. Each refractable asset has a corresponding pool, comprising cAsset and active pAsset tokens as liquidity. As time progresses and new maturity levels are introduced (or old ones expire), the amm module automatically updates the related pAssets in the respective pools.
Preliminaries
The refractor exchange rate is the ratio of the total supply of pAsset to the cAsset amount in the Refractor vault.
This rate at time is denoted as . If a user refracts a cAsset amount , they will receive of pAsset and an identical amount of yAsset.
The yield rate to maturity for a specific pAsset can be calculated using the formula
where is the pAsset's market price (in terms of the cAsset) and is the pAsset's time to maturity.
Design
YAMM utilizes Balancer's constant weighted geometric mean formula, expressed as
where, for each , and denote the balance and weight of token in the pool, respectively, and is a real number. Token represents the cAsset while tokens , , , are the pool's pAssets.
In the YAMM model, weights vary over time. We also incorporate virtual balances and leverage parameters to ensure that the balance curves for the cAsset and any pAsset progressively flatten, mirroring the balance curve of the YieldSpace AMM. YAMM trading adheres to standard Balancer formulas.
To account for a token's time to maturity, we introduce the parameter, computed as:
In this formula, signifies each pAsset, and denote the initial and maturity time of the pAsset, respectively, and represents the current time. Note that equals 0 and equals 1. For all , is set to 1. The expression represents the scaled time to maturity of token , which varies from 0 to 1.
Leverage Parameters
The YAMM utilizes leverage parameters for each pAsset in the pool to enhance pricing precision for traders and replicate the YieldSpace curve. Denoted as , these parameters are dynamic and time-dependent. For each , we define the leverage parameter as:
To avoid numerical issues when nears , it's essential to cap the leverage parameter ; we set this limit at . It's worth noting that in practice, we enforce a configurable maximum parameter to limit the alpha parameter, not the fixed value used here.
Lambda Parameter
The lambda parameter is another leverage parameter for the cAsset, denoted by . This parameter is constant for each pool and must meet the condition . A larger results in flatter curves and better trader prices, but less price discovery. We suggest a default , although this can be configured per pool.
Parameter
Parameter gauges the pool's liquidity. At all times, should equal the number of LP tokens in circulation. We also recommend that the initial number of LP tokens assigned to the pool creator—or the initial liquidity provider—should equal the quantity of cAsset deposited when the pool is established. As such, 's initial value is .
Virtual Adjustment Balances
To facilitate smooth transitions when adding or removing pAssets from the pool, we introduce virtual adjustment balances. These are amounts added to the YAMM pool's virtual balances, and are time-dependent functions. Generally zero, these balances deviate from zero when a new token is added to the pool or an existing one is removed. In these cases, the corresponding virtual adjustment balance will incrementally increase or decrease. Each pAsset in the pool has a corresponding virtual adjustment balance, denoted by .
Virtual adjustment balances are defined using parameter as follows. For each we define:
The function is defined as:
-
When pAsset is added to the pool:
Here, is when the new pAsset is added to the pool, is the time during which the virtual adjustment balance gradually decreases to , and . In the implementation, corresponds to the configurable property
introduction_virtual_balance_scaler
. can be any appropriate time duration (e.g., a week, fortnight, month) that allows a gradual decay of the virtual adjustment balance. Note that at , and at , . -
When pAsset is removed from the pool, we denote the time of maturity of the pAsset by and the length of the asset removal period by (for instance, a week). We then define as follows:
Here, and . As mentioned in the introduction, we don't use a constant in the implementation. Instead, this constant can be configured through the
expiration_virtual_balance_scaler
property. It's important to note that can be as large as possible, meaning the virtual balance continues to increase even after the interval has ended. This process continues until the token balance is zero, at which point it is removed from the pool. -
If the above conditions do not apply, then for all .
To manage the virtual adjustment balances, we store an object of the following type for introducing or removing tokens:
message TemporalVirtualBalancePoolToken {
uint64 pool_id = 1;
string denom = 2;
string target_virtual_balance = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
int64 start_unix_millis = 4;
int64 end_unix_millis = 5;
}
The target virtual balance represents the scaler parameter used in calculating . For introducing tokens it corresponds to the parameter, set as introduction_virtual_balance_scaler
, while for expiring tokens it is set as expiration_virtual_balance_scaler
.
Virtual Balances
The pool has virtual balances for trading, defined as follows. Let represent the real balances of all tokens in the pool. The virtual balances are defined by
and
for .
Weights
The weights of different tokens vary over time. The refractor exchange rate at time is denoted by . We define the weights , , as follows:
and
for all . The weights are then scaled proportionally so that .
Trading Fee
The YAMM is designed for trading among yield-bearing tokens, so it's logical that the trading fee isn't a fraction of the token amounts traded, but rather a percentage of the yield rate. The traditional constant fee rate can be problematic in this setting as it can cause a significant difference in the yield rate of the token if the yield is small or if the pAsset is nearing maturity. To define a meaningful trading fee, we'll create a fee that varies with time. Let denote the expected average monthly yield rate of the pAssets. If we have no information about the expected yield rate, we can set to a constant value of 0.01. For each , let denote the period, in months, between the introduction and maturity of pAsset . This means equals measured in months, where and are the maturity's expiration and introduction times, respectively.
We'll also establish a parameter , initially set to 1. Governance can later adjust this to raise or lower pool fees.
With these parameters in place, we define the fee for each pAsset at time as:
The factor is the yield rate of from introduction to maturity. We can round the parameter to the nearest integer to reduce computation.
Importantly, doesn't depend on the pAsset's price—an intentional design choice to avoid manipulation.
For the cASSET, we define the fee as for all . Now, for , the trading fee for trading assets and at time is:
Pool Interactions
Since yamm pools are based on weighted pool designs, standard operations like swapping, depositing liquidity, and withdrawing liquidity all function as outlined in the weighted pools page.
Zero Impact Join
If a user wants to join a pool using only cASSETs, we need to use non-proportional join methods, which involve underlying swaps. But swaps during joining can cause high price impact. To solve this, we've developed a zero-impact join feature that lets users join a YAMM pool with just cASSETs, minimizing price impact.
The zero-impact join includes two steps:
- Refract a portion of the cASSET into pool maturities.
- Use the remaining cASSET and pASSETs to join the pool.
The user receives LP tokens and yASSETs from the initial refraction.
To refract the correct amount of cASSET for each maturity, we need to get the right proportion of each token to join the pool. If the pool contains tokens with balances , where is the cASSET balance and the others are pASSETs, we calculate the required cASSET amount to refract for each token to match the pASSET balance in the pool:
Here, is the refractor's effective exchange rate, accounting for the protocol fee. The effective exchange rate can be calculated as . After determining the amount of cASSET necessary to match the actual balances in the pool, we can calculate the total cASSET required using the following formula:
This allows us to establish the ratio associated with each pASSET:
In these equations, we assume to iterate through the pool's tokens.
Using these ratios, we can calculate the amount of cASSET to refract for each maturity, represented as , where is the user-supplied input cASSET amount.
YAsset Trading
YAMM pools are designed to facilitate trading between cASSET, pASSET, and yASSETs. However, the pool structure includes just cASSET and pASSET, which makes direct yASSET trades impossible via the weighted pool equations. To allow such trades, we employ flash loans and interact with the refractor module. Note that YAMM pools only support trading between yASSET and cASSET (or vice versa), while trades involving yASSET and pASSETs necessitate two-step batch swaps.
Buying yASSETs: The buying process for yASSETs follows these steps:
- Borrow a cASSET amount, , from ammVault.
- Refract the amount.
- Trade all received pAssets for cAssets to repay the loan, and provide the yAsset to the user.
The loan amount needs precise computation to carry out these steps. Assuming that the Refractor module has a fee ratio of and an exchange rate of , let denote the effective exchange rate of the refractor. Thus, the refracted pASSET would be . As outlined in the steps, we must sell this pAsset for cAsset to recover the loan amount.
We now define a few parameters and functions to simplify our calculations.
In the formula above, stand for the pAsset and cAsset indices, respectively. We also define the function as below for added clarity.
We further define the function as:
We now can apply newton method steps as:
This Newton method applies for a maximum of 15 steps or stops early if the following condition is met:
Recall that this method approximates the loan amount. However, for practical purposes, we need to ensure that we can repay the loan. To make allowances for any estimation errors, we propose a loan fee, which is adjusted by the BuyYGivenInLoanFeeRatio
parameter. This fee is deducted from the before applying the Newton method, and later added back to the amount being refracted. Consequently, we only need to replace in the Newton method with . Nevertheless, when we want to perform the refraction, we use the actual .
Buying yASSET given out: This scenario indicates that we know the yASSET amount and we must calculate the required cASSET. Let's assume that the refractor converts an amount of cASSET into another amount using a function . Given that the output amount is and