# Curve Chaser Vault Contract
The curve_apy_vault.vy
Vyper smart contract contains all the Kallisto Chaser
Vault functionality. The source code (opens new window)
is maintained by the Volume Finance team.
# Swap Routes
The Chaser Vault defines swap routes for Curve.
The SwapRoute
takes in the addresses of the Curve pools used in the
swapping, and defines the incoming token as well as the token to swap into.
struct SwapRoute:
swap_pool: address
j_token: address
i: int128 # in token index
j: int128 # out token index
is_underlying: bool # true if exchange underlying coins using exchange_underlying()
is_crypto_pool: bool # true if token index in uint256
Parameters
Key | Type | Description |
---|---|---|
swap_pool | address | swap pool to use for swap |
j_token | address | out token address from the pool |
i | int128 | token index into the pool |
j | address | swap pool to use for swap |
is_underlying | bool | true if exchange underlying coins using exchange_underlying() |
is_crypto_pool | bool | true if token index type is uint256 |
The vault locks the balance using the following data type:
struct LockedBalance:
amount: int128
end: uint256
where
amount
is the token amount locked &end
is the end of the lock period.
# Contract Events
# ERC20 events
# Transfer event
Transfers from a destination address to a source address.
event Transfer:
_from: indexed(address)
_to: indexed(address)
_value: uint256
# Approval event
Grants permission to execute logic.
event Approval:
_owner: indexed(address)
_spender: indexed(address)
_value: uint256
# Vault events
# Deposit event:
event Deposit:
_token: indexed(address)
_from: indexed(address)
token_amount: uint256
vault_balance: uint256
Parameters
Key | Type | Description |
---|---|---|
token_address | address | Address of deposited token |
amount | uint256 | deposited amount |
i | int128 | deposit token index of the main pool |
swap_route | SwapRoute[] | best swap route on Curve |
min_amount | uint256 | minimum amount of vault balance after deposit |
# Withdrawal event:
event Withdraw:
_token: indexed(address)
_from: indexed(address)
token_amount: uint256
vault_balance: uint256
Parameters
Key | Type | Description |
---|---|---|
token_address | address | withdrawal token address |
amount | uint256 | withdrawal vault balance amount (not token amount) |
i | int128 | withdrawal token index of the main pool |
swap_route | SwapRoute[] | best swap route on Curve |
min_amount | uint256 | minimum amount of withdrawn token from withdraw |
Event to switch the Chaser vault position from the current Curve pool to the better Curve pool.
event Updated:
old_pool: indexed(address)
new_pool: indexed(address)
_timestamp: uint256
from_amount: uint256
to_amount: uint256
Parameters
Key | Type | Description |
---|---|---|
_out_token | address | token to withdraw from current pool |
old_i | int128 | withdraw token index of current pool |
swap_route | SwapRoute[] | best swap route on Curve |
new_pool | address | Address of new Curve pool |
new_deposit | address | Address of new Curve deposit contract |
new_i | int128 | deposit token index of new Curve Pool |
new_pool_coin_count | uint8 | coin count of new Curve Pool |
new_lp_token | address | Address of new Curve LP token |
new_is_crypto_pool | bool | True if new main pool coin index type is uint256 |
new_lp_min_amount | uint256 | minimum amount of new curve lp token |
# ERC20 Standard Interfaces
The Chaser Vault Contract implements ERC20 interfaces and follows the interfaces required to interact with Curve.
# Key Parametes
Key | Type | Description |
---|---|---|
name | public(String[64]) | token name |
symbol | public(String[32]) | token symbol |
balanceOf | public(HashMap[address, uint256]) | amount of tokens held by owner |
allowance | public(HashMap[address, HashMap[address, uint256]]) | grant approval to transfer to another address |
totalSupply | public(uint256) | total token supply |
paused | public(bool) | if true, pause modifier will halt function's ability to work |
main_pool | public(address) | main curve pool address |
main_pool_coin_count | public(uint8) | coin count |
is_crypto_pool | public(bool) | true if main pool coin index type is uint256 |
main_deposit | public(address) | main deposit address for meta pools; (0) for base pools, (1) for lending pools |
main_lp_token | public(address) | the main token in the Curve LP address of main pool |
validators | public (HashMap[address, bool]) | validators who can update pool |
admin | public(address) | vault admin |
zap_deposit | public(address) | ZAP deposit pool address that curve provides |
# Key Constants
Key | Type | Description |
---|---|---|
MAX_COINS = 8 | constant(uint8) | max coins in the Curve pool |
VETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE | constant(address) | virtual ETH address |
WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 | constant(address) | wrapped ETH address |
IS_A_POOL_IN_DEPOSIT = 0x0000000000000000000000000000000000000001 | constant(address) | use address(1) as deposit address for aave pools |
INIT_ZAP_DEPOSIT = 0xA79828DF1850E8a3A3064576f380D90aECDD3359 | constant(address) | initiate ZAP deposit contract address |
TRUE_BYTES32 = 0x0000000000000000000000000000000000000000000000000000000000000001 | constant(bytes32) | conversion True into bytes32 |
MAX_SWAP = 4 | constant(uint256) | max count of swap steps |
# Interface
# CrvPool
interface CrvPool:
def remove_liquidity_one_coin(token_amount: uint256, i: int128, min_amount: uint256): nonpayable
def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): payable
def exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256): payable
# CryptoPool
interface CryptoPool:
def exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256): payable
def exchange_underlying(i: uint256, j: uint256, dx: uint256, min_dy: uint256): payable
def remove_liquidity_one_coin(token_amount: uint256, i: uint256, min_amount: uint256): nonpayable
# CrvAPool
interface CrvAPool:
def remove_liquidity_one_coin(token_amount: uint256, i: int128, min_amount: uint256, use_underlying: bool): nonpayable
# CryptoAPool
interface CryptoAPool:
def remove_liquidity_one_coin(token_amount: uint256, i: uint256, min_amount: uint256, use_underlying: bool): nonpayable
# CrvZapDeposit
interface CrvZapDeposit:
def remove_liquidity_one_coin(_pool: address, token_amount: uint256, i: int128, min_amount: uint256): nonpayable
# CryptoZapDeposit
interface CryptoZapDeposit:
def remove_liquidity_one_coin(_pool: address, token_amount: uint256, i: uint256, min_amount: uint256): nonpayable
# LiquidityGauge
interface LiquidityGauge:
def deposit(amount: uint256): nonpayable
def withdraw(amount: uint256): nonpayable
# CrvMinter
interface CrvMinter:
def mint(gauge_addr: address): nonpayable
# ERC20
interface ERC20:
def balanceOf(_to: address) -> uint256: view
# WrappedEth
interface WrappedEth:
def deposit(): payable
def withdraw(amount: uint256): nonpayable
# VeCRV
interface VeCRV:
def increase_unlock_time(_unlock_time: uint256): nonpayable
def increase_amount(_value: uint256): nonpayable
def create_lock(_value: uint256, _unlock_time: uint256): nonpayable
def locked(addr: address) -> LockedBalance: view
def withdraw(): nonpayable
# Vault Initiation
The external initiation function (opens new window) is called to set up the vault by specifying the name, token, main curve pool, main deposit address, main pool coin count, and the curve LP token address of the main pool,
def __init__(_name: String[64], _symbol: String[32], _main_pool: address, _main_deposit: address, _main_pool_coin_count: uint8, _main_lp_token: address, _main_liquidity_gauge: address, _is_crypto_pool: bool):
# ERC20 Common Functions
Function types
- @public: public accessibility
- @internal: only the contract itself and contracts deriving from it can access
- @external: can only be accessed externally
- @private: can be accessed only from the contract itself
# Common Internal Functions
Internal functions are defined for minting, burning, safe transfer and safe transfer from alternative addresses.
# mint
Mints a token specified by its address at a specific amount.
def _mint(_to: address, _value: uint256):
assert _to != ZERO_ADDRESS # dev: zero address
self.totalSupply += _value
self.balanceOf[_to] += _value
log Transfer(ZERO_ADDRESS, _to, _value)
# burn
Burn a token specified by its address at a specific amount.
def _burn(_to: address, _value: uint256):
assert _to != ZERO_ADDRESS # dev: zero address
self.totalSupply -= _value
self.balanceOf[_to] -= _value
log Transfer(_to, ZERO_ADDRESS, _value)
# safe_approve
Approves the use of a token by a smart contract.
def safe_approve(_token: address, _to: address, _value: uint256):
_response: Bytes[32] = raw_call(
_token,
concat(
method_id("approve(address,uint256)"),
convert(_to, bytes32),
convert(_value, bytes32)
),
max_outsize=32
) # dev: failed approve
if len(_response) > 0:
assert convert(_response, bool) # dev: failed approve
# safe_transfer
Transfers a token from the vault to another address.
def safe_transfer(_token: address, _to: address, _value: uint256):
_response: Bytes[32] = raw_call(
_token,
concat(
method_id("transfer(address,uint256)"),
convert(_to, bytes32),
convert(_value, bytes32)
),
max_outsize=32
) # dev: failed transfer
if len(_response) > 0:
assert convert(_response, bool) # dev: failed transfer
# safe_transfer_from
Transfers a token from one address to another address.
def safe_transfer_from(_token: address, _from: address, _to: address, _value: uint256):
_response: Bytes[32] = raw_call(
_token,
concat(
method_id("transferFrom(address,address,uint256)"),
convert(_from, bytes32),
convert(_to, bytes32),
convert(_value, bytes32)
),
max_outsize=32
) # dev: failed transfer from
if len(_response) > 0:
assert convert(_response, bool) # dev: failed transfer from
# Common External Functions
These functions are used for accounting-related purposes.
# transfer
Sends tokens from the vault to another address.
def transfer(_to : address, _value : uint256) -> bool:
assert _to != ZERO_ADDRESS # dev: zero address
self.balanceOf[msg.sender] -= _value
self.balanceOf[_to] += _value
log Transfer(msg.sender, _to, _value)
return True
# transferFrom
Sends tokens from one address to another address.
def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
assert _to != ZERO_ADDRESS # dev: zero address
self.balanceOf[_from] -= _value
self.balanceOf[_to] += _value
self.allowance[_from][msg.sender] -= _value
log Transfer(_from, _to, _value)
return True
# approve
Sets the amount as the allowance of spender over the caller’s tokens.
def approve(_spender : address, _value : uint256) -> bool:
assert _value == 0 or self.allowance[msg.sender][_spender] == 0
self.allowance[msg.sender][_spender] = _value
log Approval(msg.sender, _spender, _value)
return True
# increaseAllowance
Increases the allowance granted to the spender by the caller.
def increaseAllowance(_spender: address, _value: uint256) -> bool:
allowance: uint256 = self.allowance[msg.sender][_spender]
allowance += _value
self.allowance[msg.sender][_spender] = allowance
log Approval(msg.sender, _spender, allowance)
return True
# decreaseAllowance
Decreases the allowance granted to the spender by the caller.
def decreaseAllowance(_spender: address, _value: uint256) -> bool:
allowance: uint256 = self.allowance[msg.sender][_spender]
allowance -= _value
self.allowance[msg.sender][_spender] = allowance
log Approval(msg.sender, _spender, allowance)
return True
# collect_management_fee
Collects management fee for using Chaser vault.
def collect_management_fee():
fee_amount: uint256 = (block.timestamp - convert(self.last_management_fee_epoch, uint256)) * convert(self.management_fee, uint256) * self.totalSupply / ONE_YEAR / DENOMINATOR
if fee_amount > 0:
self._mint(self.admin, fee_amount)
self.last_management_fee_epoch = convert(block.timestamp, uint40)
# transfer_admin
Transfer the admin address to another one.
def transfer_admin(_admin: address):
# transfer admin permission
old_admin: address = self.admin
assert msg.sender == old_admin and _admin != ZERO_ADDRESS and old_admin != _admin
self.validators[old_admin] = False
self.validators[msg.sender] = True
self.admin = _admin
# set_validator
Register a new validator or remove a current validator.
def set_validator(_validator: address, _value: bool):
assert msg.sender == self.admin
self.validators[_validator] = _value
# Functions that Interact with Curve
# deposit
# @internal
The internal deposit function (opens new window) interacts with Curve's smart contracts and adds liquidity for one token into a Curve liquidity pool.
def _deposit(main_pool_: address, _main_deposit: address, _main_pool_coin_count: uint8, i: int128, in_token: address, in_amount: uint256):
The internal vote-escrowed deposit function (opens new window) interacts with Curve's smart contracts to add a locked balance as stake into a Curve liquidity pool.
def ve_deposit(liquidity_gauge: address, _crv_balance: uint256):
# @external
The external deposit function (opens new window) works as a wrapper of the internal deposit and swap functions. It is called when users deposit their tokens into the vault. Upon being called, it swaps some of the deposit tokens for the tokens required by the pool, and adds liquidity.
def deposit(token_address: address, amount: uint256, i: int128, swap_route: DynArray[SwapRoute, MAX_SWAP], min_amount: uint256) -> uint256:
# swap
The internal swap function (opens new window) interacts with Curve's smart contract and uses a Curve liquidity pool to swap one token for another. Upon being called, the function swaps the specified amount of the from_token for the to_token. The inputs include the Curve liquidity pool's address, the indices for the from_token and to_token, the addresses for the two tokens, the amount to swap for the from_token, and whether the pool is a crypto pool.
def _swap(pool: address, i: int128, j: int128, from_token: address, to_token: address, is_underlying: bool, from_amount: uint256, is_crypto_pool: bool) -> uint256:
# withdraw
# @internal
The internal withdraw function (opens new window) interacts with the Curve smart contracts directly. It approves the transaction and removes liquidity in one token. The function requires inputs specifying the LP tokens and the main pool address to remove liquidity and returned token address, index, and the amount.
def _withdraw(lp_token: address, _main_pool: address, out_token: address, i: int128, out_amount: uint256) -> uint256:
# @external
The external withdraw function (opens new window) is a wrapper of the internal withdraw function. It is called when users withdraw liquidity from the vault. The inputs include withdrawal token address and amount, swap route, and the minimal amount for swapping. Since the users' withdrawal token may differ from the pool's tokens, a swap will be applied in that case.
def withdraw(token_address: address, amount: uint256, i: int128, swap_route: DynArray[SwapRoute, MAX_SWAP], min_amount: uint256) -> uint256:
# update_pool
The internal update pool function (opens new window) moves the vault's liquidity from one pool to another one. It first removes liquidity from the old pool and receives one token. Then, it swaps this token for the new token and adds liquidity to the new pool. This is followed by updating the new LP amount.
def update_pool(_out_token: address, old_i: int128, swap_route: DynArray[SwapRoute, MAX_SWAP], new_pool: address, new_deposit: address, new_i: int128, new_pool_coin_count: uint8, new_lp_token: address, new_is_crypto_pool: bool, new_lp_min_amount: uint256):
# collect_crv_reward
The external CRV reward collector function (opens new window) collects the CRV rewards accumulated through the Chaser vault's Curve pool. Each pool has a unique liquidity gauge. The function checks the
def collect_crv_reward(swap_route: DynArray[SwapRoute, MAX_SWAP], i: int128, min_amount: uint256) -> uint256:
# Emergency Functions
Emergency functions provide security for the vault and can only be executed by the admin.
# set_main_pool
Used by the admin to set the main pool.
def set_main_pool(_new_pool: address):
assert msg.sender == self.admin
self.main_pool = _new_pool
# set_main_deposit
Used by the admin to set the main deposit.
def set_main_deposit(_new_deposit: address):
assert msg.sender == self.admin
self.main_deposit = _new_deposit
# set_main_pool_coin_count
Used by the admin to set the pool coin count.
def set_main_pool_coin_count(_new_main_pool_coin_count: uint8):
assert msg.sender == self.admin
self.main_pool_coin_count = _new_main_pool_coin_count
# set_is_crypto_pool
Used by the admin to set the new Curve pool.
def set_is_crypto_pool(_new_is_crypto_pool: bool):
assert msg.sender == self.admin
self.is_crypto_pool = _new_is_crypto_pool
# set_main_lp_token
Used by the admin to set the main liquidity pool token.
def set_main_lp_token(_new_main_lp_token: address):
assert msg.sender == self.admin
self.main_lp_token = _new_main_lp_token
# set_main_liquidity_gauge
Used by admin to se the main liquidity gauge.
LiquidityGauge
measures liquidity provided by users over time, in order to distribute CRV and other rewards from staking.
def set_main_liquidity_gauge(_new_main_liquidity_gauge: address):
assert msg.sender == self.admin and self.main_liquidity_gauge == ZERO_ADDRESS
lp_token: address = self.main_lp_token
amount: uint256 = ERC20(lp_token).balanceOf(self)
self.safe_approve(lp_token, _new_main_liquidity_gauge, amount)
LiquidityGauge(_new_main_liquidity_gauge).deposit(amount)
self.liquidity = amount
self.main_liquidity_gauge = _new_main_liquidity_gauge
# set_zap_deposit
Used by the admin to set the ZAP deposit.
def set_zap_deposit(_new_zap_deposit: address):
assert msg.sender == self.admin
self.zap_deposit = _new_zap_deposit
# pause
Used by the admin to pause all functionality.
def pause(_paused: bool):
assert msg.sender == self.admin
self.paused = _paused