gauge

Created Diff never expires
36 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
697 lines
31 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
692 lines
# @version 0.2.12
# @version 0.2.15
"""
"""
@title Liquidity Gauge v3
@title Liquidity Gauge v3
@author Curve Finance
@author This version was modified starting from Curve Finance's DAO contracts
@license MIT
@license MIT
"""
"""


from vyper.interfaces import ERC20
from vyper.interfaces import ERC20


implements: ERC20
implements: ERC20




interface CRV20:
interface Distributor:
def future_epoch_time_write() -> uint256: nonpayable
def futureEpochTimeWrite() -> uint256: nonpayable
def rate() -> uint256: view
def rate() -> uint256: view


interface Controller:
interface Controller:
def period() -> int128: view
def period() -> int128: view
def period_write() -> int128: nonpayable
def period_write() -> int128: nonpayable
def period_timestamp(p: int128) -> uint256: view
def period_timestamp(p: int128) -> uint256: view
def gauge_relative_weight(addr: address, time: uint256) -> uint256: view
def gauge_relative_weight(addr: address, time: uint256) -> uint256: view
def voting_escrow() -> address: view
def voting_escrow() -> address: view
def checkpoint(): nonpayable
def checkpoint(): nonpayable
def checkpoint_gauge(addr: address): nonpayable
def checkpoint_gauge(addr: address): nonpayable


interface Minter:
interface DistributorProxy:
def token() -> address: view
def distributor() -> address: view
def controller() -> address: view
def controller() -> address: view
def minted(user: address, gauge: address) -> uint256: view
def distributed(user: address, gauge: address) -> uint256: view


interface VotingEscrow:
interface VotingEscrow:
def user_point_epoch(addr: address) -> uint256: view
def user_point_epoch(addr: address) -> uint256: view
def user_point_history__ts(addr: address, epoch: uint256) -> uint256: view
def user_point_history__ts(addr: address, epoch: uint256) -> uint256: view


interface ERC20Extended:
interface ERC20Extended:
def symbol() -> String[26]: view
def symbol() -> String[26]: view




event Deposit:
event Deposit:
provider: indexed(address)
provider: indexed(address)
value: uint256
value: uint256


event Withdraw:
event Withdraw:
provider: indexed(address)
provider: indexed(address)
value: uint256
value: uint256


event UpdateLiquidityLimit:
event UpdateLiquidityLimit:
user: address
user: address
original_balance: uint256
original_balance: uint256
original_supply: uint256
original_supply: uint256
working_balance: uint256
working_balance: uint256
working_supply: uint256
working_supply: uint256


event CommitOwnership:
event CommitOwnership:
admin: address
admin: address


event ApplyOwnership:
event ApplyOwnership:
admin: address
admin: address


event Transfer:
event Transfer:
_from: indexed(address)
_from: indexed(address)
_to: indexed(address)
_to: indexed(address)
_value: uint256
_value: uint256


event Approval:
event Approval:
_owner: indexed(address)
_owner: indexed(address)
_spender: indexed(address)
_spender: indexed(address)
_value: uint256
_value: uint256




MAX_REWARDS: constant(uint256) = 8
MAX_REWARDS: constant(uint256) = 8
TOKENLESS_PRODUCTION: constant(uint256) = 40
TOKENLESS_PRODUCTION: constant(uint256) = 40
WEEK: constant(uint256) = 604800
WEEK: constant(uint256) = 604800
CLAIM_FREQUENCY: constant(uint256) = 3600
CLAIM_FREQUENCY: constant(uint256) = 3600


minter: public(address)
distributor_proxy: public(address)
crv_token: public(address)
distributor: public(address)
lp_token: public(address)
lp_token: public(address)
controller: public(address)
controller: public(address)
voting_escrow: public(address)
voting_escrow: public(address)
future_epoch_time: public(uint256)
future_epoch_time: public(uint256)


balanceOf: public(HashMap[address, uint256])
balanceOf: public(HashMap[address, uint256])
totalSupply: public(uint256)
totalSupply: public(uint256)
allowance: public(HashMap[address, HashMap[address, uint256]])
allowance: public(HashMap[address, HashMap[address, uint256]])


name: public(String[64])
name: public(String[64])
symbol: public(String[32])
symbol: public(String[32])


working_balances: public(HashMap[address, uint256])
working_balances: public(HashMap[address, uint256])
working_supply: public(uint256)
working_supply: public(uint256)


# The goal is to be able to calculate ∫(rate * balance / totalSupply dt) from 0 till checkpoint
# The goal is to be able to calculate ∫(rate * balance / totalSupply dt) from 0 till checkpoint
# All values are kept in units of being multiplied by 1e18
# All values are kept in units of being multiplied by 1e18
period: public(int128)
period: public(int128)
period_timestamp: public(uint256[100000000000000000000000000000])
period_timestamp: public(uint256[100000000000000000000000000000])


# 1e18 * ∫(rate(t) / totalSupply(t) dt) from 0 till checkpoint
# 1e18 * ∫(rate(t) / totalSupply(t) dt) from 0 till checkpoint
integrate_inv_supply: public(uint256[100000000000000000000000000000]) # bump epoch when rate() changes
integrate_inv_supply: public(uint256[100000000000000000000000000000]) # bump epoch when rate() changes


