Skip to main content

Pool Factory: Overview

The Tricrypto-NG Factory allows the permissionless deployment of two-coin volatile asset pools, as well as gauges. **The liquidity pool and LP token share the same contract.**Additionally, the Factory contract is the direct admin and fee receiver of all pools. In turn, the Factory is controlled by the CurveDAO.

Contract Source & Deployment

Source code for this contract is available on Github.
A list of all deployed contracts can be found here.


Implementations

The Tricrypto-NG Factory makes use of blueprint contracts to deploy its contracts from the implementations.

warning

Implementation contracts are upgradable. They can either be replaced, or additional implementation contracts can be added. Therefore, please always make sure to check the most recent ones.

It utilizes four different implementations:

  • pool_implementations, containing multiple blueprint contracts that are used to deploy the pools.
  • gauge_implementation, containing a blueprint contract that is used when deploying gauges for pools.
  • views_implementation, containing a view methods contract relevant for integrators and users looking to interact with the AMMs.
  • math_implementation, containing math functions used in the AMM.

More on the Math Implementation and Views Implementation.

Query Implementations

pool_implementation

Factory.pool_implementations(arg0: uint256) -> address: view

Getter for the current pool implementation contract. This accounts for variations such as two-coin and three-pool pools.

InputTypeDescription
arg0uint256Index

Returns: Pool blueprint contract (address).

<>Source code
pool_implementations: public(HashMap[uint256, address])
Example
>>> Factory.pool_implementation(0)
'0x66442B0C5260B92cAa9c234ECf2408CBf6b19a6f'

gauge_implementation

Factory.gauge_implementation() -> address: view

Getter for the current gauge implementation contract.

Returns: Gauge blueprint contract (address).

<>Source code
gauge_implementation: public(address)
Example
>>> Factory.gauge_implementation()
'0x5fC124a161d888893529f67580ef94C2784e9233'

views_implementation

Factory.views_implementation() -> address: view

Getter for the current views implementation contract.

Returns: Views blueprint contract (address).

<>Source code
views_implementation: public(address)
Example
>>> Factory.views_implementation()
'0x064253915b8449fdEFac2c4A74aA9fdF56691a31'

math_implementation

Factory.math_implementation() -> address: view

Getter for the current pool implementation contract.

Returns: Math blueprint contract (address).

<>Source code
math_implementation: public(address)
Example
>>> Factory.math_implementation()
'0xcBFf3004a20dBfE2731543AA38599A526e0fD6eE'

Set New Implementations

New implementations can be set via these admin-only functions:

set_pool_implementation

Factory.set_pool_implementation(_pool_implementation: address, _implementation_index: uint256):
Guarded Method

This function is only callable by the admin of the contract.

Function to set a _pool_implementation for _implementation_index.

InputTypeDescription
_pool_implementationaddressNew pool implementation
_implementation_indexuint256Index

Emits event: UpdatePoolImplementation

<>Source code
event UpdatePoolImplementation:
_implemention_id: uint256
_old_pool_implementation: address
_new_pool_implementation: address

pool_implementations: public(HashMap[uint256, address])

@external
def set_pool_implementation(
_pool_implementation: address, _implementation_index: uint256
):
"""
@notice Set pool implementation
@dev Set to empty(address) to prevent deployment of new pools
@param _pool_implementation Address of the new pool implementation
@param _implementation_index Index of the pool implementation
"""
assert msg.sender == self.admin, "dev: admin only"

log UpdatePoolImplementation(
_implementation_index,
self.pool_implementations[_implementation_index],
_pool_implementation
)

self.pool_implementations[_implementation_index] = _pool_implementation
Example
>>> Factory.set_pool_implementation("todo")
'todo'

set_gauge_implementation

Factory.set_gauge_implementation(_gauge_implementation: address):
Guarded Method

This function is only callable by the admin of the contract.

Function to set a new _gauge_implementation.

InputTypeDescription
_gauge_implementationaddressGauge blueprint contract

Emits event: UpdateGaugeImplementation

<>Source code
event UpdateGaugeImplementation:
_old_gauge_implementation: address
_new_gauge_implementation: address

