### AMM Implementation Details This document was created for the purpose of reviewing and standardizing outputs across the implemented AMMs. Factory and AMM definitions are provided, and computations for Fees, Prices, and Reserves are described for further review. ### Curve #### Curve Stable Pools Pricing is calculated when the AMM data is populated. A price is calculated for every pair of tokens within the pool and and pushed onto a price hashmap on the AMM object. Price is calculated by calling get_dy or get_dy_underyling in the case of metapools:Curve StableSwap: Pools — Curve 1.0.0 documentation ``` StableSwap.get_dy(i: int128, j: int128, _dx: uint256)→ uint256: Get the amount of coin j one would receive for swapping _dx of coin i. ``` ``` StableSwap.get_dy_underlying(i: int128, j: int128, dx: uint256)→ uint256: Get the amount received (“dy”) when swapping between two underlying assets within the pool. Index values can be found using get_underlying_coins within the factory contract. i: Index value of the token to send. j: Index value of the token to receive. dx: The amount of i being exchanged. Returns the amount of j received. ``` Once we know how much of the output/base token we receive for a small amount of the input/quote token we then calculate the ratio dy/dx. This represents the value the amount of the base token we receive per 1 unit of the quote token. We then save this value into the hashmap: `amm.prices[quote_token, base_token] = dy/dx` When we call `amm.calculate_price(base_token, quote_token)`, we will return the value at `amm.prices[base_token, quote_token]`, and this will in turn provide us the amount of the quote token required to get one unit of the base_token. #### Curve Crypto Pools The pricing is implemented the same way as the curve stable pools. All of the curve factories on the indexer use the same pricing mechanism. The major difference being the ABIs of the pools used to call the pricing functions ### Kyberswap #### Kyber Elastic Pools Pricing is calculated by calling a price oracle contract. let oracle_contract = IElasticOracle::new(oracle, middleware.clone()); let seconds = 120; let ticks = oracle_contract .observe_from_pool(self.address, [0, seconds].to_vec()) .call() .await?; println!("ticks: {ticks:?}"); let price = f64::powf(1.0001, (ticks[0] - ticks[1]) as f64 / seconds as f64); what gets returned is how much token1 it would cost to get a unit of token0 (before adjusting for decimals) Within the `amm.calculate_price` function, if we call `amm.calculate_price(base_token, quote_token)`: if the `base_token == token0`, we will return the price from the oracle, adjusted by a decimal correction if the `base_token == token1`, we will return the 1/oracle price, adjusted by a decimal correction #### Kyber Classic Pools To calculate prices for Classic Pools, we call the router contract instead let pools_path = vec![self.address]; let token_path = vec![self.token_0, self.token_1]; let _amount_outs = router .get_amounts_out(*amount_in, pools_path, token_path) .call() .await; In this case, we provide the pool address, and say we want to exchange some amount of token0 for token1. Then to calculate the price we perform the ratio: `let price = amount_outs[1].as_u128() as f64 / amount_outs[0].as_u128() as f64` in which case we get the amount of token1 we recieve per unit of token0 We then save the prices into a vector on the amm: `self.prices = [1.0 / price, price]` Now when we call amm.calculate_prices(base_token, quote_token) ``` if base_token == token0: return self.prices[0] => amount of token1 that’s required for 1 unit of token0 if base_token == token1: return self.prices[1] => amount of token1 we get for a unit of token0, or alternatively how much token1 we get for a unit of token0. ``` ### Maverick #### MaverickV1Factory ``` pub struct MaverickV1Factory { pub address: H160, pub info_address: H160, pub pool_created_topic: H256, pub creation_block: u64, pub fee: u32, } ``` - **address:** Maverick pool factory address. - **info_address:** Maverick pool information address. This address is required to compute the asset swap prices. - **pool_created_topic:** The log topic representing when a pool was created. This topic is queried every block to determine if a new pool was created. - **creation_block:** The block number at which the pool was created. - **fee:** Default set to 0. Fee is computed from the associated AMM Pool object. #### MaverickV1Pool ``` pub struct MaverickV1Pool { pub address: H160, pub creation_block: u64, pub info_address: H160, pub token_a: H160, pub token_a_decimals: u8, pub token_b: H160, pub token_b_decimals: u8, pub reserve_a: U256, pub reserve_b: U256, pub fee: U256, #[serde(skip)] pub prices: HashMap<(H160, H160), U256>, pub factory: Option<H160>, } ``` ##### Fee Computation Fee is queried by calling the fee() method from the Maverick pool interface contract which returns a percentage figure of the amount traded. (Denomination TBD). If the fee returned is greater than `u32::MAX`, then the fee is assumed to be that maximum figure. ``` if fee <= U256::from(u32::MAX) { self.fee = fee; } else { self.fee = U256::from(u32::MAX); } ``` ##### Price Computation Price is computed using the `calculate_swap` method from the information contract. To compute price on a per unit basis, the decimals are removed out from the amount in figure. ``` // (base, quote): price (adjusted for decimals) amount_out = calculate_swap(amount_in); amount_in_adj = amount_in / U256::exp10(self.token_in_decimals as usize); price = amount_out / amount_in_adj; ``` ##### Reserve Computation Reserves for `token_a` and `token_b` bins are queried by calling the associated `bin_balance_a()` or `bin_balance_b()` methods from the pool interface contract. The returned values represent trading liquidity for each bin in the pool. #### BancorV3Factory ``` pub struct BancorV3Factory { pub address: H160, pub creation_block: u64, pub fee: u32, } ``` - **address:** Bancor V3 pool factory address. - **creation_block:** The block number at which the pool was created. - **fee:** Default set to 0. Fee is computed from the associated AMM Pool object. #### BancorV3Pool ``` pub struct BancorV3Pool { pub address: H160, pub collection: H160, pub token_a: H160, pub token_b: H160, pub token_a_decimals: u8, pub token_b_decimals: u8, pub liquidity_a: U256, pub liquidity_b: U256, #[serde(skip)] pub prices: HashMap<(H160, H160), U256>, pub fee: U256, } ``` ##### Fee Computation The fee is queried from the collection contract using the `trading_fee_ppm` method. The returned value is a percentage in parts per million (i.e. 2000 represents 0.2%). ##### Price Computation Price is computed using the `trade_output_and_fee_by_source_amount` method from the information contract. To compute price on a per unit basis, the decimals are removed out from the amount in figure. ``` // (base, quote): price (adjusted for decimals) amount_out = trade_output_and_fee_by_source_amount(source_token, target_token, source_amount); amount_in_adj = source_amount / U256::exp10(self.token_in_decimals as usize); price = amount_out / amount_in_adj; ``` ##### Reserve Computation Reserves for `token_a` and `token_b` are queried by calling the associated `pool_liquidity` method from the pool collection contract. The returned value is a tuple with bnt trading liquidity, base token trading liquidity, and staked balance. Token a liquidity is set to equal "base token trading liquidity", and token b is set to equal "bnt trading liquidity". Note that this implies that every pool for Bancor is set as a BNT trading pair. #### SolidlyV2Factory ``` pub struct SolidlyV2Factory { pub address: H160, pub creation_block: u64, pub fee: u32, } ``` - **address:** Solidly V2 pool factory address. - **creation_block:** The block number at which the pool was created. - **fee:** Default set to 0. Fee is computed from the associated AMM Pool object. #### SolidlyV2Pool ``` pub struct SolidlyV2Pool { pub address: H160, pub token_0: H160, pub token_0_decimals: u8, pub token_1: H160, pub token_1_decimals: u8, pub reserve_0: U256, pub reserve_1: U256, pub fee: U256, #[serde(skip)] pub prices: HashMap<(H160, H160), U256>, pub factory: Option<H160>, } ``` ##### Fee Computation The fee is queried from the pool contract using the `fee_ratio` method. The returned value is a percentage in parts per million (i.e. 100 represents 0.01%). ##### Price Computation Price is computed using the `get_amount_out` method from the pool contract. To compute price on a per unit basis, the decimals are removed out from the amount in figure. ``` // (base, quote): price (adjusted for decimals) amount_out = get_amount_out(amount_in); amount_in_adj = amount_in / U256::exp10(self.token_in_decimals as usize); price = amount_out / amount_in_adj; ``` ##### Reserve Computation Reserves for `token_0` and `token_1` are queried by calling the associated `get_reserves` method from the pool contract. The returned value is a tuple with reserve amounts for token_0 and token_b. #### TraderJoeFactory ``` pub struct TraderJoeFactory { pub address: H160, pub creation_block: u64, pub fee: u32, } ``` - **address:** Trader Joe pool LBfactory address. - **creation_block:** The block number at which the pool was created. - **fee:** Default set to 0. Fee is computed from the associated AMM Pool object. #### TraderJoePool ``` pub struct TraderJoePool { pub address: H160, pub token_x: H160, pub token_x_decimals: u8, pub token_y: H160, pub token_y_decimals: u8, pub reserve_x: U256, pub reserve_y: U256, pub fee: U256, #[serde(skip)] pub prices: HashMap<(H160, H160), U256>, pub factory: Option<H160>, } ``` ##### Fee Computation The base fee is computed using the **Base Factor** and **bin step** parameters queried from the pool contract. The base fee is computed as `base_fee` * `bin_step`. Other variable fees that are included in swaps are not computed here as they are more complex, and also because the fee is already taken into account in when computing the swap out amount. ##### Price Computation Price is computed using the `get_swap_out` method from the pool contract. To compute price on a per unit basis, the decimals are removed out from the amount in figure. ``` // (base, quote): price (adjusted for decimals) amount_out = get_amount_out(amount_in); amount_in_adj = amount_in / U256::exp10(self.token_in_decimals as usize); price = amount_out / amount_in_adj; ``` ##### Reserve Computation Reserves for `token_x` and `token_y` are queried by calling the associated `get_reserves` method from the pool contract. The returned value is a tuple with reserve amounts for token_x and token_y.