# 1e18 * ∫(rate(t) / totalSupply(t) dt) from (last_action) till checkpoint
# 1e18 * ∫(rate(t) / totalSupply(t) dt) from (last_action) till checkpoint
integrate_inv_supply_of: public(HashMap[address, uint256])
integrate_inv_supply_of: public(HashMap[address, uint256])
integrate_checkpoint_of: public(HashMap[address, uint256])
integrate_checkpoint_of: public(HashMap[address, uint256])


# ∫(balance * rate(t) / totalSupply(t) dt) from 0 till checkpoint
# ∫(balance * rate(t) / totalSupply(t) dt) from 0 till checkpoint
# Units: rate * t = already number of coins per address to issue
# Units: rate * t = already number of coins per address to issue
integrate_fraction: public(HashMap[address, uint256])
integrate_fraction: public(HashMap[address, uint256])


inflation_rate: public(uint256)
inflation_rate: public(uint256)


# For tracking external rewards
# For tracking external rewards
reward_data: uint256
reward_data: uint256
reward_tokens: public(address[MAX_REWARDS])
reward_tokens: public(address[MAX_REWARDS])


# deposit / withdraw / claim
# deposit / withdraw / claim
reward_sigs: bytes32
reward_sigs: bytes32


# claimant -> default reward receiver
# claimant -> default reward receiver
rewards_receiver: public(HashMap[address, address])
rewards_receiver: public(HashMap[address, address])


# reward token -> integral
# reward token -> integral
reward_integral: public(HashMap[address, uint256])
reward_integral: public(HashMap[address, uint256])


# reward token -> claiming address -> integral
# reward token -> claiming address -> integral
reward_integral_for: public(HashMap[address, HashMap[address, uint256]])
reward_integral_for: public(HashMap[address, HashMap[address, uint256]])


# user -> [uint128 claimable amount][uint128 claimed amount]
# user -> [uint128 claimable amount][uint128 claimed amount]
claim_data: HashMap[address, HashMap[address, uint256]]
claim_data: HashMap[address, HashMap[address, uint256]]


admin: public(address)
admin: public(address)
future_admin: public(address) # Can and will be a smart contract
future_admin: public(address) # Can and will be a smart contract
is_killed: public(bool)
is_killed: public(bool)




@external
@external
def __init__(_lp_token: address, _minter: address, _admin: address):
def __init__(_lp_token: address, _distributor_proxy: address, _admin: address):
"""
"""
@notice Contract constructor
@notice Contract constructor
@param _lp_token Liquidity Pool contract address
@param _lp_token Liquidity Pool contract address
@param _minter Minter contract address
@param _distributor_proxy DistributorProxy contract address
@param _admin Admin who can kill the gauge
@param _admin Admin who can kill the gauge
"""
"""


symbol: String[26] = ERC20Extended(_lp_token).symbol()
symbol: String[26] = ERC20Extended(_lp_token).symbol()
self.name = concat("Curve.fi ", symbol, " Gauge Deposit")
self.name = concat(symbol, " Gauge Deposit")
self.symbol = concat(symbol, "-gauge")
self.symbol = concat(symbol, "-gauge")


crv_token: address = Minter(_minter).token()
distributor: address = DistributorProxy(_distributor_proxy).distributor()
controller: address = Minter(_minter).controller()
controller: address = DistributorProxy(_distributor_proxy).controller()


self.lp_token = _lp_token
self.lp_token = _lp_token
self.minter = _minter
self.distributor_proxy = _distributor_proxy
self.admin = _admin
self.admin = _admin
self.crv_token = crv_token
self.distributor = distributor
self.controller = controller
self.controller = controller
self.voting_escrow = Controller(controller).voting_escrow()
self.voting_escrow = Controller(controller).voting_escrow()


self.period_timestamp[0] = block.timestamp
self.period_timestamp[0] = block.timestamp
self.inflation_rate = CRV20(crv_token).rate()
self.inflation_rate = Distributor(distributor).rate()
self.future_epoch_time = CRV20(crv_token).future_epoch_time_write()
self.future_epoch_time = Distributor(distributor).futureEpochTimeWrite()




@view
@view
@external
@external
def decimals() -> uint256:
def decimals() -> uint256:
"""
"""
@notice Get the number of decimals for this token
@notice Get the number of decimals for this token
@dev Implemented as a view method to reduce gas costs
@dev Implemented as a view method to reduce gas costs
@return uint256 decimal places
@return uint256 decimal places
"""
"""
return 18
return 18




@view
@view
@external
@external
def integrate_checkpoint() -> uint256:
def integrate_checkpoint() -> uint256:
return self.period_timestamp[self.period]
return self.period_timestamp[self.period]




@internal
@internal
def _update_liquidity_limit(addr: address, l: uint256, L: uint256):
def _update_liquidity_limit(addr: address, l: uint256, L: uint256):
"""
"""
@notice Calculate limits which depend on the amount of CRV token per-user.
@notice Calculate limits which depend on the amount of IDLE token per-user.
Effectively it calculates working balances to apply amplification
Effectively it calculates working balances to apply amplification
of CRV production by CRV
of IDLE distribution by `distributor`
@param addr User address
@param addr User address
@param l User's amount of liquidity (LP tokens)
@param l User's amount of liquidity (LP tokens)
@param L Total amount of liquidity (LP tokens)
@param L Total amount of liquidity (LP tokens)
"""
"""
# To be called after totalSupply is updated
# To be called after totalSupply is updated
_voting_escrow: address = self.voting_escrow
_voting_escrow: address = self.voting_escrow
voting_balance: uint256 = ERC20(_voting_escrow).balanceOf(addr)
voting_balance: uint256 = ERC20(_voting_escrow).balanceOf(addr)
voting_total: uint256 = ERC20(_voting_escrow).totalSupply()
voting_total: uint256 = ERC20(_voting_escrow).totalSupply()


