The input values of _name or _symbol are obviously non-trivial for the performance of the pool. These parameters should visualize, what kind of tokens are included in the pool.
_A represents the amplification coefficient of the pool, signifying its density.
_fee is referred to as the "base fee."
The offpeg_fee_multiplier parameter enables the system to dynamically adjust fees according to the pool's state.
ma_exp_time denotes the time window for the moving average oracle.
Recommended Parameters:
Parameter
Fiat-Redeemable Stablecoin
Crypto-Collateralized Stablecoin
A
200
100
fee
0.04%
0.04%
offpeg_fee_multiplier
2
2
ma_exp_time
866
866
_A=200_fee=4000000# 0.0001 or 0.01%_offpeg_fee_multiplier=20000000000# 5 or 500%_ma_exp_time=866# ~600 seconds
Parameter Precision
The precision of _fee and _offpeg_fee_multiplier is 1e10. The time window of the moving average exponential oracle is calculated using time_in_seconds / ln(2).
Pools are created from implementation contracts (blueprints). These contracts are added to the Factory and must be choosen when deploying a pool.
Warning
The Factory can have multiple plain- and meta-pool implementations. If there are multiple implementations for a plain or meta-pool, it's important to understand the differences and determine which one is suitable. Additionally, implementation contracts are upgradable. They can either be replaced or have additional implementation contracts set. Please always make sure to check the most recent ones.
Stableswap-NG infrastructure supports pools with the following asset types:
Asset Type
Description
0
Standard ERC-20 token with no additional features
1
Oracle - token with rate oracle (e.g. wstETH)
2
Rebasing - token with rebase (e.g. stETH)
3
ERC4626 - token with convertToAssets method (e.g. sDAI)
Consequently, supported tokens include:
ERC-20 support for return True/revert, True/False or None
ERC-20 tokens can have arbitrary decimals (≤18)
ERC-20 tokens that rebase (either positive or fee on transfer)
ERC-20 tokens that have a rate oracle (e.g. wstETH, cbETH) Oracle precision must be
ERC-4626 tokens with arbitrary percision (≤18) of Vault token and underlying asset
Warning
ERC20: Users are advised to do careful due-diligence on ERC20 tokens that they interact with, as this contract cannot differentiate between harmless and malicious ERC20 tokens.
Oracle: When using tokens with oracles, its important to know that they may be controlled externally by an EOA.
Rebasing: Users and Integrators are advised to understand how the AMM contract works with rebasing balances.
ERC4626: Some ERC4626 implementations may be susceptible to Donation/Inflation attacks. Users are advised to proceed with caution.
Choosing asset types can sometimes be quite tricky. Asset types should be seen more as information for the AMM on how to treat the assets under the hood.
Example
Let's consider the example of rmETH/mETH. - mETH is a token with a rate oracle (the underlying asset is ETH). The rate can be fetched by reading the mETHToETH method within the staking contract. - rmETH is a rebasing token.
Because the deployer wants rmETH and mETH to trade as close to 1:1 as possible, they need to treat mETH like a regular ERC-20 token (asset type 0), instead of a rate oraclized token (asset type 1).
_asset_types=[0,2]# coin(0) = asset type 0; coin(1) = asset type 2
method_ids and _oracles are required for rate oracles to function. ERC-4626 does not need either of these. The sole requirement for those is to have a convertToAssets method.
Info
When deploying pools that include coins not requiring a rate oracle, b"" or 0x00000000 should be included in the _methods_id array and the ZERO_ADDRESS should be used in the _oracles array as placeholders for each coin.
_method_ids is the first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that give rate oracles.
As an example, lets look at the rETH token. The relevant function which returns the rate is getExchangeRate, the according first four bytes of the Keccak-256 hash of the functions signature is therefore 0xe6aa216c. When calculating, its always important to include "()", as they will change the bytes.
Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that give rate oracles
_oracles
DynArray[address, MAX_COINS]
Array of rate oracle addresses
Implementation ID
There might be multiple pool implementations. To query all available ones, see here. As of the current date (31.10.2023), there is only one pool implementation available. Since the _implementation_idx starts at 0, users need to input "0" when deploying a pool.
Source code
eventPlainPoolDeployed:coins:DynArray[address,MAX_COINS]A:uint256fee:uint256deployer:addressMAX_COINS:constant(uint256)=8MAX_FEE:constant(uint256)=5*10**9FEE_DENOMINATOR:constant(uint256)=10**10@externaldefdeploy_plain_pool(_name:String[32],_symbol:String[10],_coins:DynArray[address,MAX_COINS],_A:uint256,_fee:uint256,_offpeg_fee_multiplier:uint256,_ma_exp_time:uint256,_implementation_idx:uint256,_asset_types:DynArray[uint8,MAX_COINS],_method_ids:DynArray[bytes4,MAX_COINS],_oracles:DynArray[address,MAX_COINS],)->address:""" @notice Deploy a new plain pool @param _name Name of the new plain pool @param _symbol Symbol for the new plain pool - will be concatenated with factory symbol @param _coins List of addresses of the coins being used in the pool. @param _A Amplification co-efficient - a lower value here means less tolerance for imbalance within the pool's assets. Suggested values include: * Uncollateralized algorithmic stablecoins: 5-10 * Non-redeemable, collateralized assets: 100 * Redeemable assets: 200-400 @param _fee Trade fee, given as an integer with 1e10 precision. The maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @param _asset_types Asset types for pool, as an integer @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. @return Address of the deployed pool """assertlen(_coins)>=2# dev: pool needs to have at least two coins!assertlen(_coins)==len(_method_ids)# dev: All coin arrays should be same lengthassertlen(_coins)==len(_oracles)# dev: All coin arrays should be same lengthassertlen(_coins)==len(_asset_types)# dev: All coin arrays should be same lengthassert_fee<=100000000,"Invalid fee"assert_offpeg_fee_multiplier*_fee<=MAX_FEE*FEE_DENOMINATORn_coins:uint256=len(_coins)_rate_multipliers:DynArray[uint256,MAX_COINS]=empty(DynArray[uint256,MAX_COINS])decimals:DynArray[uint256,MAX_COINS]=empty(DynArray[uint256,MAX_COINS])foriinrange(MAX_COINS):ifi==n_coins:breakcoin:address=_coins[i]decimals.append(ERC20(coin).decimals())assertdecimals[i]<19,"Max 18 decimals for coins"_rate_multipliers.append(10**(36-decimals[i]))forjinrange(i,i+MAX_COINS):if(j+1)==n_coins:breakassertcoin!=_coins[j+1],"Duplicate coins"implementation:address=self.pool_implementations[_implementation_idx]assertimplementation!=empty(address),"Invalid implementation index"pool:address=create_from_blueprint(implementation,_name,# _name: String[32]_symbol,# _symbol: String[10]_A,# _A: uint256_fee,# _fee: uint256_offpeg_fee_multiplier,# _offpeg_fee_multiplier: uint256_ma_exp_time,# _ma_exp_time: uint256_coins,# _coins: DynArray[address, MAX_COINS]_rate_multipliers,# _rate_multipliers: DynArray[uint256, MAX_COINS]_asset_types,# _asset_types: DynArray[uint8, MAX_COINS]_method_ids,# _method_ids: DynArray[bytes4, MAX_COINS]_oracles,# _oracles: DynArray[address, MAX_COINS]code_offset=3)length:uint256=self.pool_countself.pool_list[length]=poolself.pool_count=length+1self.pool_data[pool].decimals=decimalsself.pool_data[pool].n_coins=n_coinsself.pool_data[pool].base_pool=empty(address)self.pool_data[pool].implementation=implementationself.pool_data[pool].asset_types=_asset_typesforiinrange(MAX_COINS):ifi==n_coins:breakcoin:address=_coins[i]self.pool_data[pool].coins.append(coin)forjinrange(i,i+MAX_COINS):if(j+1)==n_coins:breakswappable_coin:address=_coins[j+1]key:uint256=(convert(coin,uint256)^convert(swappable_coin,uint256))length=self.market_counts[key]self.markets[key][length]=poolself.market_counts[key]=length+1logPlainPoolDeployed(_coins,_A,_fee,msg.sender)returnpool
First four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that give rate oracles
_oracle
address
Rate oracle address
Implementation ID
There might be multiple metapool implementations. To query all available ones, see here. As of the current date (31.10.2023), there is only one metapool implementation available. Since the _implementation_idx starts at 0, users need to input "0" when deploying a pool.
Source code
eventMetaPoolDeployed:coin:addressbase_pool:addressA:uint256fee:uint256deployer:addressMAX_COINS:constant(uint256)=8MAX_FEE:constant(uint256)=5*10**9FEE_DENOMINATOR:constant(uint256)=10**10@externaldefdeploy_metapool(_base_pool:address,_name:String[32],_symbol:String[10],_coin:address,_A:uint256,_fee:uint256,_offpeg_fee_multiplier:uint256,_ma_exp_time:uint256,_implementation_idx:uint256,_asset_type:uint8,_method_id:bytes4,_oracle:address,)->address:""" @notice Deploy a new metapool @param _base_pool Address of the base pool to use within the metapool @param _name Name of the new metapool @param _symbol Symbol for the new metapool - will be concatenated with the base pool symbol @param _coin Address of the coin being used in the metapool @param _A Amplification co-efficient - a higher value here means less tolerance for imbalance within the pool's assets. Suggested values include: * Uncollateralized algorithmic stablecoins: 5-10 * Non-redeemable, collateralized assets: 100 * Redeemable assets: 200-400 @param _fee Trade fee, given as an integer with 1e10 precision. The the maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @param _asset_type Asset type for token, as an integer @param _method_id First four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracle Rate oracle address. @return Address of the deployed pool """assertnotself.base_pool_assets[_coin],"Invalid asset: Cannot pair base pool asset with base pool's LP token"assert_fee<=100000000,"Invalid fee"assert_offpeg_fee_multiplier*_fee<=MAX_FEE*FEE_DENOMINATORbase_pool_n_coins:uint256=len(self.base_pool_data[_base_pool].coins)assertbase_pool_n_coins!=0,"Base pool is not added"implementation:address=self.metapool_implementations[_implementation_idx]assertimplementation!=empty(address),"Invalid implementation index"# things break if a token has >18 decimalsdecimals:uint256=ERC20(_coin).decimals()assertdecimals<19,"Max 18 decimals for coins"# combine _coins's _asset_type and basepool coins _asset_types:base_pool_asset_types:DynArray[uint8,MAX_COINS]=self.base_pool_data[_base_pool].asset_typesasset_types:DynArray[uint8,MAX_COINS]=[_asset_type,0]foriinrange(0,MAX_COINS):ifi==base_pool_n_coins:breakasset_types.append(base_pool_asset_types[i])_coins:DynArray[address,MAX_COINS]=[_coin,self.base_pool_data[_base_pool].lp_token]_rate_multipliers:DynArray[uint256,MAX_COINS]=[10**(36-decimals),10**18]_method_ids:DynArray[bytes4,MAX_COINS]=[_method_id,empty(bytes4)]_oracles:DynArray[address,MAX_COINS]=[_oracle,empty(address)]pool:address=create_from_blueprint(implementation,_name,# _name: String[32]_symbol,# _symbol: String[10]_A,# _A: uint256_fee,# _fee: uint256_offpeg_fee_multiplier,# _offpeg_fee_multiplier: uint256_ma_exp_time,# _ma_exp_time: uint256self.math_implementation,# _math_implementation: address_base_pool,# _base_pool: address_coins,# _coins: DynArray[address, MAX_COINS]self.base_pool_data[_base_pool].coins,# base_coins: DynArray[address, MAX_COINS]_rate_multipliers,# _rate_multipliers: DynArray[uint256, MAX_COINS]asset_types,# asset_types: DynArray[uint8, MAX_COINS]_method_ids,# _method_ids: DynArray[bytes4, MAX_COINS]_oracles,# _oracles: DynArray[address, MAX_COINS]code_offset=3)# add pool to pool_listlength:uint256=self.pool_countself.pool_list[length]=poolself.pool_count=length+1base_lp_token:address=self.base_pool_data[_base_pool].lp_tokenself.pool_data[pool].decimals=[decimals,18,0,0,0,0,0,0]self.pool_data[pool].n_coins=2self.pool_data[pool].base_pool=_base_poolself.pool_data[pool].coins=[_coin,self.base_pool_data[_base_pool].lp_token]self.pool_data[pool].implementation=implementationis_finished:bool=Falseswappable_coin:address=empty(address)foriinrange(MAX_COINS):ifi<len(self.base_pool_data[_base_pool].coins):swappable_coin=self.base_pool_data[_base_pool].coins[i]else:is_finished=Trueswappable_coin=base_lp_tokenkey:uint256=(convert(_coin,uint256)^convert(swappable_coin,uint256))length=self.market_counts[key]self.markets[key][length]=poolself.market_counts[key]=length+1ifis_finished:breaklogMetaPoolDeployed(_coin,_base_pool,_A,_fee,msg.sender)returnpool
>>>Factory.deploy_metapool("0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7",# _base_pool"crvUSD/3CRV",# _name"crvusd-3crv"# _symbol"0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E",# _coin1500# _A1000000,# _fee20000000000,# _offpeg_fee_multiplier865,# _ma_exp_time0,# _implementation_idx0,# _asset_type"b""",# _method_id"0x0000000000000000000000000000000000000000"# _oracle)'returns address of the deployed metapool'
Function to deploy a gauge. The Factory utilizes the gauge_implementation to create the contract from a blueprint.
Returns: Deployed gauge (address).
Emits: LiquidityGaugeDeployed
Input
Type
Description
_pool
address
Pool address to deploy the gauge for
Source code
eventLiquidityGaugeDeployed:pool:addressgauge:address@externaldefdeploy_gauge(_pool:address)->address:""" @notice Deploy a liquidity gauge for a factory pool @param _pool Factory pool address to deploy a gauge for @return Address of the deployed gauge """assertself.pool_data[_pool].coins[0]!=empty(address),"Unknown pool"assertself.pool_data[_pool].liquidity_gauge==empty(address),"Gauge already deployed"implementation:address=self.gauge_implementationassertimplementation!=empty(address),"Gauge implementation not set"gauge:address=create_from_blueprint(self.gauge_implementation,_pool,code_offset=3)self.pool_data[_pool].liquidity_gauge=gaugelogLiquidityGaugeDeployed(_pool,gauge)returngauge
>>>Factory.deploy_gauge("0x36DfE783603522566C046Ba1Fa403C8c6F569220")'returns address of the deployed gauge'