gauge_implementation: public(address)

@external
def set_gauge_implementation(_gauge_implementation: address):
"""
@notice Set gauge implementation
@dev Set to empty(address) to prevent deployment of new gauges
@param _gauge_implementation Address of the new token implementation
"""
assert msg.sender == self.admin, "dev: admin only"

log UpdateGaugeImplementation(self.gauge_implementation, _gauge_implementation)
self.gauge_implementation = _gauge_implementation
Example
>>> Factory.set_gauge_implementation("todo")
'todo'

set_views_implementation

Factory.set_views_implementation(_views_implementation: address):
Guarded Method

This function is only callable by the admin of the contract.

Function to set a new _views_implementation.

InputTypeDescription
_views_implementationaddressViews blueprint contract

Emits event: UpdateViewsImplementation

<>Source code
event UpdateViewsImplementation:
_old_views_implementation: address
_new_views_implementation: address

views_implementation: public(address)

@external
def set_views_implementation(_views_implementation: address):
"""
@notice Set views contract implementation
@param _views_implementation Address of the new views contract
"""
assert msg.sender == self.admin, "dev: admin only"

log UpdateViewsImplementation(self.views_implementation, _views_implementation)
self.views_implementation = _views_implementation
Example
>>> Factory.set_views_implementation("todo")
'todo'

set_math_implementation

Factory.set_math_implementation(_math_implementation: address):
Guarded Method

This function is only callable by the admin of the contract.

Function to set a new _math_implementation.

InputTypeDescription
_math_implementationaddressMath blueprint contract

Emits event: UpdateMathImplementation

<>Source code
event UpdateMathImplementation:
_old_math_implementation: address
_new_math_implementation: address

math_implementation: public(address)

@external
def set_math_implementation(_math_implementation: address):
"""
@notice Set math implementation
@param _math_implementation Address of the new math contract
"""
assert msg.sender == self.admin, "dev: admin only"

log UpdateMathImplementation(self.math_implementation, _math_implementation)
self.math_implementation = _math_implementation
Example
>>> Factory.set_math_implementation("todo")
'todo'

Deploying Pools

warning

The transaction will revert if the following requirements are not met.

deploy_pool

The pool deployment is permissionless, but it must adhere to certain parameter limitations:

ParameterLimitation
AA_min - 1 < A < A_max + 1
gammagamma_min - 1 < gamma < gamma_max + 1
mid_feemid_fee < fee_max - 1; (mid_fee can be 0)
out_feeout_fee >= mid_fee AND out_fee < fee_max - 1
fee_gamma0 < fee_gamma < 10^18 + 1
allowed_extra_profitallowed_extra_profit < 10^18 + 1
adjustment_step0 < adjustment_step < 10^18 + 1
ma_exp_time86 < ma_exp_time < 872542
initial_prices10^6 < initial_prices[0] and initial_prices[1] < 10^30
  • Three coins; no duplicate coins possible.
  • **implementation_id**cannot be ZERO_ADDRESS.

With:

ParametersValue
n_coins3
A_multiplier10000
A_minn_coins^n_coins * A_multiplier = 270000
A_max1000 * A_multiplier * n_coins^n_coins = 270000000
gamma_min10^10 = 10000000000
gamma_max5 * 10^16 = 50000000000000000
fee_max10 * 10^9 = 10000000000
Factory.deploy_pool(_name: String[64], _symbol: String[32], _coins: address[N_COINS], _weth: address, implementation_id: uint256, A: uint256, gamma: uint256, mid_fee: uint256, out_fee: uint256, fee_gamma: uint256, allowed_extra_profit: uint256, adjustment_step: uint256, ma_exp_time: uint256, initial_prices: uint256[N_COINS-1],) -> address:

Function to deploy a tricrypto pool.