lim: uint256 = l * TOKENLESS_PRODUCTION / 100
lim: uint256 = l * TOKENLESS_PRODUCTION / 100
if voting_total > 0:
if voting_total > 0:
lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100
lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100


lim = min(l, lim)
lim = min(l, lim)
old_bal: uint256 = self.working_balances[addr]
old_bal: uint256 = self.working_balances[addr]
self.working_balances[addr] = lim
self.working_balances[addr] = lim
_working_supply: uint256 = self.working_supply + lim - old_bal
_working_supply: uint256 = self.working_supply + lim - old_bal
self.working_supply = _working_supply
self.working_supply = _working_supply


log UpdateLiquidityLimit(addr, l, L, lim, _working_supply)
log UpdateLiquidityLimit(addr, l, L, lim, _working_supply)




@internal
@internal
def _checkpoint_rewards( _user: address, _total_supply: uint256, _claim: bool, _receiver: address):
def _checkpoint_rewards( _user: address, _total_supply: uint256, _claim: bool, _receiver: address):
"""
"""
@notice Claim pending rewards and checkpoint rewards for a user
@notice Claim pending rewards and checkpoint rewards for a user
"""
"""
# load reward tokens and integrals into memory
# load reward tokens and integrals into memory
reward_tokens: address[MAX_REWARDS] = empty(address[MAX_REWARDS])
reward_tokens: address[MAX_REWARDS] = empty(address[MAX_REWARDS])
reward_integrals: uint256[MAX_REWARDS] = empty(uint256[MAX_REWARDS])
reward_integrals: uint256[MAX_REWARDS] = empty(uint256[MAX_REWARDS])
for i in range(MAX_REWARDS):
for i in range(MAX_REWARDS):
token: address = self.reward_tokens[i]
token: address = self.reward_tokens[i]
if token == ZERO_ADDRESS:
if token == ZERO_ADDRESS:
break
break
reward_tokens[i] = token
reward_tokens[i] = token
reward_integrals[i] = self.reward_integral[token]
reward_integrals[i] = self.reward_integral[token]


reward_data: uint256 = self.reward_data
reward_data: uint256 = self.reward_data
if _total_supply != 0 and reward_data != 0 and block.timestamp > shift(reward_data, -160) + CLAIM_FREQUENCY:
if _total_supply != 0 and reward_data != 0 and block.timestamp > shift(reward_data, -160) + CLAIM_FREQUENCY:
# track balances prior to claiming
# track balances prior to claiming
reward_balances: uint256[MAX_REWARDS] = empty(uint256[MAX_REWARDS])
reward_balances: uint256[MAX_REWARDS] = empty(uint256[MAX_REWARDS])
for i in range(MAX_REWARDS):
for i in range(MAX_REWARDS):
token: address = self.reward_tokens[i]
token: address = self.reward_tokens[i]
if token == ZERO_ADDRESS:
if token == ZERO_ADDRESS:
break
break
reward_balances[i] = ERC20(token).balanceOf(self)
reward_balances[i] = ERC20(token).balanceOf(self)


# claim from reward contract
# claim from reward contract
reward_contract: address = convert(reward_data % 2**160, address)
reward_contract: address = convert(reward_data % 2**160, address)
raw_call(reward_contract, slice(self.reward_sigs, 8, 4)) # dev: bad claim sig
raw_call(reward_contract, slice(self.reward_sigs, 8, 4)) # dev: bad claim sig
self.reward_data = convert(reward_contract, uint256) + shift(block.timestamp, 160)
self.reward_data = convert(reward_contract, uint256) + shift(block.timestamp, 160)


# get balances after claim and calculate new reward integrals
# get balances after claim and calculate new reward integrals
for i in range(MAX_REWARDS):
for i in range(MAX_REWARDS):
token: address = reward_tokens[i]
token: address = reward_tokens[i]
if token == ZERO_ADDRESS:
if token == ZERO_ADDRESS:
break
break
dI: uint256 = 10**18 * (ERC20(token).balanceOf(self) - reward_balances[i]) / _total_supply
dI: uint256 = 10**18 * (ERC20(token).balanceOf(self) - reward_balances[i]) / _total_supply
if dI > 0:
if dI > 0:
reward_integrals[i] += dI
reward_integrals[i] += dI
self.reward_integral[token] = reward_integrals[i]
self.reward_integral[token] = reward_integrals[i]


if _user != ZERO_ADDRESS:
if _user != ZERO_ADDRESS:


receiver: address = _receiver
receiver: address = _receiver
if _claim and receiver == ZERO_ADDRESS:
if _claim and receiver == ZERO_ADDRESS:
# if receiver is not explicitly declared, check for default receiver
# if receiver is not explicitly declared, check for default receiver
receiver = self.rewards_receiver[_user]
receiver = self.rewards_receiver[_user]
if receiver == ZERO_ADDRESS:
if receiver == ZERO_ADDRESS:
# direct claims to user if no default receiver is set
# direct claims to user if no default receiver is set
receiver = _user
receiver = _user


# calculate new user reward integral and transfer any owed rewards
# calculate new user reward integral and transfer any owed rewards
user_balance: uint256 = self.balanceOf[_user]
user_balance: uint256 = self.balanceOf[_user]
for i in range(MAX_REWARDS):
for i in range(MAX_REWARDS):
token: address = reward_tokens[i]
token: address = reward_tokens[i]
if token == ZERO_ADDRESS:
if token == ZERO_ADDRESS:
break
break


