Created
February 13, 2022 22:00
-
-
Save kassandraoftroy/cebf4b015b067695b43d8a28acbe5fd7 to your computer and use it in GitHub Desktop.
LiquidtyGaugeUpgrade
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# @version 0.2.16 | |
""" | |
@title Liquidity Gauge v4 | |
@author Angle Protocol | |
@license MIT | |
""" | |
# Original idea and credit: | |
# Curve Finance's LiquidityGaugeV4 | |
# https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV4.vy | |
from vyper.interfaces import ERC20 | |
implements: ERC20 | |
interface CRV20: | |
def future_epoch_time_write() -> uint256: nonpayable | |
def rate() -> uint256: view | |
interface Controller: | |
def period() -> int128: view | |
def period_write() -> int128: nonpayable | |
def period_timestamp(p: int128) -> uint256: view | |
def gauge_relative_weight(addr: address, time: uint256) -> uint256: view | |
def voting_escrow() -> address: view | |
def checkpoint(): nonpayable | |
def checkpoint_gauge(addr: address): nonpayable | |
interface Minter: | |
def token() -> address: view | |
def controller() -> address: view | |
def minted(user: address, gauge: address) -> uint256: view | |
interface VotingEscrow: | |
def user_point_epoch(addr: address) -> uint256: view | |
def user_point_history__ts(addr: address, epoch: uint256) -> uint256: view | |
interface VotingEscrowBoost: | |
def adjusted_balance_of(_account: address) -> uint256: view | |
interface ERC20Extended: | |
def symbol() -> String[26]: view | |
event Deposit: | |
provider: indexed(address) | |
value: uint256 | |
event Withdraw: | |
provider: indexed(address) | |
value: uint256 | |
event UpdateLiquidityLimit: | |
user: address | |
original_balance: uint256 | |
original_supply: uint256 | |
working_balance: uint256 | |
working_supply: uint256 | |
event UpdateLiquidityLimitRewards: | |
user: address | |
token: address | |
original_balance: uint256 | |
original_supply: uint256 | |
working_balance: uint256 | |
working_supply: uint256 | |
event CommitOwnership: | |
admin: address | |
event ApplyOwnership: | |
admin: address | |
event Transfer: | |
_from: indexed(address) | |
_to: indexed(address) | |
_value: uint256 | |
event Approval: | |
_owner: indexed(address) | |
_spender: indexed(address) | |
_value: uint256 | |
event RewardDataUpdate: | |
_token: indexed(address) | |
_amount: uint256 | |
struct OldReward: | |
token: address | |
distributor: address | |
period_finish: uint256 | |
rate: uint256 | |
last_update: uint256 | |
integral: uint256 | |
struct Reward: | |
distributor: address | |
ve: address | |
veBoost: address | |
period_finish: uint256 | |
rate: uint256 | |
last_update: uint256 | |
integral: uint256 | |
MAX_REWARDS: constant(uint256) = 8 | |
TOKENLESS_PRODUCTION: constant(uint256) = 40 | |
WEEK: constant(uint256) = 604800 | |
token: public(address) | |
voting_escrow: public(address) | |
veBoost_proxy: public(address) | |
lp_token: public(address) | |
decimal_staking_token: public(uint256) | |
balanceOf: public(HashMap[address, uint256]) | |
totalSupply: public(uint256) | |
allowance: public(HashMap[address, HashMap[address, uint256]]) | |
name: public(String[64]) | |
symbol: public(String[32]) | |
old_working_balances: public(HashMap[address, uint256]) | |
old_working_supply: public(uint256) | |
old_integrate_checkpoint_of: public(HashMap[address, uint256]) | |
# For tracking external rewards | |
reward_count: public(uint256) | |
reward_tokens: public(address[MAX_REWARDS]) | |
old_reward_data: public(HashMap[address, OldReward]) | |
# claimant -> default reward receiver | |
rewards_receiver: public(HashMap[address, address]) | |
# reward token -> claiming address -> integral | |
reward_integral_for: public(HashMap[address, HashMap[address, uint256]]) | |
# user -> [uint128 claimable amount][uint128 claimed amount] | |
claim_data: HashMap[address, HashMap[address, uint256]] | |
admin: public(address) | |
future_admin: public(address) | |
initialized: public(bool) | |
### NEW STATE VARS | |
minter: public(address) | |
gauge_controller: public(address) | |
future_epoch_time: public(uint256) | |
working_balances: public(HashMap[address, uint256]) | |
working_supply: public(uint256) | |
working_balances_rewards: public(HashMap[address, HashMap[address, uint256]]) | |
working_supply_rewards: public(HashMap[address, uint256]) | |
integrate_checkpoint_of_rewards: public(HashMap[address, HashMap[address, uint256]]) | |
# 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 | |
period: public(int128) | |
period_timestamp: public(uint256[100000000000000000000000000000]) | |
# 1e18 * ∫(rate(t) / totalSupply(t) dt) from 0 till checkpoint | |
integrate_inv_supply: public(uint256[100000000000000000000000000000]) # bump epoch when rate() changes | |
# 1e18 * ∫(rate(t) / totalSupply(t) dt) from (last_action) till checkpoint | |
integrate_inv_supply_of: public(HashMap[address, uint256]) | |
integrate_checkpoint_of: public(HashMap[address, uint256]) | |
# ∫(balance * rate(t) / totalSupply(t) dt) from 0 till checkpoint | |
# Units: rate * t = already number of coins per address to issue | |
integrate_fraction: public(HashMap[address, uint256]) | |
inflation_rate: public(uint256) | |
# For tracking external rewards | |
reward_data: public(HashMap[address, Reward]) | |
is_killed: public(bool) | |
reinitialized: public(bool) | |
@external | |
def __init__(): | |
""" | |
@notice Contract constructor | |
""" | |
assert self.reinitialized == False #dev: contract is already initialized | |
self.reinitialized = True | |
@external | |
def initialize(_lp_token: address, _minter: address, _veBoost_proxy: address): | |
assert self.reinitialized == False #dev: contract is already initialized | |
self.reinitialized = True | |
_angle: address = self.token | |
assert _angle != ZERO_ADDRESS | |
_controller: address = Minter(_minter).controller() | |
_token: address = Minter(_minter).token() | |
_ve: address = Controller(_controller).voting_escrow() | |
self.reward_data[_angle].distributor = self.old_reward_data[_angle].distributor | |
self.reward_data[_angle].ve = self.voting_escrow | |
self.reward_data[_angle].veBoost = self.veBoost_proxy | |
self.reward_data[_angle].period_finish = self.old_reward_data[_angle].period_finish | |
self.reward_data[_angle].rate = self.old_reward_data[_angle].rate | |
self.reward_data[_angle].last_update = self.old_reward_data[_angle].last_update | |
self.reward_data[_angle].integral = self.old_reward_data[_angle].integral | |
self.working_balances_rewards[_angle] = self.old_working_balances | |
self.working_supply_rewards[_angle] = self.old_working_supply | |
self.integrate_checkpoint_of_rewards[_angle] = self.old_integrate_checkpoint_of | |
self.lp_token = _lp_token | |
self.minter = _minter | |
self.gauge_controller = _controller | |
self.token = _token | |
self.voting_escrow = _ve | |
self.veBoost_proxy = _veBoost_proxy | |
# symbol: String[26] = ERC20Extended(_lp_token).symbol() | |
# self.name = concat(symbol, " Spice Harvester Deposit") | |
# self.symbol = concat("st", symbol) | |
self.period_timestamp[0] = block.timestamp | |
self.inflation_rate = CRV20(_token).rate() | |
self.future_epoch_time = CRV20(_token).future_epoch_time_write() | |
@view | |
@external | |
def decimals() -> uint256: | |
""" | |
@notice Get the number of decimals for this token | |
@dev Implemented as a view method to reduce gas costs | |
@return uint256 decimal places | |
""" | |
return 18 | |
@view | |
@external | |
def integrate_checkpoint() -> uint256: | |
return self.period_timestamp[self.period] | |
@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(self.veBoost_proxy).adjusted_balance_of(addr) | |
voting_total: uint256 = ERC20(self.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) | |
@internal | |
def _update_liquidity_limit_rewards(addr: address, l: uint256, L: uint256, token: address): | |
""" | |
@notice Calculate limits which depend on the amount of TOKEN token per-user. | |
Effectively it calculates working balances to apply amplification | |
of TOKEN production | |
@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 | |
veBoost: address = self.reward_data[token].veBoost | |
ve: address = self.reward_data[token].ve | |
voting_balance: uint256 = VotingEscrowBoost(veBoost).adjusted_balance_of(addr) | |
voting_total: uint256 = ERC20(ve).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_rewards[token][addr] | |
self.working_balances_rewards[token][addr] = lim | |
_working_supply: uint256 = self.working_supply_rewards[token] + lim - old_bal | |
self.working_supply_rewards[token] = _working_supply | |
log UpdateLiquidityLimitRewards(addr, token, l, L, lim, _working_supply) | |
@internal | |
def _checkpoint_reward(_user: address, token: address, _total_supply: uint256, _user_balance: uint256, _claim: bool, receiver: address): | |
""" | |
@notice Claim pending rewards and checkpoint rewards for a user | |
""" | |
total_supply: uint256 = _total_supply | |
user_balance: uint256 = _user_balance | |
if self.reward_data[token].ve != ZERO_ADDRESS: | |
total_supply = self.working_supply_rewards[token] | |
user_balance = self.working_balances_rewards[token][_user] | |
self.integrate_checkpoint_of_rewards[token][_user] = block.timestamp | |
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: | |
self.reward_data[token].last_update = last_update | |
if total_supply != 0: | |
integral += duration * self.reward_data[token].rate * 10**18 / total_supply | |
self.reward_data[token].integral = integral | |
if _user != ZERO_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 = shift(claim_data, -128) + new_claimable | |
if total_claimable > 0: | |
total_claimed: uint256 = claim_data % 2**128 | |
if _claim: | |
response: Bytes[32] = raw_call( | |
token, | |
concat( | |
method_id("transfer(address,uint256)"), | |
convert(receiver, bytes32), | |
convert(total_claimable, bytes32), | |
), | |
max_outsize=32, | |
) | |
if len(response) != 0: | |
assert convert(response, bool) | |
self.claim_data[_user][token] = total_claimed + total_claimable | |
elif new_claimable > 0: | |
self.claim_data[_user][token] = total_claimed + shift(total_claimable, 128) | |
@internal | |
def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _receiver: address): | |
""" | |
@notice Claim pending rewards and checkpoint rewards for a user | |
""" | |
receiver: address = _receiver | |
user_balance: uint256 = 0 | |
if _user != ZERO_ADDRESS: | |
user_balance = self.balanceOf[_user] | |
if _claim and _receiver == ZERO_ADDRESS: | |
# if receiver is not explicitly declared, check if a default receiver is set | |
receiver = self.rewards_receiver[_user] | |
if receiver == ZERO_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] | |
self._checkpoint_reward(_user, token, _total_supply, user_balance, _claim, receiver) | |
@internal | |
def _checkpoint(addr: address): | |
""" | |
@notice Checkpoint for a user | |
@param addr User address | |
""" | |
_period: int128 = self.period | |
_period_time: uint256 = self.period_timestamp[_period] | |
_integrate_inv_supply: uint256 = self.integrate_inv_supply[_period] | |
rate: uint256 = self.inflation_rate | |
new_rate: uint256 = rate | |
prev_future_epoch: uint256 = self.future_epoch_time | |
if prev_future_epoch >= _period_time: | |
_token: address = self.token | |
self.future_epoch_time = CRV20(_token).future_epoch_time_write() | |
new_rate = CRV20(_token).rate() | |
self.inflation_rate = new_rate | |
if self.is_killed: | |
# Stop distributing inflation as soon as killed | |
rate = 0 | |
# Update integral of 1/supply | |
if block.timestamp > _period_time: | |
_working_supply: uint256 = self.working_supply | |
_controller: address = self.gauge_controller | |
Controller(_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(_controller).gauge_relative_weight(self, prev_week_time / WEEK * WEEK) | |
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 | |
@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, self.minter] # dev: unauthorized | |
_total_supply: uint256 = self.totalSupply | |
user_balance: uint256 = self.balanceOf[addr] | |
self._checkpoint(addr) | |
self._update_liquidity_limit(addr, user_balance, _total_supply) | |
if msg.sender == addr: | |
reward_count: uint256 = self.reward_count | |
for i in range(MAX_REWARDS): | |
if i == reward_count: | |
break | |
token: address = self.reward_tokens[i] | |
if self.reward_data[token].ve != ZERO_ADDRESS: | |
self._checkpoint_reward(addr, token, _total_supply, user_balance, False, ZERO_ADDRESS) | |
self._update_liquidity_limit_rewards(addr, user_balance, _total_supply, token) | |
return True | |
@external | |
def claimable_tokens(addr: address) -> uint256: | |
""" | |
@notice Get the number of claimable tokens per user | |
@dev This function should be manually changed to "view" in the ABI | |
@return uint256 number of claimable tokens per user | |
""" | |
self._checkpoint(addr) | |
return self.integrate_fraction[addr] - Minter(self.minter).minted(addr, self) | |
@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 | |
@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 | |
user_balance: uint256 = self.balanceOf[_user] | |
if self.reward_data[_reward_token].ve != ZERO_ADDRESS: | |
total_supply = self.working_supply_rewards[_reward_token] | |
user_balance = self.working_balances_rewards[_reward_token][_user] | |
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 = user_balance * (integral - integral_for) / 10**18 | |
return shift(self.claim_data[_user][_reward_token], -128) + new_claimable | |
@external | |
def set_rewards_receiver(_receiver: address): | |
""" | |
@notice Set the default reward receiver for 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` | |
""" | |
self.rewards_receiver[msg.sender] = _receiver | |
@external | |
@nonreentrant('lock') | |
def claim_rewards(_addr: address = msg.sender, _receiver: address = ZERO_ADDRESS): | |
""" | |
@notice Claim available reward tokens for `_addr` | |
@param _addr Address to claim for | |
@param _receiver Address to transfer rewards to - if set to | |
ZERO_ADDRESS, uses the default reward receiver | |
for the caller | |
""" | |
if _receiver != ZERO_ADDRESS: | |
assert _addr == msg.sender # dev: cannot redirect when claiming for another user | |
self._checkpoint_rewards(_addr, self.totalSupply, True, _receiver) | |
@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] | |
_ve: address = self.voting_escrow | |
t_ve: uint256 = VotingEscrow(_ve).user_point_history__ts( | |
addr, VotingEscrow(_ve).user_point_epoch(addr) | |
) | |
_balance: uint256 = self.balanceOf[addr] | |
assert ERC20(_ve).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) | |
@external | |
def kick_rewards(addr: address, token: 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 | |
""" | |
ve: address = self.reward_data[token].ve | |
assert ve != ZERO_ADDRESS | |
t_last: uint256 = self.integrate_checkpoint_of_rewards[token][addr] | |
t_ve: uint256 = VotingEscrow(ve).user_point_history__ts( | |
addr, VotingEscrow(ve).user_point_epoch(addr) | |
) | |
_balance: uint256 = self.balanceOf[addr] | |
assert ERC20(ve).balanceOf(addr) == 0 or t_ve > t_last # dev: kick not allowed | |
assert self.working_balances_rewards[token][addr] > _balance * TOKENLESS_PRODUCTION / 100 # dev: kick not needed | |
total_supply: uint256 = self.totalSupply | |
self._checkpoint_reward(addr, token, total_supply, _balance, False, ZERO_ADDRESS) | |
self._update_liquidity_limit_rewards(addr, _balance, total_supply, token) | |
@external | |
@nonreentrant('lock') | |
def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = False): | |
""" | |
@notice Deposit `_value` LP tokens | |
@dev Depositing also claims pending reward tokens | |
@param _value Number of tokens to deposit | |
@param _addr Address to deposit for | |
""" | |
self._checkpoint(_addr) | |
if _value != 0: | |
total_supply: uint256 = self.totalSupply | |
reward_count: uint256 = self.reward_count | |
if reward_count > 0: | |
self._checkpoint_rewards(_addr, total_supply, _claim_rewards, ZERO_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) | |
for i in range(MAX_REWARDS): | |
if i == reward_count: | |
break | |
token: address = self.reward_tokens[i] | |
if self.reward_data[token].ve != ZERO_ADDRESS: | |
self._update_liquidity_limit_rewards(_addr, new_balance, total_supply, token) | |
ERC20(self.lp_token).transferFrom(msg.sender, self, _value) | |
log Deposit(_addr, _value) | |
log Transfer(ZERO_ADDRESS, _addr, _value) | |
@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: | |
total_supply: uint256 = self.totalSupply | |
reward_count: uint256 = self.reward_count | |
if reward_count > 0: | |
self._checkpoint_rewards(msg.sender, total_supply, _claim_rewards, ZERO_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) | |
for i in range(MAX_REWARDS): | |
if i == reward_count: | |
break | |
token: address = self.reward_tokens[i] | |
if self.reward_data[token].ve != ZERO_ADDRESS: | |
self._update_liquidity_limit_rewards(msg.sender, new_balance, total_supply, token) | |
ERC20(self.lp_token).transfer(msg.sender, _value) | |
log Withdraw(msg.sender, _value) | |
log Transfer(msg.sender, ZERO_ADDRESS, _value) | |
@internal | |
def _transfer(_from: address, _to: address, _value: uint256): | |
self._checkpoint(_from) | |
self._checkpoint(_to) | |
if _value != 0: | |
total_supply: uint256 = self.totalSupply | |
reward_count: uint256 = self.reward_count | |
if reward_count > 0: | |
self._checkpoint_rewards(_from, total_supply, False, ZERO_ADDRESS) | |
new_balance_from: uint256 = self.balanceOf[_from] - _value | |
self.balanceOf[_from] = new_balance_from | |
self._update_liquidity_limit(_from, new_balance_from, total_supply) | |
if reward_count > 0: | |
self._checkpoint_rewards(_to, total_supply, False, ZERO_ADDRESS) | |
new_balance_to: uint256 = self.balanceOf[_to] + _value | |
self.balanceOf[_to] = new_balance_to | |
self._update_liquidity_limit(_to, new_balance_to, total_supply) | |
for i in range(MAX_REWARDS): | |
if i == reward_count: | |
break | |
token: address = self.reward_tokens[i] | |
if self.reward_data[token].ve != ZERO_ADDRESS: | |
self._update_liquidity_limit_rewards(_from, new_balance_from, total_supply, token) | |
self._update_liquidity_limit_rewards(_to, new_balance_to, total_supply, token) | |
log Transfer(_from, _to, _value) | |
@external | |
@nonreentrant('lock') | |
def transfer(_to : address, _value : uint256) -> bool: | |
""" | |
@notice Transfer token for a specified address | |
@dev Transferring claims pending reward tokens for the sender and receiver | |
@param _to The address to transfer to. | |
@param _value The amount to be transferred. | |
""" | |
self._transfer(msg.sender, _to, _value) | |
return True | |
@external | |
@nonreentrant('lock') | |
def transferFrom(_from : address, _to : address, _value : uint256) -> bool: | |
""" | |
@notice Transfer tokens from one address to another. | |
@dev Transferring claims pending reward tokens for the sender and receiver | |
@param _from address The address which you want to send tokens from | |
@param _to address The address which you want to transfer to | |
@param _value uint256 the amount of tokens to be transferred | |
""" | |
_allowance: uint256 = self.allowance[_from][msg.sender] | |
if _allowance != MAX_UINT256: | |
self.allowance[_from][msg.sender] = _allowance - _value | |
self._transfer(_from, _to, _value) | |
return True | |
@external | |
def approve(_spender : address, _value : uint256) -> bool: | |
""" | |
@notice Approve the passed address to transfer the specified amount of | |
tokens on behalf of msg.sender | |
@dev Beware that changing an allowance via this method brings the risk | |
that someone may use both the old and new allowance by unfortunate | |
transaction ordering. This may be mitigated with the use of | |
{incraseAllowance} and {decreaseAllowance}. | |
https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | |
@param _spender The address which will transfer the funds | |
@param _value The amount of tokens that may be transferred | |
@return bool success | |
""" | |
self.allowance[msg.sender][_spender] = _value | |
log Approval(msg.sender, _spender, _value) | |
return True | |
@external | |
def increaseAllowance(_spender: address, _added_value: uint256) -> bool: | |
""" | |
@notice Increase the allowance granted to `_spender` by the caller | |
@dev This is alternative to {approve} that can be used as a mitigation for | |
the potential race condition | |
@param _spender The address which will transfer the funds | |
@param _added_value The amount of to increase the allowance | |
@return bool success | |
""" | |
allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value | |
self.allowance[msg.sender][_spender] = allowance | |
log Approval(msg.sender, _spender, allowance) | |
return True | |
@external | |
def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool: | |
""" | |
@notice Decrease the allowance granted to `_spender` by the caller | |
@dev This is alternative to {approve} that can be used as a mitigation for | |
the potential race condition | |
@param _spender The address which will transfer the funds | |
@param _subtracted_value The amount of to decrease the allowance | |
@return bool success | |
""" | |
allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value | |
self.allowance[msg.sender][_spender] = allowance | |
log Approval(msg.sender, _spender, allowance) | |
return True | |
@external | |
def add_reward(_reward_token: address, _distributor: address, _ve: address, _veBoost: address): | |
""" | |
@notice Set the active reward contract | |
""" | |
assert msg.sender == self.admin # dev: only owner | |
assert _distributor != ZERO_ADDRESS | |
reward_count: uint256 = self.reward_count | |
assert reward_count < MAX_REWARDS | |
assert self.reward_data[_reward_token].distributor == ZERO_ADDRESS | |
self.reward_data[_reward_token].distributor = _distributor | |
self.reward_data[_reward_token].ve = _ve | |
self.reward_data[_reward_token].veBoost = _veBoost | |
self.reward_tokens[reward_count] = _reward_token | |
self.reward_count = reward_count + 1 | |
@external | |
def set_reward_distributor(_reward_token: address, _distributor: address): | |
current_distributor: address = self.reward_data[_reward_token].distributor | |
assert msg.sender == current_distributor or msg.sender == self.admin | |
assert current_distributor != ZERO_ADDRESS | |
assert _distributor != ZERO_ADDRESS | |
self.reward_data[_reward_token].distributor = _distributor | |
@external | |
def set_reward_voting_escrow(_reward_token: address, _ve: address, _veBoost: address): | |
assert msg.sender == self.admin # dev: only owner | |
self.reward_data[_reward_token].ve = _ve | |
self.reward_data[_reward_token].veBoost = _veBoost | |
@external | |
@nonreentrant("lock") | |
def deposit_reward_token(_reward_token: address, _amount: uint256): | |
assert msg.sender == self.reward_data[_reward_token].distributor | |
self._checkpoint_rewards(ZERO_ADDRESS, self.totalSupply, False, ZERO_ADDRESS) | |
response: Bytes[32] = raw_call( | |
_reward_token, | |
concat( | |
method_id("transferFrom(address,address,uint256)"), | |
convert(msg.sender, bytes32), | |
convert(self, bytes32), | |
convert(_amount, bytes32), | |
), | |
max_outsize=32, | |
) | |
if len(response) != 0: | |
assert convert(response, bool) | |
period_finish: uint256 = self.reward_data[_reward_token].period_finish | |
if block.timestamp >= period_finish: | |
self.reward_data[_reward_token].rate = _amount / WEEK | |
else: | |
remaining: uint256 = period_finish - block.timestamp | |
leftover: uint256 = remaining * self.reward_data[_reward_token].rate | |
self.reward_data[_reward_token].rate = (_amount + leftover) / WEEK | |
self.reward_data[_reward_token].last_update = block.timestamp | |
self.reward_data[_reward_token].period_finish = block.timestamp + WEEK | |
@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 == self.admin | |
self.is_killed = _is_killed | |
@external | |
def commit_transfer_ownership(addr: address): | |
""" | |
@notice Transfer ownership of GaugeController to `addr` | |
@param addr Address to have ownership transferred to | |
""" | |
assert msg.sender == self.admin # dev: admin only | |
self.future_admin = addr | |
log CommitOwnership(addr) | |
@external | |
def accept_transfer_ownership(): | |
""" | |
@notice Accept a pending ownership transfer | |
""" | |
_admin: address = self.future_admin | |
assert msg.sender == _admin # dev: future admin only | |
self.admin = _admin | |
log ApplyOwnership(_admin) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment