Liquidity Gauge V6
LiquidityGaugeV6.vyThe source code for the LiquidityGaugeV6.vy contract can be found on GitHub. The contract is written in Vyper version 0.3.10.
The contract is deployed on Ethereum at
0x38D9BdA812da2C68dFC6aDE85A7F7a54E77F8325.
{ }Contract ABI▼
[{"name":"Deposit","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"Withdraw","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"UpdateLiquidityLimit","inputs":[{"name":"user","type":"address","indexed":true},{"name":"original_balance","type":"uint256","indexed":false},{"name":"original_supply","type":"uint256","indexed":false},{"name":"working_balance","type":"uint256","indexed":false},{"name":"working_supply","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"CommitOwnership","inputs":[{"name":"admin","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"ApplyOwnership","inputs":[{"name":"admin","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"SetGaugeManager","inputs":[{"name":"_gauge_manager","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"Transfer","inputs":[{"name":"_from","type":"address","indexed":true},{"name":"_to","type":"address","indexed":true},{"name":"_value","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"Approval","inputs":[{"name":"_owner","type":"address","indexed":true},{"name":"_spender","type":"address","indexed":true},{"name":"_value","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_lp_token","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"deposit","inputs":[{"name":"_value","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"deposit","inputs":[{"name":"_value","type":"uint256"},{"name":"_addr","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"deposit","inputs":[{"name":"_value","type":"uint256"},{"name":"_addr","type":"address"},{"name":"_claim_rewards","type":"bool"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"withdraw","inputs":[{"name":"_value","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"withdraw","inputs":[{"name":"_value","type":"uint256"},{"name":"_claim_rewards","type":"bool"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"claim_rewards","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"claim_rewards","inputs":[{"name":"_addr","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"claim_rewards","inputs":[{"name":"_addr","type":"address"},{"name":"_receiver","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"transferFrom","inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"transfer","inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"approve","inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"permit","inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"increaseAllowance","inputs":[{"name":"_spender","type":"address"},{"name":"_added_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"decreaseAllowance","inputs":[{"name":"_spender","type":"address"},{"name":"_subtracted_value","type":"uint256"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"user_checkpoint","inputs":[{"name":"addr","type":"address"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"set_rewards_receiver","inputs":[{"name":"_receiver","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"kick","inputs":[{"name":"addr","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_gauge_manager","inputs":[{"name":"_gauge_manager","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"deposit_reward_token","inputs":[{"name":"_reward_token","type":"address"},{"name":"_amount","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"deposit_reward_token","inputs":[{"name":"_reward_token","type":"address"},{"name":"_amount","type":"uint256"},{"name":"_epoch","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"add_reward","inputs":[{"name":"_reward_token","type":"address"},{"name":"_distributor","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_reward_distributor","inputs":[{"name":"_reward_token","type":"address"},{"name":"_distributor","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_killed","inputs":[{"name":"_is_killed","type":"bool"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"claimed_reward","inputs":[{"name":"_addr","type":"address"},{"name":"_token","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"claimable_reward","inputs":[{"name":"_user","type":"address"},{"name":"_reward_token","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"claimable_tokens","inputs":[{"name":"addr","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"integrate_checkpoint","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"future_epoch_time","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"inflation_rate","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"version","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"DOMAIN_SEPARATOR","inputs":[],"outputs":[{"name":"","type":"bytes32"}]},{"stateMutability":"view","type":"function","name":"salt","inputs":[],"outputs":[{"name":"","type":"bytes32"}]},{"stateMutability":"view","type":"function","name":"balanceOf","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"allowance","inputs":[{"name":"arg0","type":"address"},{"name":"arg1","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"nonces","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"factory","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"manager","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"lp_token","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"is_killed","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"reward_count","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"reward_data","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"tuple","components":[{"name":"token","type":"address"},{"name":"distributor","type":"address"},{"name":"period_finish","type":"uint256"},{"name":"rate","type":"uint256"},{"name":"last_update","type":"uint256"},{"name":"integral","type":"uint256"}]}]},{"stateMutability":"view","type":"function","name":"rewards_receiver","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"reward_integral_for","inputs":[{"name":"arg0","type":"address"},{"name":"arg1","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"working_balances","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"working_supply","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"integrate_inv_supply_of","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"integrate_checkpoint_of","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"integrate_fraction","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"period","inputs":[],"outputs":[{"name":"","type":"int128"}]},{"stateMutability":"view","type":"function","name":"reward_tokens","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"period_timestamp","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"integrate_inv_supply","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]}]
Depositing and Withdrawing
Liquidity pool (LP) tokens can be deposited into or withdrawn from a gauge at any time.
In user interfaces and documentation, the terms "staking" and "unstaking" are often used when referring to gauges. However, the terminology used in the actual source code is deposit and withdraw.
When LP tokens are deposited into a gauge, the smart contract mints an equivalent amount of "gauge tokens" to the depositor. This mechanism ensures that when tokens are withdrawn, the depositor receives the same amount of LP tokens originally deposited. LP tokens are ERC20 tokens and transferable.
Alice deposits 100 crvUSD into the crvusd/USDC liquidity pool and receives 99 LP tokens in return. Observing significant gauge weight and subsequent CRV emissions to this pool, she decides to deposit (stake) her LP tokens into the gauge. Consequently, she begins to earn CRV rewards based on her liquidity share and her boost factor within the pool. Alice can claim rewards or withdraw her LP tokens at any point in time.
deposit
LiquidityGaugeV6.deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = False)Function to deposit _value of LP tokens into the gauge. When depositing LP tokens into the gauge, the contract mints the equivalent amount of "gauge token" to the user.
Emits: Deposit, Transfer, UpdateLiquidityLimit events.
| Input | Type | Description |
|---|---|---|
_value | uint256 | Number of LP tokens to deposit. |
_addr | address | Address to deposit the LP tokens for. Defaults to msg.sender. |
_claim_rewards | bool | Whether to additionally claim rewards or not. |
<>Source code▼
event Deposit:
provider: indexed(address)
value: uint256
event Transfer:
_from: indexed(address)
_to: indexed(address)
_value: uint256
@external
@nonreentrant('lock')
def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = False):
"""
@notice Deposit `_value` LP tokens
@dev Depositting also claims pending reward tokens
@param _value Number of tokens to deposit
@param _addr Address to deposit for
"""
assert _addr != empty(address) # dev: cannot deposit for zero address
self._checkpoint(_addr)
if _value != 0:
is_rewards: bool = self.reward_count != 0
total_supply: uint256 = self.totalSupply
if is_rewards:
self._checkpoint_rewards(_addr, total_supply, _claim_rewards, empty(address))
total_supply += _value
new_balance: uint256 = self.balanceOf[_addr] + _value
self.balanceOf[_addr] = new_balance
self.totalSupply = total_supply
self._update_liquidity_limit(_addr, new_balance, total_supply)
ERC20(self.lp_token).transferFrom(msg.sender, self, _value)
log Deposit(_addr, _value)
log Transfer(empty(address), _addr, _value)
▶Example▼
>>> LiquidityGaugeV6.deposit(1000000000000000000, '0x989AEb4d175e16225E39E87d0D97A3360524AD80', False)
withdraw
LiquidityGaugeV6.withdraw(_value: uint256, _claim_rewards: bool = False)Function to withdraw _value of LP tokens from the gauge.
Emits: Withdraw, Transfer, UpdateLiquidityLimit events.
| Input | Type | Description |
|---|---|---|
_value | uint256 | Number of LP tokens to withdraw. |
_claim_rewards | bool | Whether to additionally claim rewards or not. |
<>Source code▼
event Withdraw:
provider: indexed(address)
value: uint256
event Transfer:
_from: indexed(address)
_to: indexed(address)
_value: uint256
@external
@nonreentrant('lock')
def withdraw(_value: uint256, _claim_rewards: bool = False):
"""
@notice Withdraw `_value` LP tokens
@dev Withdrawing also claims pending reward tokens
@param _value Number of tokens to withdraw
"""
self._checkpoint(msg.sender)
if _value != 0:
is_rewards: bool = self.reward_count != 0
total_supply: uint256 = self.totalSupply
if is_rewards:
self._checkpoint_rewards(msg.sender, total_supply, _claim_rewards, empty(address))
total_supply -= _value
new_balance: uint256 = self.balanceOf[msg.sender] - _value
self.balanceOf[msg.sender] = new_balance
self.totalSupply = total_supply
self._update_liquidity_limit(msg.sender, new_balance, total_supply)
ERC20(self.lp_token).transfer(msg.sender, _value)
log Withdraw(msg.sender, _value)
log Transfer(msg.sender, empty(address), _value)
▶Example▼
>>> LiquidityGaugeV6.withdraw(1000000000000000000, False)
Claiming Rewards
Reward tokens can be claimed using the claim_rewards function. This function claims all externally added rewards from the gauge in a single transaction.
claim_rewards claim?The claim_rewards function only claims "permissionless rewards", not CRV emissions directed to the gauge. If there are multiple reward tokens, calling the function will result in a claim of all reward tokens at once.
CRV emissions directed to the gauge are claimable from the Minter.vy contract using the mint function.
The liquidity gauge records checkpoints to determine how much external rewards each user is entitled to claim.
▶_checkpoint_rewards▼
@internal
def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _receiver: address):
"""
@notice Claim pending rewards and checkpoint rewards for a user
"""
user_balance: uint256 = 0
receiver: address = _receiver
if _user != empty(address):
user_balance = self.balanceOf[_user]
if _claim and _receiver == empty(address):
# if receiver is not explicitly declared, check if a default receiver is set
receiver = self.rewards_receiver[_user]
if receiver == empty(address):
# if no default receiver is set, direct claims to the user
receiver = _user
reward_count: uint256 = self.reward_count
for i in range(MAX_REWARDS):
if i == reward_count:
break
token: address = self.reward_tokens[i]
integral: uint256 = self.reward_data[token].integral
last_update: uint256 = min(block.timestamp, self.reward_data[token].period_finish)
duration: uint256 = last_update - self.reward_data[token].last_update
if duration != 0 and _total_supply != 0:
self.reward_data[token].last_update = last_update
integral += duration * self.reward_data[token].rate * 10**18 / _total_supply
self.reward_data[token].integral = integral
if _user != empty(address):
integral_for: uint256 = self.reward_integral_for[token][_user]
new_claimable: uint256 = 0
if integral_for < integral:
self.reward_integral_for[token][_user] = integral
new_claimable = user_balance * (integral - integral_for) / 10**18
claim_data: uint256 = self.claim_data[_user][token]
total_claimable: uint256 = (claim_data >> 128) + new_claimable
if total_claimable > 0:
total_claimed: uint256 = claim_data % 2**128
if _claim:
assert ERC20(token).transfer(receiver, total_claimable, default_return_value=True)
self.claim_data[_user][token] = total_claimed + total_claimable
elif new_claimable > 0:
self.claim_data[_user][token] = total_claimed + (total_claimable << 128)
These checkpoints occur:
- When a reward token is deposited (this does not record a checkpoint for an individual user but creates a general checkpoint).
- When transferring LP tokens (records a checkpoint for both the sender and the receiver).
- When depositing (staking) LP tokens into the gauge.
- When withdrawing (unstaking) LP tokens from the gauge.
- When rewards (excluding CRV emission rewards, which are claimed via the
Minter.vycontract) are claimed.
claim_rewards
LiquidityGaugeV6.claim_rewards(_addr: address = msg.sender, _receiver: address = empty(address))When claiming for another user, the rewards cannot be redirected to another wallet.
Function to claim rewards from the gauge.
| Input | Type | Description |
|---|---|---|
_addr | address | Address to claim the rewards for. Defaults to msg.sender. |
_receiver | address | Receiver of the rewards. |
<>Source code▼
@external
@nonreentrant('lock')
def claim_rewards(_addr: address = msg.sender, _receiver: address = empty(address)):
"""
@notice Claim available reward tokens for `_addr`
@param _addr Address to claim for
@param _receiver Address to transfer rewards to - if set to
empty(address), uses the default reward receiver
for the caller
"""
if _receiver != empty(address):
assert _addr == msg.sender # dev: cannot redirect when claiming for another user
self._checkpoint_rewards(_addr, self.totalSupply, True, _receiver)
@internal
def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _receiver: address):
"""
@notice Claim pending rewards and checkpoint rewards for a user
"""
user_balance: uint256 = 0
receiver: address = _receiver
if _user != empty(address):
user_balance = self.balanceOf[_user]
if _claim and _receiver == empty(address):
# if receiver is not explicitly declared, check if a default receiver is set
receiver = self.rewards_receiver[_user]
if receiver == empty(address):
# if no default receiver is set, direct claims to the user
receiver = _user
reward_count: uint256 = self.reward_count
for i in range(MAX_REWARDS):
if i == reward_count:
break
token: address = self.reward_tokens[i]
integral: uint256 = self.reward_data[token].integral
last_update: uint256 = min(block.timestamp, self.reward_data[token].period_finish)
duration: uint256 = last_update - self.reward_data[token].last_update
if duration != 0 and _total_supply != 0:
self.reward_data[token].last_update = last_update
integral += duration * self.reward_data[token].rate * 10**18 / _total_supply
self.reward_data[token].integral = integral
if _user != empty(address):
integral_for: uint256 = self.reward_integral_for[token][_user]
new_claimable: uint256 = 0
if integral_for < integral:
self.reward_integral_for[token][_user] = integral
new_claimable = user_balance * (integral - integral_for) / 10**18
claim_data: uint256 = self.claim_data[_user][token]
total_claimable: uint256 = (claim_data >> 128) + new_claimable
if total_claimable > 0:
total_claimed: uint256 = claim_data % 2**128
if _claim:
assert ERC20(token).transfer(receiver, total_claimable, default_return_value=True)
self.claim_data[_user][token] = total_claimed + total_claimable
elif new_claimable > 0:
self.claim_data[_user][token] = total_claimed + (total_claimable << 128)
▶Example▼
>>> LiquidityGaugeV6.claim_rewards('0x989AEb4d175e16225E39E87d0D97A3360524AD80')
claimed_reward
LiquidityGaugeV6.claimed_reward(_addr: address, _token: address) -> uint256: viewGetter for the total amount of _token claimed by _addr.
Returns: claimed tokens (uint256).
| Input | Type | Description |
|---|---|---|
_addr | address | User address to check for. |
_token | address | Reward token to check for. |
<>Source code▼
# user -> [uint128 claimable amount][uint128 claimed amount]
claim_data: HashMap[address, HashMap[address, uint256]]
@view
@external
def claimed_reward(_addr: address, _token: address) -> uint256:
"""
@notice Get the number of already-claimed reward tokens for a user
@param _addr Account to get reward amount for
@param _token Token to get reward amount for
@return uint256 Total amount of `_token` already claimed by `_addr`
"""
return self.claim_data[_addr][_token] % 2**128
▶Example▼
>>> LiquidityGaugeV6.claimed_reward('0x989AEb4d175e16225E39E87d0D97A3360524AD80', '0xfe18aE03741a5b84e39C295Ac9C856eD7991C38e')
30563368675260319
claimable_reward
LiquidityGaugeV6.claimable_reward(_user: address, _reward_token: address) -> uint256: viewFunction to check the claimable amount of _reward_token for _user.
Returns: claimable tokens (uint256).
| Input | Type | Description |
|---|---|---|
_user | address | User address to check for. |
_reward_token | address | Reward token to check for. |
<>Source code▼
reward_integral_for: public(HashMap[address, HashMap[address, uint256]])
@view
@external
def claimable_reward(_user: address, _reward_token: address) -> uint256:
"""
@notice Get the number of claimable reward tokens for a user
@param _user Account to get reward amount for
@param _reward_token Token to get reward amount for
@return uint256 Claimable reward token amount
"""
integral: uint256 = self.reward_data[_reward_token].integral
total_supply: uint256 = self.totalSupply
if total_supply != 0:
last_update: uint256 = min(block.timestamp, self.reward_data[_reward_token].period_finish)
duration: uint256 = last_update - self.reward_data[_reward_token].last_update
integral += (duration * self.reward_data[_reward_token].rate * 10**18 / total_supply)
integral_for: uint256 = self.reward_integral_for[_reward_token][_user]
new_claimable: uint256 = self.balanceOf[_user] * (integral - integral_for) / 10**18
return (self.claim_data[_user][_reward_token] >> 128) + new_claimable
▶Example▼
>>> LiquidityGaugeV6.claimable_reward('0x989AEb4d175e16225E39E87d0D97A3360524AD80', '0xfe18aE03741a5b84e39C295Ac9C856eD7991C38e')
121423107585280954
rewards_receiver
LiquidityGaugeV6.rewards_receiver(arg0: address) -> address: viewGetter for the reward receiver of the caller. By default, this value is set to empty(address), which means the rewards will be claimed to the user. But e.g. for integrations like Convex, the rewards_receiver is set to another contract address, from which the rewards are further distributed.
Returns: reward receiver (address).
| Input | Type | Description |
|---|---|---|
arg0 | address | Receiver of the rewards. |
<>Source code▼
rewards_receiver: public(HashMap[address, address])
▶Example▼
>>> LiquidityGaugeV6.rewards_receiver('0x2618F4c64805526a3092d41f25597CcfE4Dd8216') # random user
'0x0000000000000000000000000000000000000000'
>>> LiquidityGaugeV6.rewards_receiver('0x989AEb4d175e16225E39E87d0D97A3360524AD80') # convex
'0xF681fd1C9118085c3aCB0Eec9d57e25A6e99208f'
set_rewards_receiver
LiquidityGaugeV6.set_rewards_receiver(_receiver: address)Function to set the default reward receiver for the caller. When set to empty(address), rewards are sent to the caller.
| Input | Type | Description |
|---|---|---|
_receiver | address | Receiver address for any rewards claimed. |
<>Source code▼
rewards_receiver: public(HashMap[address, address])
@external
def set_rewards_receiver(_receiver: address):
"""
@notice Set the default reward receiver for the caller.
@dev When set to empty(address), rewards are sent to the caller
@param _receiver Receiver address for any rewards claimed via `claim_rewards`
"""
self.rewards_receiver[msg.sender] = _receiver
▶Example▼
>>> LiquidityGaugeV6.set_rewards_receiver('0x0000000000000000000000000000000000000000')
Permissionless Rewards
Newer liquidity gauges (from LiquidityGaugeV3.vy and upwards) introduce the possibility to add what are termed "permissionless rewards." However, the term "permissionless" might be misleading as only a distributor address, set by the gauge's manager, can add these rewards. The manager address is set to tx.origin at the time of contract deployment.
To add rewards to a gauge, a reward token and a distributor must be set by calling the set_reward_distributor function. This action can only be performed by the manager or the admin of the Factory contract, wich deployed the pool. Each reward token can have only one distributor. The "right to add a reward token" can be transfered. Tokens are added as rewards to the gauge via the add_reward method.
Externally added rewards are not boostable and are distributed purely based on the user's unboosted share of liquidity in the gauge.
For example, if Alice holds 10% of the LP tokens staked in the gauge, she will receive 10% of the externally added rewards, assuming there are no changes in her liquidity share or the amount of rewards.
reward_data
LiquidityGaugeV6.reward_data(arg0: address) -> tuple: viewGetter for the data of a specific reward token.
Returns: token (address), distributor (address), finish_period (uint256), rate (uint256), last_update (uint256) and integral (uint256).
| Input | Type | Description |
|---|---|---|
arg0 | address | Address of the reward token. |
<>Source code▼
struct Reward:
token: address
distributor: address
period_finish: uint256
rate: uint256
last_update: uint256
integral: uint256
reward_data: public(HashMap[address, Reward])
▶Example▼
>>> LiquidityGaugeV6.reward_data('0xfe18aE03741a5b84e39C295Ac9C856eD7991C38e')
'0x0000000000000000000000000000000000000000', '0xC56706334afE5a1638845ED9168E2ca3b3dbCCe7', 1715673839, 1186851500823, 1713351359, 16346318221475032
reward_tokens
LiquidityGaugeV6.reward_tokens(arg0: uint256) -> address: viewGetter for the added reward token at index arg0. New tokens are populated to this variable when calling the add_reward function.
Returns: reward token (address).
| Input | Type | Description |
|---|---|---|
arg0 | uint256 | Index. |
<>Source code▼
MAX_REWARDS: constant(uint256) = 8
# array of reward tokens
reward_tokens: public(address[MAX_REWARDS])
▶Example▼
>>> LiquidityGaugeV6.reward_tokens(0)
'0xfe18aE03741a5b84e39C295Ac9C856eD7991C38e'
>>> LiquidityGaugeV6.reward_tokens(1)
'0x0000000000000000000000000000000000000000'
reward_count
LiquidityGaugeV6.reward_count() -> uint256: viewGetter for the count of added reward tokens. This variable is incremented by one each time add_reward is called.
Returns: number of reward tokens added (uint256).
<>Source code▼
reward_count: public(uint256)
▶Example▼
>>> LiquidityGaugeV6.reward_count()
1
manager
LiquidityGaugeV6.manager() -> address: viewGetter for the gauge manager. This address can add new reward tokens or set distributors for those tokens. The variable is populated when initializing the contract and is set to tx.origin, meaning the signer of the transaction which deploys the gauge is assigned as the gauge manager. The gauge manager is upgradable. It can be changed via the set_gauge_manager function.
Returns: gauge manager (address).
<>Source code▼
manager: public(address)
@external
def __init__(_lp_token: address):
"""
@notice Contract constructor
@param _lp_token Liquidity Pool contract address
"""
self.lp_token = _lp_token
self.factory = msg.sender
self.manager = tx.origin
...
▶Example▼
>>> LiquidityGaugeV6.manager()
'0xC56706334afE5a1638845ED9168E2ca3b3dbCCe7'
add_reward
LiquidityGaugeV6.add_reward(_reward_token: address, _distributor: address)This function can only be called by the manager of the gauge or the owner of the Factory.
Function to add specify a reward token and distributor for the gauge. Once a reward token is added, it cannot be removed anymore.
| Input | Type | Description |
|---|---|---|
_reward_token | address | Reward token address to add. |
_distributor | address | Address which can deposit the reward token. |
<>Source code▼
@external
def add_reward(_reward_token: address, _distributor: address):
"""
@notice Add additional rewards to be distributed to stakers
@param _reward_token The token to add as an additional reward
@param _distributor Address permitted to fund this contract with the reward token
"""
assert msg.sender in [self.manager, Factory(self.factory).admin()] # dev: only manager or factory admin
assert _distributor != empty(address) # dev: distributor cannot be zero address
reward_count: uint256 = self.reward_count
assert reward_count < MAX_REWARDS
assert self.reward_data[_reward_token].distributor == empty(address)
self.reward_data[_reward_token].distributor = _distributor
self.reward_tokens[reward_count] = _reward_token
self.reward_count = reward_count + 1
▶Example▼
>>> LiquidityGaugeV6.add_reward('0xfe18aE03741a5b84e39C295Ac9C856eD7991C38e', '0xC56706334afE5a1638845ED9168E2ca3b3dbCCe7')
set_gauge_manager
LiquidityGaugeV6.set_gauge_manager(_gauge_manager: address)This function can only be called by the manager of the gauge or the admin of the Factory.
Function to set a new gauge manager.
Emits: SetGaugeManager
| Input | Type | Description |
|---|---|---|
set_gauge_manager | address | New gauge manager address. |
<>Source code▼
event SetGaugeManager:
_gauge_manager: address
manager: public(address)
@external
def set_gauge_manager(_gauge_manager: address):
"""
@notice Change the gauge manager for a gauge
@dev The manager of this contract, or the ownership admin can outright modify gauge
managership. A gauge manager can also transfer managership to a new manager via this
method, but only for the gauge which they are the manager of.
@param _gauge_manager The account to set as the new manager of the gauge.
"""
assert msg.sender in [self.manager, Factory(self.factory).admin()] # dev: only manager or factory admin
self.manager = _gauge_manager
log SetGaugeManager(_gauge_manager)
▶Example▼
>>> LiquidityGaugeV6.set_gauge_manager('0xC56706334afE5a1638845ED9168E2ca3b3dbCCe7')
set_reward_distributor
LiquidityGaugeV6.set_reward_distributor(_reward_token: address, _distributor: address)This function can only be called by the manager of the gauge or the admin of the Factory.
Function to reassign the reward distributor for a reward token.
| Input | Type | Description |
|---|---|---|
_reward_token | address | Reward token to reassign the distribution rights for. |
_distributor | address | New reward distributor. |
<>Source code▼
reward_data: public(HashMap[address, Reward])
@external
def set_reward_distributor(_reward_token: address, _distributor: address):
"""
@notice Reassign the reward distributor for a reward token
@param _reward_token The reward token to reassign distribution rights to
@param _distributor The address of the new distributor
"""
current_distributor: address = self.reward_data[_reward_token].distributor
assert msg.sender in [current_distributor, Factory(self.factory).admin(), self.manager]
assert current_distributor != empty(address)
assert _distributor != empty(address)
self.reward_data[_reward_token].distributor = _distributor
▶Example▼
>>> LiquidityGaugeV6.set_reward_distributor('0xfe18aE03741a5b84e39C295Ac9C856eD7991C38e', '0xC56706334afE5a1638845ED9168E2ca3b3dbCCe7')
deposit_reward_token
LiquidityGaugeV6.deposit_reward_token(_reward_token: address, _amount: uint256, _epoch: uint256 = WEEK)This function can only be called by the manager of the gauge or the admin of the Factory.
Function to deposit a specific amount of reward tokens for a specified duration. If additional amounts of the same reward tokens are added, the leftover from the current distribution will be rolled over into the next distribution.
The gauge manager deposits 70 tokens right at the beginning of the week. Distribution payoff is the following:
tokens per second, which equals to 10 tokens per day for the next 7 days.
After six days, the gauge manager decides to add 70 additional tokens, again for a duration of 7 days. The leftover 10 tokens which have not yet been distributed are rolled into the next "distribution phase":
tokens per second, which equals to around 11.43 tokens per day for the next 7 days.
| Input | Type | Description |
|---|---|---|
_reward_token | address | Reward token to deposit. |
_amount | uint256 | Amount of reward tokens to deposit. |
_epoch | uint256 | Duration the rewards are distributed across, denominated in seconds. Defaults to a week (604800s). |
<>Source code▼
WEEK: constant(uint256) = 604800
@external
@nonreentrant("lock")
def deposit_reward_token(_reward_token: address, _amount: uint256, _epoch: uint256 = WEEK):
"""
@notice Deposit a reward token for distribution
@param _reward_token The reward token being deposited
@param _amount The amount of `_reward_token` being deposited
@param _epoch The duration the rewards are distributed across.
"""
assert msg.sender == self.reward_data[_reward_token].distributor
self._checkpoint_rewards(empty(address), self.totalSupply, False, empty(address))
# transferFrom reward token and use transferred amount henceforth:
amount_received: uint256 = ERC20(_reward_token).balanceOf(self)
assert ERC20(_reward_token).transferFrom(
msg.sender,
self,
_amount,
default_return_value=True
)
amount_received = ERC20(_reward_token).balanceOf(self) - amount_received
period_finish: uint256 = self.reward_data[_reward_token].period_finish
assert amount_received > _epoch # dev: rate will tend to zero!
if block.timestamp >= period_finish:
self.reward_data[_reward_token].rate = amount_received / _epoch
else:
remaining: uint256 = period_finish - block.timestamp
leftover: uint256 = remaining * self.reward_data[_reward_token].rate
self.reward_data[_reward_token].rate = (amount_received + leftover) / _epoch
self.reward_data[_reward_token].last_update = block.timestamp
self.reward_data[_reward_token].period_finish = block.timestamp + _epoch
▶Example▼
>>> LiquidityGaugeV6.deposit_reward_token('0xfe18aE03741a5b84e39C295Ac9C856eD7991C38e', 1000000000000000000, 604800)
Boosting Your LP Tokens
Provided liquidity is boosted by the veCRV balance of the user, allowing for boosts up to 2.5 times. Gauges measure liquidity with respect to the user's boost in the working_balances variable. The total liquidity deposited in the gauge is represented by the working_supply method.
The working_balances of a user and the total working_supply are adjusted via the internal _update_liquidity_limit function when the following actions occur:
- Transferring Tokens:
working_balancesare adjusted for both the sender and the receiver of the LP tokens. - Depositing LP Tokens into the Gauge: Adjusts the balance to reflect the new total.
- Withdrawing LP Tokens from the Gauge: Reduces the balance according to the amount withdrawn.
- Performing a Manual Checkpoint: Using the
user_checkpointfunction. - When a User is 'Kicked' for Abusing Their Boost: For more information on what constitutes abuse and the repercussions, see here.
▶_update_liquidity_limit▼
TOKENLESS_PRODUCTION: constant(uint256) = 40
@internal
def _update_liquidity_limit(addr: address, l: uint256, L: uint256):
"""
@notice Calculate limits which depend on the amount of CRV token per-user.
Effectively it calculates working balances to apply amplification
of CRV production by CRV
@param addr User address
@param l User's amount of liquidity (LP tokens)
@param L Total amount of liquidity (LP tokens)
"""
# To be called after totalSupply is updated
voting_balance: uint256 = VotingEscrowBoost(VEBOOST_PROXY).adjusted_balance_of(addr)
voting_total: uint256 = ERC20(VOTING_ESCROW).totalSupply()
lim: uint256 = l * TOKENLESS_PRODUCTION / 100
if voting_total > 0:
lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100
lim = min(l, lim)
old_bal: uint256 = self.working_balances[addr]
self.working_balances[addr] = lim
_working_supply: uint256 = self.working_supply + lim - old_bal
self.working_supply = _working_supply
log UpdateLiquidityLimit(addr, l, L, lim, _working_supply)
General formula for calculating the boost:
with:
| Variable | Description |
|---|---|
| User LP tokens deposited into the gauge.1 | |
| Total LP tokens deposited into the gauge. | |
| Users veCRV balance. | |
| Total veCRV balance. |
Let's examine two different users. Both are providing the same amount of LP tokens (same liquidity), but the first user does not receive a boost because he does not have any veCRV. The second user has a veCRV balance of 500. The total veCRV balance is assumed to be 10,000.
l = 1000 # users LP tokens in gauge
L = 50000 # total LP tokens in gauge
voting_balance_user1 = 0 # veCRV balance user1
voting_balance_user2 = 500 # veCRV balance user2
voting_total = 10000 # total veCRV balance
NO BOOST
Lets calculate the LP position of a user that has a vecrv balance of 0:
The working supply of this user is 400 LP tokens. The boost is calculated by:
BOOST
Lets calculate the LP position of a user that has a vecrv balance of 500 and therefore receives a boost on his provided liquidity:
The working supply of this user is 1000 LP tokens. The boost is calculated by:
working_balances
LiquidityGaugeV6.working_balances(arg0: address) -> uint256: viewGetter for the working balances of a user. This represents the effective liquidity of a user, which is used to calculate the CRV rewards they are entitled to. Essentially, it's the boosted balance of a user if they have some veCRV. If a user has no boost at all, their working_balance will be 40% of their LP tokens. If the position is fully boosted (2.5x), their working_balance will be equal to their LP tokens.
For example:
- 1 LP token with no boost =
working_balances(user) = 0.4 - 1 LP token with 1.5 boost =
working_balances(user) = 1.5 - 1 LP token with 2.5 boost =
working_balances(user) = 2.5
Returns: working balance (uint256).
| Input | Type | Description |
|---|---|---|
arg0 | address | Address to check the working balance for. |
<>Source code▼
TOKENLESS_PRODUCTION: constant(uint256) = 40
working_balances: public(HashMap[address, uint256])
@internal
def _update_liquidity_limit(addr: address, l: uint256, L: uint256):
"""
@notice Calculate limits which depend on the amount of CRV token per-user.
Effectively it calculates working balances to apply amplification
of CRV production by CRV
@param addr User address
@param l User's amount of liquidity (LP tokens)
@param L Total amount of liquidity (LP tokens)
"""
# To be called after totalSupply is updated
voting_balance: uint256 = VotingEscrowBoost(VEBOOST_PROXY).adjusted_balance_of(addr)
voting_total: uint256 = ERC20(VOTING_ESCROW).totalSupply()
lim: uint256 = l * TOKENLESS_PRODUCTION / 100
if voting_total > 0:
lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100
lim = min(l, lim)
old_bal: uint256 = self.working_balances[addr]
self.working_balances[addr] = lim
_working_supply: uint256 = self.working_supply + lim - old_bal
self.working_supply = _working_supply
log UpdateLiquidityLimit(addr, l, L, lim, _working_supply)
▶Example▼
>>> LiquidityGaugeV6.working_balances('0x989AEb4d175e16225E39E87d0D97A3360524AD80')
11470659994458155726
working_supply
LiquidityGaugeV6.working_supply() -> uint256: viewGetter for the working supply. This variale represents the sum of all working_balances of users who provided liquidity in the gauge.
Returns: working supply (uint256).
<>Source code▼
working_supply: public(uint256)
@internal
def _update_liquidity_limit(addr: address, l: uint256, L: uint256):
"""
@notice Calculate limits which depend on the amount of CRV token per-user.
Effectively it calculates working balances to apply amplification
of CRV production by CRV
@param addr User address
@param l User's amount of liquidity (LP tokens)
@param L Total amount of liquidity (LP tokens)
"""
# To be called after totalSupply is updated
voting_balance: uint256 = VotingEscrowBoost(VEBOOST_PROXY).adjusted_balance_of(addr)
voting_total: uint256 = ERC20(VOTING_ESCROW).totalSupply()
lim: uint256 = l * TOKENLESS_PRODUCTION / 100
if voting_total > 0:
lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100
lim = min(l, lim)
old_bal: uint256 = self.working_balances[addr]
self.working_balances[addr] = lim
_working_supply: uint256 = self.working_supply + lim - old_bal
self.working_supply = _working_supply
log UpdateLiquidityLimit(addr, l, L, lim, _working_supply)
▶Example▼
>>> LiquidityGaugeV6.working_supply()
12665099687428791483
Checkpoints
user_checkpoint
LiquidityGaugeV6.user_checkpoint(addr: address) -> boolThis function can only be called by the addr himself or the Minter.vy contract.
Function to record a checkpoint for addr.
Returns: True (bool).
| Input | Type | Description |
|---|---|---|
addr | address | Address who's checkpoint is recoreded. |
<>Source code▼
@external
def user_checkpoint(addr: address) -> bool:
"""
@notice Record a checkpoint for `addr`
@param addr User address
@return bool success
"""
assert msg.sender in [addr, MINTER] # dev: unauthorized
self._checkpoint(addr)
self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply)
return True
@internal
def _checkpoint(addr: address):
"""
@notice Checkpoint for a user
@dev Updates the CRV emissions a user is entitled to receive
@param addr User address
"""
_period: int128 = self.period
_period_time: uint256 = self.period_timestamp[_period]
_integrate_inv_supply: uint256 = self.integrate_inv_supply[_period]
inflation_params: uint256 = self.inflation_params
prev_future_epoch: uint256 = inflation_params >> 216
gauge_is_killed: bool = self.is_killed
rate: uint256 = inflation_params % 2 **216
new_rate: uint256 = rate
if gauge_is_killed:
rate = 0
new_rate = 0
if prev_future_epoch >= _period_time:
future_epoch_time_write: uint256 = CRV20(CRV).future_epoch_time_write()
if not gauge_is_killed:
new_rate = CRV20(CRV).rate()
self.inflation_params = (future_epoch_time_write << 216) + new_rate
# Update integral of 1/supply
if block.timestamp > _period_time:
_working_supply: uint256 = self.working_supply
Controller(GAUGE_CONTROLLER).checkpoint_gauge(self)
prev_week_time: uint256 = _period_time
week_time: uint256 = min((_period_time + WEEK) / WEEK * WEEK, block.timestamp)
for i in range(500):
dt: uint256 = week_time - prev_week_time
w: uint256 = Controller(GAUGE_CONTROLLER).gauge_relative_weight(self, prev_week_time)
if _working_supply > 0:
if prev_future_epoch >= prev_week_time and prev_future_epoch < week_time:
# If we went across one or multiple epochs, apply the rate
# of the first epoch until it ends, and then the rate of
# the last epoch.
# If more than one epoch is crossed - the gauge gets less,
# but that'd meen it wasn't called for more than 1 year
_integrate_inv_supply += rate * w * (prev_future_epoch - prev_week_time) / _working_supply
rate = new_rate
_integrate_inv_supply += rate * w * (week_time - prev_future_epoch) / _working_supply
else:
_integrate_inv_supply += rate * w * dt / _working_supply
# On precisions of the calculation
# rate ~= 10e18
# last_weight > 0.01 * 1e18 = 1e16 (if pool weight is 1%)
# _working_supply ~= TVL * 1e18 ~= 1e26 ($100M for example)
# The largest loss is at dt = 1
# Loss is 1e-9 - acceptable
if week_time == block.timestamp:
break
prev_week_time = week_time
week_time = min(week_time + WEEK, block.timestamp)
_period += 1
self.period = _period
self.period_timestamp[_period] = block.timestamp
self.integrate_inv_supply[_period] = _integrate_inv_supply
# Update user-specific integrals
_working_balance: uint256 = self.working_balances[addr]
self.integrate_fraction[addr] += _working_balance * (_integrate_inv_supply - self.integrate_inv_supply_of[addr]) / 10 ** 18
self.integrate_inv_supply_of[addr] = _integrate_inv_supply
self.integrate_checkpoint_of[addr] = block.timestamp
@internal
def _update_liquidity_limit(addr: address, l: uint256, L: uint256):
"""
@notice Calculate limits which depend on the amount of CRV token per-user.
Effectively it calculates working balances to apply amplification
of CRV production by CRV
@param addr User address
@param l User's amount of liquidity (LP tokens)
@param L Total amount of liquidity (LP tokens)
"""
# To be called after totalSupply is updated
voting_balance: uint256 = VotingEscrowBoost(VEBOOST_PROXY).adjusted_balance_of(addr)
voting_total: uint256 = ERC20(VOTING_ESCROW).totalSupply()
lim: uint256 = l * TOKENLESS_PRODUCTION / 100
if voting_total > 0:
lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100
lim = min(l, lim)
old_bal: uint256 = self.working_balances[addr]
self.working_balances[addr] = lim
_working_supply: uint256 = self.working_supply + lim - old_bal
self.working_supply = _working_supply
log UpdateLiquidityLimit(addr, l, L, lim, _working_supply)
▶Example▼
>>> LiquidityGaugeV6.user_checkpoint('0x989AEb4d175e16225E39E87d0D97A3360524AD80')
True
kick
LiquidityGaugeV6.kick(addr: address)Function to trigger a checkpoint for addr and therefore updating their boost. A user can only be kicked if they either had another voting event or their voting escrow lock expired. This function ensures no abusive usage of a boost.
Emits: UpdateLiquidityLimit
| Input | Type | Description |
|---|---|---|
addr | address | Address to kick. |
<>Source code▼
event UpdateLiquidityLimit:
user: indexed(address)
original_balance: uint256
original_supply: uint256
working_balance: uint256
working_supply: uint256
@external
def kick(addr: address):
"""
@notice Kick `addr` for abusing their boost
@dev Only if either they had another voting event, or their voting escrow lock expired
@param addr Address to kick
"""
t_last: uint256 = self.integrate_checkpoint_of[addr]
t_ve: uint256 = VotingEscrow(VOTING_ESCROW).user_point_history__ts(
addr, VotingEscrow(VOTING_ESCROW).user_point_epoch(addr)
)
_balance: uint256 = self.balanceOf[addr]
assert ERC20(VOTING_ESCROW).balanceOf(addr) == 0 or t_ve > t_last # dev: kick not allowed
assert self.working_balances[addr] > _balance * TOKENLESS_PRODUCTION / 100 # dev: kick not needed
self._checkpoint(addr)
self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply)
@internal
def _checkpoint(addr: address):
"""
@notice Checkpoint for a user
@dev Updates the CRV emissions a user is entitled to receive
@param addr User address
"""
_period: int128 = self.period
_period_time: uint256 = self.period_timestamp[_period]
_integrate_inv_supply: uint256 = self.integrate_inv_supply[_period]
inflation_params: uint256 = self.inflation_params
prev_future_epoch: uint256 = inflation_params >> 216
gauge_is_killed: bool = self.is_killed
rate: uint256 = inflation_params % 2 **216
new_rate: uint256 = rate
if gauge_is_killed:
rate = 0
new_rate = 0
if prev_future_epoch >= _period_time:
future_epoch_time_write: uint256 = CRV20(CRV).future_epoch_time_write()
if not gauge_is_killed:
new_rate = CRV20(CRV).rate()
self.inflation_params = (future_epoch_time_write << 216) + new_rate
# Update integral of 1/supply
if block.timestamp > _period_time:
_working_supply: uint256 = self.working_supply
Controller(GAUGE_CONTROLLER).checkpoint_gauge(self)
prev_week_time: uint256 = _period_time
week_time: uint256 = min((_period_time + WEEK) / WEEK * WEEK, block.timestamp)
for i in range(500):
dt: uint256 = week_time - prev_week_time
w: uint256 = Controller(GAUGE_CONTROLLER).gauge_relative_weight(self, prev_week_time)
if _working_supply > 0:
if prev_future_epoch >= prev_week_time and prev_future_epoch < week_time:
# If we went across one or multiple epochs, apply the rate
# of the first epoch until it ends, and then the rate of
# the last epoch.
# If more than one epoch is crossed - the gauge gets less,
# but that'd meen it wasn't called for more than 1 year
_integrate_inv_supply += rate * w * (prev_future_epoch - prev_week_time) / _working_supply
rate = new_rate
_integrate_inv_supply += rate * w * (week_time - prev_future_epoch) / _working_supply
else:
_integrate_inv_supply += rate * w * dt / _working_supply
# On precisions of the calculation
# rate ~= 10e18
# last_weight > 0.01 * 1e18 = 1e16 (if pool weight is 1%)
# _working_supply ~= TVL * 1e18 ~= 1e26 ($100M for example)
# The largest loss is at dt = 1
# Loss is 1e-9 - acceptable
if week_time == block.timestamp:
break
prev_week_time = week_time
week_time = min(week_time + WEEK, block.timestamp)
_period += 1
self.period = _period
self.period_timestamp[_period] = block.timestamp
self.integrate_inv_supply[_period] = _integrate_inv_supply
# Update user-specific integrals
_working_balance: uint256 = self.working_balances[addr]
self.integrate_fraction[addr] += _working_balance * (_integrate_inv_supply - self.integrate_inv_supply_of[addr]) / 10 ** 18
self.integrate_inv_supply_of[addr] = _integrate_inv_supply
self.integrate_checkpoint_of[addr] = block.timestamp
@internal
def _update_liquidity_limit(addr: address, l: uint256, L: uint256):
"""
@notice Calculate limits which depend on the amount of CRV token per-user.
Effectively it calculates working balances to apply amplification
of CRV production by CRV
@param addr User address
@param l User's amount of liquidity (LP tokens)
@param L Total amount of liquidity (LP tokens)
"""
# To be called after totalSupply is updated
voting_balance: uint256 = VotingEscrowBoost(VEBOOST_PROXY).adjusted_balance_of(addr)
voting_total: uint256 = ERC20(VOTING_ESCROW).totalSupply()
lim: uint256 = l * TOKENLESS_PRODUCTION / 100
if voting_total > 0:
lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100
lim = min(l, lim)
old_bal: uint256 = self.working_balances[addr]
self.working_balances[addr] = lim
_working_supply: uint256 = self.working_supply + lim - old_bal
self.working_supply = _working_supply
log UpdateLiquidityLimit(addr, l, L, lim, _working_supply)
▶Example▼
>>> LiquidityGaugeV6.kick('0x989AEb4d175e16225E39E87d0D97A3360524AD80')
Killing Gauges
Liquidity gauges have a "killed status" stored in the is_killed variable. This status can be set by the admin of the Factory, which was used to initially deploy the gauge, using the set_killed function. If the status is set to True, the gauges' rate and future_rate will be set to zero, and it will not be eligible to receive any more CRV emissions.
"Killing a gauge" can be undone by simply setting the is_killed status back to false using the set_killed function again.
"Killing a gauge" affects only CRV emissions; externally added rewards will still be distributed.
is_killed
LiquidityGaugeV6.is_killed() -> bool: viewGetter function to check if the gauge is killed. If true, the inflation rate for the gauge will be set to zero.
Returns: killed status (bool).
<>Source code▼
is_killed: public(bool)
@internal
def _checkpoint(addr: address):
"""
@notice Checkpoint for a user
@dev Updates the CRV emissions a user is entitled to receive
@param addr User address
"""
_period: int128 = self.period
_period_time: uint256 = self.period_timestamp[_period]
_integrate_inv_supply: uint256 = self.integrate_inv_supply[_period]
inflation_params: uint256 = self.inflation_params
prev_future_epoch: uint256 = inflation_params >> 216
gauge_is_killed: bool = self.is_killed
rate: uint256 = inflation_params % 2 **216
new_rate: uint256 = rate
if gauge_is_killed:
rate = 0
new_rate = 0
...
▶Example▼
>>> LiquidityGaugeV6.is_killed()
'False'
set_killed
LiquidityGaugeV6.set_killed(_is_killed: bool)This function can only be called by the owner of the Factory.
Function to kill a gauge.
Emits: SetKilled event.
| Input | Type | Description |
|---|---|---|
_is_killed | bool | Status to set the killed status to. |
<>Source code▼
is_killed: public(bool)
@external
def set_killed(_is_killed: bool):
"""
@notice Set the killed status for this contract
@dev When killed, the gauge always yields a rate of 0 and so cannot mint CRV
@param _is_killed Killed status to set
"""
assert msg.sender == Factory(self.factory).admin() # dev: only owner
self.is_killed = _is_killed
▶Example▼
>>> LiquidityGaugeV6.set_killed(True)
Contract Info Methods
Basic contract informations:
integrate_fraction
LiquidityGaugeV6.integrate_fraction(arg0: address) -> uint256: viewGetter for the total amount of CRV, both mintable and already minted, that has been allocated to arg0 from this gauge.
Returns: integral of accrued rewards (uint256).
| Input | Type | Description |
|---|---|---|
arg0 | address | Address to check for. |
<>Source code▼
integrate_fraction: public(HashMap[address, uint256])
▶Example▼
>>> LiquidityGaugeV6.integrate_fraction('0x989AEb4d175e16225E39E87d0D97A3360524AD80')
1662908936954145
period
LiquidityGaugeV6.period() -> int128: viewGetter for the period of the gauge. This variable is incremented by one each time a checkpoint was made.
Returns: current period (int128).
<>Source code▼
period: public(int128)
▶Example▼
>>> LiquidityGaugeV6.period()
6
period_timestamp
LiquidityGaugeV6.period_timestamp(arg0: uint256) -> uint256: viewGetter for the timestamp of a period.
Returns: timestamp
| Input | Type | Description |
|---|---|---|
arg0 | uint256 | Period to get the timestamp for. |
<>Source code▼
period_timestamp: public(uint256[100000000000000000000000000000])
▶Example▼
>>> LiquidityGaugeV6.period_timestamp(7)
1713351359
inflation_rate
LiquidityGaugeV6.inflation_rate() -> uint256: viewGetter for the current inflation rate per second of CRV. This getter retrieves the lower 216 bits of inflation_params, which stores the inflation rate and the future epoch time.
Returns: CRV inflation rate (uint256).
<>Source code▼
inflation_params: uint256
@view
@external
def inflation_rate() -> uint256:
"""
@notice Get the locally stored CRV inflation rate
"""
return self.inflation_params % 2 **216
▶Example▼
>>> LiquidityGaugeV6.inflation_rate()
5181574864521283150 # 5.18157486452 CRV per second
future_epoch_time
LiquidityGaugeV6.future_epoch_time() -> uint256: viewGetter for the future epoch time. This getter retrieves the upper 216 bits of inflation_params, which stores the inflation rate and the future epoch time.
Returns: future epoch time (uint256).
<>Source code▼
inflation_params: uint256
@view
@external
def future_epoch_time() -> uint256:
"""
@notice Get the locally stored CRV future epoch start time
"""
return self.inflation_params >> 216
▶Example▼
>>> LiquidityGaugeV6.future_epoch_time()
1723501048
factory
LiquidityGaugeV6.factory() -> address: viewGetter for the factory which deployed the gauge.
Returns: factory (address).
<>Source code▼
factory: public(address)
@external
def __init__(_lp_token: address):
"""
@notice Contract constructor
@param _lp_token Liquidity Pool contract address
"""
self.lp_token = _lp_token
self.factory = msg.sender
self.manager = tx.origin
...
▶Example▼
>>> LiquidityGaugeV6.factory()
'0x98EE851a00abeE0d95D08cF4CA2BdCE32aeaAF7F'
lp_token
LiquidityGaugeV6.lp_token() -> address: viewGetter for the LP token which is deposited into of withdrawn from the gauge.
Returns: LP token (address).
<>Source code▼
lp_token: public(address)
@external
def __init__(_lp_token: address):
"""
@notice Contract constructor
@param _lp_token Liquidity Pool contract address
"""
self.lp_token = _lp_token
self.factory = msg.sender
self.manager = tx.origin
...
▶Example▼
>>> LiquidityGaugeV6.lp_token()
'0x86EA1191a219989d2dA3a85c949a12A92f8ED3Db'