integral: uint256 = reward_integrals[i]
integral: uint256 = reward_integrals[i]
integral_for: uint256 = self.reward_integral_for[token][_user]
integral_for: uint256 = self.reward_integral_for[token][_user]
new_claimable: uint256 = 0
new_claimable: uint256 = 0
if integral_for < integral:
if integral_for < integral:
self.reward_integral_for[token][_user] = integral
self.reward_integral_for[token][_user] = integral
new_claimable = user_balance * (integral - integral_for) / 10**18
new_claimable = user_balance * (integral - integral_for) / 10**18


claim_data: uint256 = self.claim_data[_user][token]
claim_data: uint256 = self.claim_data[_user][token]
total_claimable: uint256 = shift(claim_data, -128) + new_claimable
total_claimable: uint256 = shift(claim_data, -128) + new_claimable
if total_claimable > 0:
if total_claimable > 0:
total_claimed: uint256 = claim_data % 2 ** 128
total_claimed: uint256 = claim_data % 2 ** 128
if _claim:
if _claim:
response: Bytes[32] = raw_call(
response: Bytes[32] = raw_call(
token,
token,
concat(
concat(
method_id("transfer(address,uint256)"),
method_id("transfer(address,uint256)"),
convert(receiver, bytes32),
convert(receiver, bytes32),
convert(total_claimable, bytes32),
convert(total_claimable, bytes32),
),
),
max_outsize=32,
max_outsize=32,
)
)
if len(response) != 0:
if len(response) != 0:
assert convert(response, bool)
assert convert(response, bool)
# update amount claimed (lower order bytes)
# update amount claimed (lower order bytes)
self.claim_data[_user][token] = total_claimed + total_claimable
self.claim_data[_user][token] = total_claimed + total_claimable
elif new_claimable > 0:
elif new_claimable > 0:
# update total_claimable (higher order bytes)
# update total_claimable (higher order bytes)
self.claim_data[_user][token] = total_claimed + shift(total_claimable, 128)
self.claim_data[_user][token] = total_claimed + shift(total_claimable, 128)




@internal
@internal
def _checkpoint(addr: address):
def _checkpoint(addr: address):
"""
"""
@notice Checkpoint for a user
@notice Checkpoint for a user
@param addr User address
@param addr User address
"""
"""
_period: int128 = self.period
_period: int128 = self.period
_period_time: uint256 = self.period_timestamp[_period]
_period_time: uint256 = self.period_timestamp[_period]
_integrate_inv_supply: uint256 = self.integrate_inv_supply[_period]
_integrate_inv_supply: uint256 = self.integrate_inv_supply[_period]
rate: uint256 = self.inflation_rate
rate: uint256 = self.inflation_rate
new_rate: uint256 = rate
new_rate: uint256 = rate
prev_future_epoch: uint256 = self.future_epoch_time
prev_future_epoch: uint256 = self.future_epoch_time
if prev_future_epoch >= _period_time:
if prev_future_epoch >= _period_time:
_token: address = self.crv_token
_distributor: address = self.distributor
self.future_epoch_time = CRV20(_token).future_epoch_time_write()
self.future_epoch_time = Distributor(_distributor).futureEpochTimeWrite()
new_rate = CRV20(_token).rate()
new_rate = Distributor(_distributor).rate()
self.inflation_rate = new_rate
self.inflation_rate = new_rate


if self.is_killed:
if self.is_killed:
# Stop distributing inflation as soon as killed
# Stop distributing inflation as soon as killed
rate = 0
rate = 0


# Update integral of 1/supply
# Update integral of 1/supply
if block.timestamp > _period_time:
if block.timestamp > _period_time:
_working_supply: uint256 = self.working_supply
_working_supply: uint256 = self.working_supply
_controller: address = self.controller
_controller: address = self.controller
Controller(_controller).checkpoint_gauge(self)
Controller(_controller).checkpoint_gauge(self)
prev_week_time: uint256 = _period_time
prev_week_time: uint256 = _period_time
week_time: uint256 = min((_period_time + WEEK) / WEEK * WEEK, block.timestamp)
week_time: uint256 = min((_period_time + WEEK) / WEEK * WEEK, block.timestamp)


for i in range(500):
for i in range(500):
dt: uint256 = week_time - prev_week_time
dt: uint256 = week_time - prev_week_time
w: uint256 = Controller(_controller).gauge_relative_weight(self, prev_week_time / WEEK * WEEK)
w: uint256 = Controller(_controller).gauge_relative_weight(self, prev_week_time / WEEK * WEEK)


if _working_supply > 0:
if _working_supply > 0:
if prev_future_epoch >= prev_week_time and prev_future_epoch < week_time:
if prev_future_epoch >= prev_week_time and prev_future_epoch < week_time:
# If we went across one or multiple epochs, apply the rate
# If we went across one or multiple epochs, apply the rate
# of the first epoch until it ends, and then the rate of
# of the first epoch until it ends, and then the rate of
# the last epoch.
# the last epoch.
# If more than one epoch is crossed - the gauge gets less,
# If more than one epoch is crossed - the gauge gets less,
# but that'd meen it wasn't called for more than 1 year
# 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
_integrate_inv_supply += rate * w * (prev_future_epoch - prev_week_time) / _working_supply
rate = new_rate
rate = new_rate
_integrate_inv_supply += rate * w * (week_time - prev_future_epoch) / _working_supply
_integrate_inv_supply += rate * w * (week_time - prev_future_epoch) / _working_supply
else:
else:
_integrate_inv_supply += rate * w * dt / _working_supply
_integrate_inv_supply += rate * w * dt / _working_supply
# On precisions of the calculation
# On precisions of the calculation
# rate ~= 10e18
# rate ~= 10e18
# last_weight > 0.01 * 1e18 = 1e16 (if pool weight is 1%)
# last_weight > 0.01 * 1e18 = 1e16 (if pool weight is 1%)
# _working_supply ~= TVL * 1e18 ~= 1e26 ($100M for example)
# _working_supply ~= TVL * 1e18 ~= 1e26 ($100M for example)
# The largest loss is at dt = 1
# The largest loss is at dt = 1
# Loss is 1e-9 - acceptable
# Loss is 1e-9 - acceptable