InputTypeDescription
_nameString[64]Pool Name
_symbolString[32]Pool Symbol
_coinsaddress[N_COINS]Included Coins
_wethaddressWETH Address
implementation_iduint256Index of Pool Implementation
Auint256Amplification Factor
gammauint256Gamma
mid_feeuint256Mid Fee
out_feeuint256Out Fee
fee_gammauint256Fee Gamma
allowed_extra_profituint256Allowed Extra Profit
adjustment_stepuint256Adjustment Step
ma_exp_timeuint256Exponential Moving Average Time
initial_pricesuint256[N_COINS-1]Initial Prices

Returns: Deployed pool (address).

Emits event: TricryptoPoolDeployed

<>Source code
event TricryptoPoolDeployed:
pool: address
name: String[64]
symbol: String[32]
weth: address
coins: address[N_COINS]
math: address
salt: bytes32
packed_precisions: uint256
packed_A_gamma: uint256
packed_fee_params: uint256
packed_rebalancing_params: uint256
packed_prices: uint256
deployer: address

N_COINS: constant(uint256) = 3
A_MULTIPLIER: constant(uint256) = 10000

MAX_FEE: constant(uint256) = 10 * 10 **9

MIN_GAMMA: constant(uint256) = 10 **10
MAX_GAMMA: constant(uint256) = 5 * 10**16

MIN_A: constant(uint256) = N_COINS **N_COINS * A_MULTIPLIER / 100
MAX_A: constant(uint256) = 1000 * A_MULTIPLIER * N_COINS**N_COINS

PRICE_SIZE: constant(uint128) = 256 / (N_COINS - 1)
PRICE_MASK: constant(uint256) = 2**PRICE_SIZE - 1

@external
def deploy_pool(
_name: String[64],
_symbol: String[32],
_coins: address[N_COINS],
_weth: address,
implementation_id: uint256,
A: uint256,
gamma: uint256,
mid_fee: uint256,
out_fee: uint256,
fee_gamma: uint256,
allowed_extra_profit: uint256,
adjustment_step: uint256,
ma_exp_time: uint256,
initial_prices: uint256[N_COINS-1],
) -> address:
"""
@notice Deploy a new pool
@param _name Name of the new plain pool
@param _symbol Symbol for the new plain pool - will be concatenated with factory symbol

@return Address of the deployed pool
"""
pool_implementation: address = self.pool_implementations[implementation_id]
assert pool_implementation != empty(address), "Pool implementation not set"

# Validate parameters
assert A > MIN_A-1
assert A < MAX_A+1

assert gamma > MIN_GAMMA-1
assert gamma < MAX_GAMMA+1

assert mid_fee < MAX_FEE-1 # mid_fee can be zero
assert out_fee >= mid_fee
assert out_fee < MAX_FEE-1
assert fee_gamma < 10**18+1
assert fee_gamma > 0

assert allowed_extra_profit < 10**18+1

assert adjustment_step < 10**18+1
assert adjustment_step > 0

assert ma_exp_time < 872542 # 7 * 24 * 60 * 60 / ln(2)
assert ma_exp_time > 86 # 60 / ln(2)

assert min(initial_prices[0], initial_prices[1]) > 10**6
assert max(initial_prices[0], initial_prices[1]) < 10**30

assert _coins[0] != _coins[1] and _coins[1] != _coins[2] and _coins[0] != _coins[2], "Duplicate coins"

decimals: uint256[N_COINS] = empty(uint256[N_COINS])
precisions: uint256[N_COINS] = empty(uint256[N_COINS])
for i in range(N_COINS):
d: uint256 = ERC20(_coins[i]).decimals()
assert d < 19, "Max 18 decimals for coins"
decimals[i] = d
precisions[i] = 10**(18 - d)

# pack precisions
packed_precisions: uint256 = self._pack(precisions)

# pack fees
packed_fee_params: uint256 = self._pack(
[mid_fee, out_fee, fee_gamma]
)

# pack liquidity rebalancing params
packed_rebalancing_params: uint256 = self._pack(
[allowed_extra_profit, adjustment_step, ma_exp_time]
)

# pack A_gamma
packed_A_gamma: uint256 = A << 128
packed_A_gamma = packed_A_gamma | gamma

# pack initial prices
packed_prices: uint256 = 0
for k in range(N_COINS - 1):
packed_prices = packed_prices << PRICE_SIZE
p: uint256 = initial_prices[N_COINS - 2 - k]
assert p < PRICE_MASK
packed_prices = p | packed_prices