if week_time == block.timestamp:
if week_time == block.timestamp:
break
break
prev_week_time = week_time
prev_week_time = week_time
week_time = min(week_time + WEEK, block.timestamp)
week_time = min(week_time + WEEK, block.timestamp)


_period += 1
_period += 1
self.period = _period
self.period = _period
self.period_timestamp[_period] = block.timestamp
self.period_timestamp[_period] = block.timestamp
self.integrate_inv_supply[_period] = _integrate_inv_supply
self.integrate_inv_supply[_period] = _integrate_inv_supply


# Update user-specific integrals
# Update user-specific integrals
_working_balance: uint256 = self.working_balances[addr]
_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_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_inv_supply_of[addr] = _integrate_inv_supply
self.integrate_checkpoint_of[addr] = block.timestamp
self.integrate_checkpoint_of[addr] = block.timestamp




@external
@external
def user_checkpoint(addr: address) -> bool:
def user_checkpoint(addr: address) -> bool:
"""
"""
@notice Record a checkpoint for `addr`
@notice Record a checkpoint for `addr`
@param addr User address
@param addr User address
@return bool success
@return bool success
"""
"""
assert (msg.sender == addr) or (msg.sender == self.minter) # dev: unauthorized
assert (msg.sender == addr) or (msg.sender == self.distributor_proxy) # dev: unauthorized
self._checkpoint(addr)
self._checkpoint(addr)
self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply)
self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply)
return True
return True




@external
@external
def claimable_tokens(addr: address) -> uint256:
def claimable_tokens(addr: address) -> uint256:
"""
"""
@notice Get the number of claimable tokens per user
@notice Get the number of claimable tokens per user
@dev This function should be manually changed to "view" in the ABI
@dev This function should be manually changed to "view" in the ABI
@return uint256 number of claimable tokens per user
@return uint256 number of claimable tokens per user
"""
"""
self._checkpoint(addr)
self._checkpoint(addr)
return self.integrate_fraction[addr] - Minter(self.minter).minted(addr, self)
return self.integrate_fraction[addr] - DistributorProxy(self.distributor_proxy).distributed(addr, self)




@view
@view
@external
@external
def reward_contract() -> address:
def reward_contract() -> address:
"""
"""
@notice Address of the reward contract providing non-CRV incentives for this gauge
@notice Address of the reward contract providing non-IDLE incentives for this gauge
@dev Returns `ZERO_ADDRESS` if there is no reward contract active
@dev Returns `ZERO_ADDRESS` if there is no reward contract active
"""
"""
return convert(self.reward_data % 2**160, address)
return convert(self.reward_data % 2**160, address)




@view
@view
@external
@external
def last_claim() -> uint256:
def last_claim() -> uint256:
"""
"""
@notice Epoch timestamp of the last call to claim from `reward_contract`
@notice Epoch timestamp of the last call to claim from `reward_contract`
@dev Rewards are claimed at most once per hour in order to reduce gas costs
@dev Rewards are claimed at most once per hour in order to reduce gas costs
"""
"""
return shift(self.reward_data, -160)
return shift(self.reward_data, -160)




@view
@view
@external
@external
def claimed_reward(_addr: address, _token: address) -> uint256:
def claimed_reward(_addr: address, _token: address) -> uint256:
"""
"""
@notice Get the number of already-claimed reward tokens for a user
@notice Get the number of already-claimed reward tokens for a user
@param _addr Account to get reward amount for
@param _addr Account to get reward amount for
@param _token Token to get reward amount for
@param _token Token to get reward amount for
@return uint256 Total amount of `_token` already claimed by `_addr`
@return uint256 Total amount of `_token` already claimed by `_addr`
"""
"""
return self.claim_data[_addr][_token] % 2**128
return self.claim_data[_addr][_token] % 2**128




@view
@view
@external
@external
def claimable_reward(_addr: address, _token: address) -> uint256:
def claimable_reward(_addr: address, _token: address) -> uint256:
"""
"""
@notice Get the number of claimable reward tokens for a user
@notice Get the number of claimable reward tokens for a user
@dev This call does not consider pending claimable amount in `reward_contract`.
@dev This call does not consider pending claimable amount in `reward_contract`.
Off-chain callers should instead use `claimable_rewards_write` as a
Off-chain callers should instead use `claimable_rewards_write` as a
view method.
view method.
@param _addr Account to get reward amount for
@param _addr Account to get reward amount for
@param _token Token to get reward amount for
@param _token Token to get reward amount for
@return uint256 Claimable reward token amount
@return uint256 Claimable reward token amount
"""
"""
return shift(self.claim_data[_addr][_token], -128)
return shift(self.claim_data[_addr][_token], -128)




@external
@external
@nonreentrant('lock')
@nonreentrant('lock')
def claimable_reward_write(_addr: address, _token: address) -> uint256:
def claimable_reward_write(_addr: address, _token: address) -> uint256:
"""
"""
@notice Get the number of claimable reward tokens for a user
@notice Get the number of claimable reward tokens for a user
@dev This function should be manually changed to "view" in the ABI
@dev This function should be manually changed to "view" in the ABI
Calling it via a transaction will claim available reward tokens
Calling it via a transaction will claim available reward tokens
@param _addr Account to get reward amount for
@param _addr Account to get reward amount for
@param _token Token to get reward amount for
@param _token Token to get reward amount for
@return uint256 Claimable reward token amount
@return uint256 Claimable reward token amount
"""
"""
if self.reward_tokens[0] != ZERO_ADDRESS:
if self.reward_tokens[0] != ZERO_ADDRESS:
self._checkpoint_rewards(_addr, self.totalSupply, False, ZERO_ADDRESS)
self._checkpoint_rewards(_addr, self.totalSupply, False, ZERO_ADDRESS)
return shift(self.claim_data[_addr][_token], -128)
return shift(self.claim_data[_addr][_token], -128)




@external
@external
def set_rewards_receiver(_receiver: address):
def set_rewards_receiver(_receiver: address):
"""
"""
@notice Set the default reward receiver for the caller.
@notice Set the default reward receiver for the caller.
@dev When set to ZERO_ADDRESS, rewards are sent to the caller
@dev When set to ZERO_ADDRESS, rewards are sent to the caller
@param _receiver Receiver address for any rewards claimed via `claim_rewards`
@param _receiver Receiver address for any rewards claimed via `claim_rewards`
"""
"""
self.rewards_receiver[msg.sender] = _receiver
self.rewards_receiver[msg.sender] = _receiver




@external
@external
@nonreentrant('lock')
@nonreentrant('lock')
def claim_rewards(_addr: address = msg.sender, _receiver: address = ZERO_ADDRESS):
def claim_rewards(_addr: address = msg.sender, _receiver: address = ZERO_ADDRESS):
"""
"""
@notice Claim available reward tokens for `_addr`
@notice Claim available reward tokens for `_addr`
@param _addr Address to claim for
@param _addr Address to claim for
@param _receiver Address to transfer rewards to - if set to
@param _receiver Address to transfer rewards to - if set to
ZERO_ADDRESS, uses the default reward receiver
ZERO_ADDRESS, uses the default reward receiver
for the caller
for the caller
"""
"""
if _receiver != ZERO_ADDRESS:
if _receiver != ZERO_ADDRESS:
assert _addr == msg.sender # dev: cannot redirect when claiming for another user
assert _addr == msg.sender # dev: cannot redirect when claiming for another user
self._checkpoint_rewards(_addr, self.totalSupply, True, _receiver)
self._checkpoint_rewards(_addr, self.totalSupply, True, _receiver)




@external
@external
def kick(addr: address):
def kick(addr: address):
"""
"""
@notice Kick `addr` for abusing their boost
@notice Kick `addr` for abusing their boost
@dev Only if either they had another voting event, or their voting escrow lock expired
@dev Only if either they had another voting event, or their voting escrow lock expired
@param addr Address to kick
@param addr Address to kick
"""
"""
_voting_escrow: address = self.voting_escrow
_voting_escrow: address = self.voting_escrow
t_last: uint256 = self.integrate_checkpoint_of[addr]
t_last: uint256 = self.integrate_checkpoint_of[addr]
t_ve: uint256 = VotingEscrow(_voting_escrow).user_point_history__ts(
t_ve: uint256 = VotingEscrow(_voting_escrow).user_point_history__ts(
addr, VotingEscrow(_voting_escrow).user_point_epoch(addr)
addr, VotingEscrow(_voting_escrow).user_point_epoch(addr)
)
)
_balance: uint256 = self.balanceOf[addr]
_balance: uint256 = self.balanceOf[addr]


assert ERC20(_voting_escrow).balanceOf(addr) == 0 or t_ve > t_last # dev: kick not allowed
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
assert self.working_balances[addr] > _balance * TOKENLESS_PRODUCTION / 100 # dev: kick not needed


self._checkpoint(addr)
self._checkpoint(addr)
self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply)
self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply)




@external
@external
@nonreentrant('lock')
@nonreentrant('lock')
def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = False):
def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = False):
"""
"""
@notice Deposit `_value` LP tokens
@notice Deposit `_value` LP tokens
@dev Depositting also claims pending reward tokens
@dev Depositting also claims pending reward tokens
@param _value Number of tokens to deposit
@param _value Number of tokens to deposit
@param _addr Address to deposit for
@param _addr Address to deposit for
"""
"""


self._checkpoint(_addr)
self._checkpoint(_addr)


if _value != 0:
if _value != 0:
is_rewards: bool = self.reward_tokens[0] != ZERO_ADDRESS
is_rewards: bool = self.reward_tokens[0] != ZERO_ADDRESS
total_supply: uint256 = self.totalSupply
total_supply: uint256 = self.totalSupply
if is_rewards:
if is_rewards:
self._checkpoint_rewards(_addr, total_supply, _claim_rewards, ZERO_ADDRESS)
self._checkpoint_rewards(_addr, total_supply, _claim_rewards, ZERO_ADDRESS)