# pool is an ERC20 implementation
_salt: bytes32 = block.prevhash
_math_implementation: address = self.math_implementation
pool: address = create_from_blueprint(
pool_implementation,
_name,
_symbol,
_coins,
_math_implementation,
_weth,
_salt,
packed_precisions,
packed_A_gamma,
packed_fee_params,
packed_rebalancing_params,
packed_prices,
code_offset=3
)

# populate pool data
length: uint256 = self.pool_count
self.pool_list[length] = pool
self.pool_count = length + 1
self.pool_data[pool].decimals = decimals
self.pool_data[pool].coins = _coins

# add coins to market:
self._add_coins_to_market(_coins[0], _coins[1], pool)
self._add_coins_to_market(_coins[0], _coins[2], pool)
self._add_coins_to_market(_coins[1], _coins[2], pool)

log TricryptoPoolDeployed(
pool,
_name,
_symbol,
_weth,
_coins,
_math_implementation,
_salt,
packed_precisions,
packed_A_gamma,
packed_fee_params,
packed_rebalancing_params,
packed_prices,
msg.sender,
)

return pool
Example
>>> TricryptoFactory.deploy_pool(
_name: crv/weth/tbtc tripool,
_symbol: crv-weth-tbtc,
_coins: '0xD533a949740bb3306d119CC777fa900bA034cd52', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', '0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa',
_weth: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
implementation_id: 0,
A: 2700000,
gamma: 1300000000000,
mid_fee: 2999999,
out_fee: 80000000,
fee_gamma: 350000000000000,
allowed_extra_profit: 100000000000,
adjustment_step: 100000000000,
ma_exp_time: 600,
initial_prices: todo,
)

'returns address of the deployed pool'

Deploying Gauges

info

Liquidity gauges can only be successfully deployed from the same contract from which the pool was deployed!

deploy_gauge

deploy_gauge(_pool: address) -> address

Deploy a liquidity gauge for a factory pool. The deployed gauge implementation is based on what the factory admin has set for gauge_implementation.

InputTypeDescription
_pooladdressPool address to deploy a gauge for
<>Source code
@external
def deploy_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
"""
assert self.pool_data[_pool].coins[0] != ZERO_ADDRESS, "Unknown pool"
assert self.pool_data[_pool].liquidity_gauge == ZERO_ADDRESS, "Gauge already deployed"
implementation: address = self.gauge_implementation
assert implementation != ZERO_ADDRESS, "Gauge implementation not set"

gauge: address = create_forwarder_to(implementation)
LiquidityGauge(gauge).initialize(_pool)
self.pool_data[_pool].liquidity_gauge = gauge

log LiquidityGaugeDeployed(_pool, gauge)
return gauge
Example
>>> Factory.deploy_gauge('0x...')

'returns address of the deployed gauge'

Fee Receiver

fee_receiver

Factory.fee_receiver() -> address: view

Getter for the fee receiver.

Returns: fee receiver (address).

<>Source code
fee_receiver: public(address)
Example
>>> Factory.fee_receiver()
'0xeCb456EA5365865EbAb8a2661B0c503410e9B347'

set_fee_receiver

Factory.set_fee_receiver(_fee_receiver: address):
Guarded Method

This function is only callable by the admin of the contract.

Function to set a new fee_receiver address.

InputTypeDescription
_fee_receiveraddressnew fee receiver address

Emits event: UpdateFeeReceiver

<>Source code
event UpdateFeeReceiver:
_old_fee_receiver: address
_new_fee_receiver: address

admin: public(address)
fee_receiver: public(address)

@external
def set_fee_receiver(_fee_receiver: address):
"""
@notice Set fee receiver
@param _fee_receiver Address that fees are sent to
"""
assert msg.sender == self.admin, "dev: admin only"

log UpdateFeeReceiver(self.fee_receiver, _fee_receiver)
self.fee_receiver = _fee_receiver
Example
>>> Factory.set_fee_receiver("todo")
'todo'