total_supply += _value
total_supply += _value
new_balance: uint256 = self.balanceOf[_addr] + _value
new_balance: uint256 = self.balanceOf[_addr] + _value
self.balanceOf[_addr] = new_balance
self.balanceOf[_addr] = new_balance
self.totalSupply = total_supply
self.totalSupply = total_supply


self._update_liquidity_limit(_addr, new_balance, total_supply)
self._update_liquidity_limit(_addr, new_balance, total_supply)


ERC20(self.lp_token).transferFrom(msg.sender, self, _value)
ERC20(self.lp_token).transferFrom(msg.sender, self, _value)
if is_rewards:
if is_rewards:
reward_data: uint256 = self.reward_data
reward_data: uint256 = self.reward_data
if reward_data > 0:
if reward_data > 0:
deposit_sig: Bytes[4] = slice(self.reward_sigs, 0, 4)
deposit_sig: Bytes[4] = slice(self.reward_sigs, 0, 4)
if convert(deposit_sig, uint256) != 0:
if convert(deposit_sig, uint256) != 0:
raw_call(
raw_call(
convert(reward_data % 2**160, address),
convert(reward_data % 2**160, address),
concat(deposit_sig, convert(_value, bytes32))
concat(deposit_sig, convert(_value, bytes32))
)
)


log Deposit(_addr, _value)
log Deposit(_addr, _value)
log Transfer(ZERO_ADDRESS, _addr, _value)
log Transfer(ZERO_ADDRESS, _addr, _value)




@external
@external
@nonreentrant('lock')
@nonreentrant('lock')
def withdraw(_value: uint256, _claim_rewards: bool = False):
def withdraw(_value: uint256, _claim_rewards: bool = False):
"""
"""
@notice Withdraw `_value` LP tokens
@notice Withdraw `_value` LP tokens
@dev Withdrawing also claims pending reward tokens
@dev Withdrawing also claims pending reward tokens
@param _value Number of tokens to withdraw
@param _value Number of tokens to withdraw
"""
"""
self._checkpoint(msg.sender)
self._checkpoint(msg.sender)


if _value != 0:
if _value != 0:
is_rewards: bool = self.reward_tokens[0] != ZERO_ADDRESS
is_rewards: bool = self.reward_tokens[0] != ZERO_ADDRESS
total_supply: uint256 = self.totalSupply
total_supply: uint256 = self.totalSupply
if is_rewards:
if is_rewards:
self._checkpoint_rewards(msg.sender, total_supply, _claim_rewards, ZERO_ADDRESS)
self._checkpoint_rewards(msg.sender, total_supply, _claim_rewards, ZERO_ADDRESS)


total_supply -= _value
total_supply -= _value
new_balance: uint256 = self.balanceOf[msg.sender] - _value
new_balance: uint256 = self.balanceOf[msg.sender] - _value
self.balanceOf[msg.sender] = new_balance
self.balanceOf[msg.sender] = new_balance
self.totalSupply = total_supply
self.totalSupply = total_supply


self._update_liquidity_limit(msg.sender, new_balance, total_supply)
self._update_liquidity_limit(msg.sender, new_balance, total_supply)


if is_rewards:
if is_rewards:
reward_data: uint256 = self.reward_data
reward_data: uint256 = self.reward_data
if reward_data > 0:
if reward_data > 0:
withdraw_sig: Bytes[4] = slice(self.reward_sigs, 4, 4)
withdraw_sig: Bytes[4] = slice(self.reward_sigs, 4, 4)
if convert(withdraw_sig, uint256) != 0:
if convert(withdraw_sig, uint256) != 0:
raw_call(
raw_call(
convert(reward_data % 2**160, address),
convert(reward_data % 2**160, address),
concat(withdraw_sig, convert(_value, bytes32))
concat(withdraw_sig, convert(_value, bytes32))
)
)
ERC20(self.lp_token).transfer(msg.sender, _value)
ERC20(self.lp_token).transfer(msg.sender, _value)


log Withdraw(msg.sender, _value)
log Withdraw(msg.sender, _value)
log Transfer(msg.sender, ZERO_ADDRESS, _value)
log Transfer(msg.sender, ZERO_ADDRESS, _value)




@internal
@internal
def _transfer(_from: address, _to: address, _value: uint256):
def _transfer(_from: address, _to: address, _value: uint256):
self._checkpoint(_from)
self._checkpoint(_from)
self._checkpoint(_to)
self._checkpoint(_to)


if _value != 0:
if _value != 0:
total_supply: uint256 = self.totalSupply
total_supply: uint256 = self.totalSupply
is_rewards: bool = self.reward_tokens[0] != ZERO_ADDRESS
is_rewards: bool = self.reward_tokens[0] != ZERO_ADDRESS
if is_rewards:
if is_rewards:
self._checkpoint_rewards(_from, total_supply, False, ZERO_ADDRESS)
self._checkpoint_rewards(_from, total_supply, False, ZERO_ADDRESS)
new_balance: uint256 = self.balanceOf[_from] - _value
new_balance: uint256 = self.balanceOf[_from] - _value
self.balanceOf[_from] = new_balance
self.balanceOf[_from] = new_balance
self._update_liquidity_limit(_from, new_balance, total_supply)
self._update_liquidity_limit(_from, new_balance, total_supply)


if is_rewards:
if is_rewards:
self._checkpoint_rewards(_to, total_supply, False, ZERO_ADDRESS)
self._checkpoint_rewards(_to, total_supply, False, ZERO_ADDRESS)
new_balance = self.balanceOf[_to] + _value
new_balance = self.balanceOf[_to] + _value
self.balanceOf[_to] = new_balance
self.balanceOf[_to] = new_balance
self._update_liquidity_limit(_to, new_balance, total_supply)
self._update_liquidity_limit(_to, new_balance, total_supply)


log Transfer(_from, _to, _value)
log Transfer(_from, _to, _value)




@external
@external
@nonreentrant('lock')
@nonreentrant('lock')
def transfer(_to : address, _value : uint256) -> bool:
def transfer(_to : address, _value : uint256) -> bool:
"""
"""
@notice Transfer token for a specified address
@notice Transfer token for a specified address
@dev Transferring claims pending reward tokens for the sender and receiver
@dev Transferring claims pending reward tokens for the sender and receiver
@param _to The address to transfer to.
@param _to The address to transfer to.
@param _value The amount to be transferred.
@param _value The amount to be transferred.
"""
"""
self._transfer(msg.sender, _to, _value)
self._transfer(msg.sender, _to, _value)


return True
return True




@external
@external
@nonreentrant('lock')
@nonreentrant('lock')
def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
"""
"""
@notice Transfer tokens from one address to another.
@notice Transfer tokens from one address to another.
@dev Transferring claims pending reward tokens for the sender and receiver
@dev Transferring claims pending reward tokens for the sender and receiver
@param _from address The address which you want to send tokens from
@param _from address The address which you want to send tokens from
@param _to address The address which you want to transfer to
@param _to address The address which you want to transfer to
@param _value uint256 the amount of tokens to be transferred
@param _value uint256 the amount of tokens to be transferred
"""
"""
_allowance: uint256 = self.allowance[_from][msg.sender]
_allowance: uint256 = self.allowance[_from][msg.sender]
if _allowance != MAX_UINT256:
if _allowance != MAX_UINT256:
self.allowance[_from][msg.sender] = _allowance - _value
self.allowance[_from][msg.sender] = _allowance - _value


self._transfer(_from, _to, _value)
self._transfer(_from, _to, _value)


return True
return True




@external
@external
def approve(_spender : address, _value : uint256) -> bool:
def approve(_spender : address, _value : uint256) -> bool:
"""
"""
@notice Approve the passed address to transfer the specified amount of
@notice Approve the passed address to transfer the specified amount of
tokens on behalf of msg.sender
tokens on behalf of msg.sender
@dev Beware that changing an allowance via this method brings the risk
@dev Beware that changing an allowance via this method brings the risk
that someone may use both the old and new allowance by unfortunate
that someone may use both the old and new allowance by unfortunate
transaction ordering. This may be mitigated with the use of
transaction ordering. This may be mitigated with the use of
{incraseAllowance} and {decreaseAllowance}.
{incraseAllowance} and {decreaseAllowance}.
https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
@param _spender The address which will transfer the funds
@param _spender The address which will transfer the funds
@param _value The amount of tokens that may be transferred
@param _value The amount of tokens that may be transferred
@return bool success
@return bool success
"""
"""
self.allowance[msg.sender][_spender] = _value
self.allowance[msg.sender][_spender] = _value
log Approval(msg.sender, _spender, _value)
log Approval(msg.sender, _spender, _value)


return True
return True




@external
@external
def increaseAllowance(_spender: address, _added_value: uint256) -> bool:
def increaseAllowance(_spender: address, _added_value: uint256) -> bool:
"""
"""
@notice Increase the allowance granted to `_spender` by the caller
@notice Increase the allowance granted to `_spender` by the caller
@dev This is alternative to {approve} that can be used as a mitigation for
@dev This is alternative to {approve} that can be used as a mitigation for
the potential race condition
the potential race condition
@param _spender The address which will transfer the funds
@param _spender The address which will transfer the funds
@param _added_value The amount of to increase the allowance
@param _added_value The amount of to increase the allowance
@return bool success
@return bool success
"""
"""
allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value
allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value
self.allowance[msg.sender][_spender] = allowance
self.allowance[msg.sender][_spender] = allowance


log Approval(msg.sender, _spender, allowance)
log Approval(msg.sender, _spender, allowance)


return True
return True




@external
@external
def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool:
def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool:
"""
"""
@notice Decrease the allowance granted to `_spender` by the caller
@notice Decrease the allowance granted to `_spender` by the caller
@dev This is alternative to {approve} that can be used as a mitigation for
@dev This is alternative to {approve} that can be used as a mitigation for
the potential race condition
the potential race condition
@param _spender The address which will transfer the funds
@param _spender The address which will transfer the funds
@param _subtracted_value The amount of to decrease the allowance
@param _subtracted_value The amount of to decrease the allowance
@return bool success
@return bool success
"""
"""
allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value
allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value
self.allowance[msg.sender][_spender] = allowance
self.allowance[msg.sender][_spender] = allowance


log Approval(msg.sender, _spender, allowance)
log Approval(msg.sender, _spender, allowance)


return True
return True




@external
@external
@nonreentrant('lock')
@nonreentrant('lock')
def set_rewards(_reward_contract: address, _sigs: bytes32, _reward_tokens: address[MAX_REWARDS]):
def set_rewards(_reward_contract: address, _sigs: bytes32, _reward_tokens: address[MAX_REWARDS]):
"""
"""
@notice Set the active reward contract
@notice Set the active reward contract
@dev A reward contract cannot be set while this contract has no deposits
@param _reward_contract Reward contract address. Set to ZERO_ADDRESS to
disable staking.
@param _sigs Four byte selectors for staking, withdrawing and claiming,