Transaction Hash:
Block:
13814465 at Dec-16-2021 06:07:55 AM +UTC
Transaction Fee:
0.005019388451449938 ETH
$12.47
Gas Used:
86,661 Gas / 57.919807658 Gwei
Emitted Events:
109 |
Spell.Transfer( _from=[Receiver] BribeV2, _to=[Sender] 0xd8ce0efcc3f2dd2ea0b1d7b9bd260a3987f7ad46, _value=982120587357108315922215 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x090185f2...C2D37e5F6 | |||||
0x7893bbb4...3FfeE1026 | (bribe.crv.finance: Gauge Bribe V2) | ||||
0x829BD824...93333A830
Miner
| (F2Pool Old) | 1,565.225461001596016419 Eth | 1,565.225634323596016419 Eth | 0.000173322 | |
0xd8Ce0Efc...987f7ad46 |
0.707692952883459685 Eth
Nonce: 154
|
0.702673564432009747 Eth
Nonce: 155
| 0.005019388451449938 |
Execution Trace
BribeV2.claim_reward( gauge=0xd8b712d29381748dB89c36BCa0138d7c75866ddF, reward_token=0x090185f2135308BaD17527004364eBcC2D37e5F6 ) => ( 982120587357108315922215 )
-
Vyper_contract.last_user_vote( arg0=0xd8Ce0Efcc3f2dd2EA0B1D7b9bD260a3987f7ad46, arg1=0xd8b712d29381748dB89c36BCa0138d7c75866ddF ) => ( 1638979141 )
-
Vyper_contract.vote_user_slopes( arg0=0xd8Ce0Efcc3f2dd2EA0B1D7b9bD260a3987f7ad46, arg1=0xd8b712d29381748dB89c36BCa0138d7c75866ddF ) => ( slope=6341958396752917, power=10000, end=1720051200 )
-
Spell.transfer( to=0xd8Ce0Efcc3f2dd2EA0B1D7b9bD260a3987f7ad46, amount=982120587357108315922215 ) => ( True )
claim_reward[BribeV2 (ln:105)]
_claim_reward[BribeV2 (ln:106)]
_update_period[BribeV2 (ln:114)]
checkpoint_gauge[BribeV2 (ln:71)]
points_weight[BribeV2 (ln:72)]
last_user_vote[BribeV2 (ln:118)]
vote_user_slopes[BribeV2 (ln:120)]
_safeTransfer[BribeV2 (ln:124)]
call[BribeV2 (ln:137)]
encodeWithSelector[BribeV2 (ln:137)]
decode[BribeV2 (ln:138)]
File 1 of 3: BribeV2
File 2 of 3: Spell
File 3 of 3: Vyper_contract
// SPDX-License-Identifier: MIT pragma solidity 0.8.6; interface GaugeController { struct VotedSlope { uint slope; uint power; uint end; } struct Point { uint bias; uint slope; } function vote_user_slopes(address, address) external view returns (VotedSlope memory); function last_user_vote(address, address) external view returns (uint); function points_weight(address, uint256) external view returns (Point memory); function checkpoint_gauge(address) external; } interface ve { function get_last_user_slope(address) external view returns (int128); } interface erc20 { function transfer(address recipient, uint256 amount) external returns (bool); function decimals() external view returns (uint8); function balanceOf(address) external view returns (uint); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); function approve(address spender, uint amount) external returns (bool); } contract BribeV2 { uint constant WEEK = 86400 * 7; uint constant PRECISION = 10**18; GaugeController constant GAUGE = GaugeController(0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB); ve constant VE = ve(0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2); mapping(address => mapping(address => uint)) _claims_per_gauge; mapping(address => mapping(address => uint)) _reward_per_gauge; mapping(address => mapping(address => uint)) public reward_per_token; mapping(address => mapping(address => uint)) public active_period; mapping(address => mapping(address => mapping(address => uint))) public last_user_claim; mapping(address => address[]) _rewards_per_gauge; mapping(address => address[]) _gauges_per_reward; mapping(address => mapping(address => bool)) _rewards_in_gauge; function _add(address gauge, address reward) internal { if (!_rewards_in_gauge[gauge][reward]) { _rewards_per_gauge[gauge].push(reward); _gauges_per_reward[reward].push(gauge); _rewards_in_gauge[gauge][reward] = true; } } function rewards_per_gauge(address gauge) external view returns (address[] memory) { return _rewards_per_gauge[gauge]; } function gauges_per_reward(address reward) external view returns (address[] memory) { return _gauges_per_reward[reward]; } function _update_period(address gauge, address reward_token) internal returns (uint) { uint _period = active_period[gauge][reward_token]; if (block.timestamp >= _period + WEEK) { _period = block.timestamp / WEEK * WEEK; GAUGE.checkpoint_gauge(gauge); uint _slope = GAUGE.points_weight(gauge, _period).slope; uint _amount = _reward_per_gauge[gauge][reward_token] - _claims_per_gauge[gauge][reward_token]; reward_per_token[gauge][reward_token] = _amount * PRECISION / _slope; active_period[gauge][reward_token] = _period; } return _period; } function add_reward_amount(address gauge, address reward_token, uint amount) external returns (bool) { _safeTransferFrom(reward_token, msg.sender, address(this), amount); _reward_per_gauge[gauge][reward_token] += amount; _update_period(gauge, reward_token); _add(gauge, reward_token); return true; } function tokens_for_bribe(address user, address gauge, address reward_token) external view returns (uint) { return uint(int(VE.get_last_user_slope(user))) * reward_per_token[gauge][reward_token] / PRECISION; } function claimable(address user, address gauge, address reward_token) external view returns (uint) { uint _period = active_period[gauge][reward_token]; uint _amount = 0; if (last_user_claim[user][gauge][reward_token] < _period) { uint _last_vote = GAUGE.last_user_vote(user, gauge); if (_last_vote < _period) { uint _slope = GAUGE.vote_user_slopes(user, gauge).slope; _amount = _slope * reward_per_token[gauge][reward_token] / PRECISION; } } return _amount; } function claim_reward(address user, address gauge, address reward_token) external returns (uint) { return _claim_reward(user, gauge, reward_token); } function claim_reward(address gauge, address reward_token) external returns (uint) { return _claim_reward(msg.sender, gauge, reward_token); } function _claim_reward(address user, address gauge, address reward_token) internal returns (uint) { uint _period = _update_period(gauge, reward_token); uint _amount = 0; if (last_user_claim[user][gauge][reward_token] < _period) { last_user_claim[user][gauge][reward_token] = _period; uint _last_vote = GAUGE.last_user_vote(user, gauge); if (_last_vote < _period) { uint _slope = GAUGE.vote_user_slopes(user, gauge).slope; _amount = _slope * reward_per_token[gauge][reward_token] / PRECISION; if (_amount > 0) { _claims_per_gauge[gauge][reward_token] += _amount; _safeTransfer(reward_token, user, _amount); } } } return _amount; } function _safeTransfer( address token, address to, uint256 value ) internal { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(erc20.transfer.selector, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool)))); } function _safeTransferFrom( address token, address from, address to, uint256 value ) internal { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(erc20.transferFrom.selector, from, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool)))); } }
File 2 of 3: Spell
// SPDX-License-Identifier: MIT // .d8888b. 888 888 // d88P Y88b 888 888 // Y88b. 888 888 // "Y888b. 88888b. .d88b. 888 888 // "Y88b. 888 "88b d8P Y8b 888 888 // "888 888 888 88888888 888 888 // Y88b d88P 888 d88P Y8b. 888 888 // "Y8888P" 88888P" "Y8888 888 888 // 888 // 888 // 888 // Special thanks to: // @BoringCrypto for his great libraries @ https://github.com/boringcrypto/BoringSolidity pragma solidity 0.6.12; // Contract: BoringOwnable // Audit on 5-Jan-2021 by Keno and BoringCrypto // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol // Edited by BoringCrypto contract BoringOwnableData { address public owner; address public pendingOwner; } contract BoringOwnable is BoringOwnableData { event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /// @notice `owner` defaults to msg.sender on construction. constructor() public { owner = msg.sender; emit OwnershipTransferred(address(0), msg.sender); } /// @notice Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner. /// Can only be invoked by the current `owner`. /// @param newOwner Address of the new owner. /// @param direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`. /// @param renounce Allows the `newOwner` to be `address(0)` if `direct` and `renounce` is True. Has no effect otherwise. function transferOwnership( address newOwner, bool direct, bool renounce ) public onlyOwner { if (direct) { // Checks require(newOwner != address(0) || renounce, "Ownable: zero address"); // Effects emit OwnershipTransferred(owner, newOwner); owner = newOwner; pendingOwner = address(0); } else { // Effects pendingOwner = newOwner; } } /// @notice Needs to be called by `pendingOwner` to claim ownership. function claimOwnership() public { address _pendingOwner = pendingOwner; // Checks require(msg.sender == _pendingOwner, "Ownable: caller != pending owner"); // Effects emit OwnershipTransferred(owner, _pendingOwner); owner = _pendingOwner; pendingOwner = address(0); } /// @notice Only allows the `owner` to execute the function. modifier onlyOwner() { require(msg.sender == owner, "Ownable: caller is not the owner"); _; } } contract Domain { bytes32 private constant DOMAIN_SEPARATOR_SIGNATURE_HASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); // See https://eips.ethereum.org/EIPS/eip-191 string private constant EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA = "\x19\x01"; // solhint-disable var-name-mixedcase bytes32 private immutable _DOMAIN_SEPARATOR; uint256 private immutable DOMAIN_SEPARATOR_CHAIN_ID; /// @dev Calculate the DOMAIN_SEPARATOR function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) { return keccak256( abi.encode( DOMAIN_SEPARATOR_SIGNATURE_HASH, chainId, address(this) ) ); } constructor() public { uint256 chainId; assembly {chainId := chainid()} _DOMAIN_SEPARATOR = _calculateDomainSeparator(DOMAIN_SEPARATOR_CHAIN_ID = chainId); } /// @dev Return the DOMAIN_SEPARATOR // It's named internal to allow making it public from the contract that uses it by creating a simple view function // with the desired public name, such as DOMAIN_SEPARATOR or domainSeparator. // solhint-disable-next-line func-name-mixedcase function _domainSeparator() internal view returns (bytes32) { uint256 chainId; assembly {chainId := chainid()} return chainId == DOMAIN_SEPARATOR_CHAIN_ID ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(chainId); } function _getDigest(bytes32 dataHash) internal view returns (bytes32 digest) { digest = keccak256( abi.encodePacked( EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA, _domainSeparator(), dataHash ) ); } } interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); /// @notice EIP 2612 function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; } // Data part taken out for building of contracts that receive delegate calls contract ERC20Data { /// @notice owner > balance mapping. mapping(address => uint256) public balanceOf; /// @notice owner > spender > allowance mapping. mapping(address => mapping(address => uint256)) public allowance; /// @notice owner > nonce mapping. Used in `permit`. mapping(address => uint256) public nonces; } abstract contract ERC20 is IERC20, Domain { /// @notice owner > balance mapping. mapping(address => uint256) public override balanceOf; /// @notice owner > spender > allowance mapping. mapping(address => mapping(address => uint256)) public override allowance; /// @notice owner > nonce mapping. Used in `permit`. mapping(address => uint256) public nonces; event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); /// @notice Transfers `amount` tokens from `msg.sender` to `to`. /// @param to The address to move the tokens. /// @param amount of the tokens to move. /// @return (bool) Returns True if succeeded. function transfer(address to, uint256 amount) public returns (bool) { // If `amount` is 0, or `msg.sender` is `to` nothing happens if (amount != 0 || msg.sender == to) { uint256 srcBalance = balanceOf[msg.sender]; require(srcBalance >= amount, "ERC20: balance too low"); if (msg.sender != to) { require(to != address(0), "ERC20: no zero address"); // Moved down so low balance calls safe some gas balanceOf[msg.sender] = srcBalance - amount; // Underflow is checked balanceOf[to] += amount; } } emit Transfer(msg.sender, to, amount); return true; } /// @notice Transfers `amount` tokens from `from` to `to`. Caller needs approval for `from`. /// @param from Address to draw tokens from. /// @param to The address to move the tokens. /// @param amount The token amount to move. /// @return (bool) Returns True if succeeded. function transferFrom( address from, address to, uint256 amount ) public returns (bool) { // If `amount` is 0, or `from` is `to` nothing happens if (amount != 0) { uint256 srcBalance = balanceOf[from]; require(srcBalance >= amount, "ERC20: balance too low"); if (from != to) { uint256 spenderAllowance = allowance[from][msg.sender]; // If allowance is infinite, don't decrease it to save on gas (breaks with EIP-20). if (spenderAllowance != type(uint256).max) { require(spenderAllowance >= amount, "ERC20: allowance too low"); allowance[from][msg.sender] = spenderAllowance - amount; // Underflow is checked } require(to != address(0), "ERC20: no zero address"); // Moved down so other failed calls safe some gas balanceOf[from] = srcBalance - amount; // Underflow is checked balanceOf[to] += amount; } } emit Transfer(from, to, amount); return true; } /// @notice Approves `amount` from sender to be spend by `spender`. /// @param spender Address of the party that can draw from msg.sender's account. /// @param amount The maximum collective amount that `spender` can draw. /// @return (bool) Returns True if approved. function approve(address spender, uint256 amount) public override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32) { return _domainSeparator(); } // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 private constant PERMIT_SIGNATURE_HASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; /// @notice Approves `value` from `owner_` to be spend by `spender`. /// @param owner_ Address of the owner. /// @param spender The address of the spender that gets approved to draw from `owner_`. /// @param value The maximum collective amount that `spender` can draw. /// @param deadline This permit must be redeemed before this deadline (UTC timestamp in seconds). function permit( address owner_, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external override { require(owner_ != address(0), "ERC20: Owner cannot be 0"); require(block.timestamp < deadline, "ERC20: Expired"); require( ecrecover(_getDigest(keccak256(abi.encode(PERMIT_SIGNATURE_HASH, owner_, spender, value, nonces[owner_]++, deadline))), v, r, s) == owner_, "ERC20: Invalid Signature" ); allowance[owner_][spender] = value; emit Approval(owner_, spender, value); } } // Contract: BoringMath /// @notice A library for performing overflow-/underflow-safe math, /// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math). library BoringMath { function add(uint256 a, uint256 b) internal pure returns (uint256 c) { require((c = a + b) >= b, "BoringMath: Add Overflow"); } function sub(uint256 a, uint256 b) internal pure returns (uint256 c) { require((c = a - b) <= a, "BoringMath: Underflow"); } function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow"); } } /// @title Spell /// @author 0xMerlin /// @dev This contract spreads Magic. contract Spell is ERC20, BoringOwnable { using BoringMath for uint256; // ERC20 'variables' string public constant symbol = "SPELL"; string public constant name = "Spell Token"; uint8 public constant decimals = 18; uint256 public override totalSupply; uint256 public constant MAX_SUPPLY = 420 * 1e27; function mint(address to, uint256 amount) public onlyOwner { require(to != address(0), "SPELL: no mint to zero address"); require(MAX_SUPPLY >= totalSupply.add(amount), "SPELL: Don't go over MAX"); totalSupply = totalSupply + amount; balanceOf[to] += amount; emit Transfer(address(0), to, amount); } }
File 3 of 3: Vyper_contract
# @version 0.2.4 """ @title Gauge Controller @author Curve Finance @license MIT @notice Controls liquidity gauges and the issuance of coins through the gauges """ # 7 * 86400 seconds - all future times are rounded by week WEEK: constant(uint256) = 604800 # Cannot change weight votes more often than once in 10 days WEIGHT_VOTE_DELAY: constant(uint256) = 10 * 86400 struct Point: bias: uint256 slope: uint256 struct VotedSlope: slope: uint256 power: uint256 end: uint256 interface VotingEscrow: def get_last_user_slope(addr: address) -> int128: view def locked__end(addr: address) -> uint256: view event CommitOwnership: admin: address event ApplyOwnership: admin: address event AddType: name: String[64] type_id: int128 event NewTypeWeight: type_id: int128 time: uint256 weight: uint256 total_weight: uint256 event NewGaugeWeight: gauge_address: address time: uint256 weight: uint256 total_weight: uint256 event VoteForGauge: time: uint256 user: address gauge_addr: address weight: uint256 event NewGauge: addr: address gauge_type: int128 weight: uint256 MULTIPLIER: constant(uint256) = 10 ** 18 admin: public(address) # Can and will be a smart contract future_admin: public(address) # Can and will be a smart contract token: public(address) # CRV token voting_escrow: public(address) # Voting escrow # Gauge parameters # All numbers are "fixed point" on the basis of 1e18 n_gauge_types: public(int128) n_gauges: public(int128) gauge_type_names: public(HashMap[int128, String[64]]) # Needed for enumeration gauges: public(address[1000000000]) # we increment values by 1 prior to storing them here so we can rely on a value # of zero as meaning the gauge has not been set gauge_types_: HashMap[address, int128] vote_user_slopes: public(HashMap[address, HashMap[address, VotedSlope]]) # user -> gauge_addr -> VotedSlope vote_user_power: public(HashMap[address, uint256]) # Total vote power used by user last_user_vote: public(HashMap[address, HashMap[address, uint256]]) # Last user vote's timestamp for each gauge address # Past and scheduled points for gauge weight, sum of weights per type, total weight # Point is for bias+slope # changes_* are for changes in slope # time_* are for the last change timestamp # timestamps are rounded to whole weeks points_weight: public(HashMap[address, HashMap[uint256, Point]]) # gauge_addr -> time -> Point changes_weight: HashMap[address, HashMap[uint256, uint256]] # gauge_addr -> time -> slope time_weight: public(HashMap[address, uint256]) # gauge_addr -> last scheduled time (next week) points_sum: public(HashMap[int128, HashMap[uint256, Point]]) # type_id -> time -> Point changes_sum: HashMap[int128, HashMap[uint256, uint256]] # type_id -> time -> slope time_sum: public(uint256[1000000000]) # type_id -> last scheduled time (next week) points_total: public(HashMap[uint256, uint256]) # time -> total weight time_total: public(uint256) # last scheduled time points_type_weight: public(HashMap[int128, HashMap[uint256, uint256]]) # type_id -> time -> type weight time_type_weight: public(uint256[1000000000]) # type_id -> last scheduled time (next week) @external def __init__(_token: address, _voting_escrow: address): """ @notice Contract constructor @param _token `ERC20CRV` contract address @param _voting_escrow `VotingEscrow` contract address """ assert _token != ZERO_ADDRESS assert _voting_escrow != ZERO_ADDRESS self.admin = msg.sender self.token = _token self.voting_escrow = _voting_escrow self.time_total = block.timestamp / WEEK * WEEK @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 apply_transfer_ownership(): """ @notice Apply pending ownership transfer """ assert msg.sender == self.admin # dev: admin only _admin: address = self.future_admin assert _admin != ZERO_ADDRESS # dev: admin not set self.admin = _admin log ApplyOwnership(_admin) @external @view def gauge_types(_addr: address) -> int128: """ @notice Get gauge type for address @param _addr Gauge address @return Gauge type id """ gauge_type: int128 = self.gauge_types_[_addr] assert gauge_type != 0 return gauge_type - 1 @internal def _get_type_weight(gauge_type: int128) -> uint256: """ @notice Fill historic type weights week-over-week for missed checkins and return the type weight for the future week @param gauge_type Gauge type id @return Type weight """ t: uint256 = self.time_type_weight[gauge_type] if t > 0: w: uint256 = self.points_type_weight[gauge_type][t] for i in range(500): if t > block.timestamp: break t += WEEK self.points_type_weight[gauge_type][t] = w if t > block.timestamp: self.time_type_weight[gauge_type] = t return w else: return 0 @internal def _get_sum(gauge_type: int128) -> uint256: """ @notice Fill sum of gauge weights for the same type week-over-week for missed checkins and return the sum for the future week @param gauge_type Gauge type id @return Sum of weights """ t: uint256 = self.time_sum[gauge_type] if t > 0: pt: Point = self.points_sum[gauge_type][t] for i in range(500): if t > block.timestamp: break t += WEEK d_bias: uint256 = pt.slope * WEEK if pt.bias > d_bias: pt.bias -= d_bias d_slope: uint256 = self.changes_sum[gauge_type][t] pt.slope -= d_slope else: pt.bias = 0 pt.slope = 0 self.points_sum[gauge_type][t] = pt if t > block.timestamp: self.time_sum[gauge_type] = t return pt.bias else: return 0 @internal def _get_total() -> uint256: """ @notice Fill historic total weights week-over-week for missed checkins and return the total for the future week @return Total weight """ t: uint256 = self.time_total _n_gauge_types: int128 = self.n_gauge_types if t > block.timestamp: # If we have already checkpointed - still need to change the value t -= WEEK pt: uint256 = self.points_total[t] for gauge_type in range(100): if gauge_type == _n_gauge_types: break self._get_sum(gauge_type) self._get_type_weight(gauge_type) for i in range(500): if t > block.timestamp: break t += WEEK pt = 0 # Scales as n_types * n_unchecked_weeks (hopefully 1 at most) for gauge_type in range(100): if gauge_type == _n_gauge_types: break type_sum: uint256 = self.points_sum[gauge_type][t].bias type_weight: uint256 = self.points_type_weight[gauge_type][t] pt += type_sum * type_weight self.points_total[t] = pt if t > block.timestamp: self.time_total = t return pt @internal def _get_weight(gauge_addr: address) -> uint256: """ @notice Fill historic gauge weights week-over-week for missed checkins and return the total for the future week @param gauge_addr Address of the gauge @return Gauge weight """ t: uint256 = self.time_weight[gauge_addr] if t > 0: pt: Point = self.points_weight[gauge_addr][t] for i in range(500): if t > block.timestamp: break t += WEEK d_bias: uint256 = pt.slope * WEEK if pt.bias > d_bias: pt.bias -= d_bias d_slope: uint256 = self.changes_weight[gauge_addr][t] pt.slope -= d_slope else: pt.bias = 0 pt.slope = 0 self.points_weight[gauge_addr][t] = pt if t > block.timestamp: self.time_weight[gauge_addr] = t return pt.bias else: return 0 @external def add_gauge(addr: address, gauge_type: int128, weight: uint256 = 0): """ @notice Add gauge `addr` of type `gauge_type` with weight `weight` @param addr Gauge address @param gauge_type Gauge type @param weight Gauge weight """ assert msg.sender == self.admin assert (gauge_type >= 0) and (gauge_type < self.n_gauge_types) assert self.gauge_types_[addr] == 0 # dev: cannot add the same gauge twice n: int128 = self.n_gauges self.n_gauges = n + 1 self.gauges[n] = addr self.gauge_types_[addr] = gauge_type + 1 next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK if weight > 0: _type_weight: uint256 = self._get_type_weight(gauge_type) _old_sum: uint256 = self._get_sum(gauge_type) _old_total: uint256 = self._get_total() self.points_sum[gauge_type][next_time].bias = weight + _old_sum self.time_sum[gauge_type] = next_time self.points_total[next_time] = _old_total + _type_weight * weight self.time_total = next_time self.points_weight[addr][next_time].bias = weight if self.time_sum[gauge_type] == 0: self.time_sum[gauge_type] = next_time self.time_weight[addr] = next_time log NewGauge(addr, gauge_type, weight) @external def checkpoint(): """ @notice Checkpoint to fill data common for all gauges """ self._get_total() @external def checkpoint_gauge(addr: address): """ @notice Checkpoint to fill data for both a specific gauge and common for all gauges @param addr Gauge address """ self._get_weight(addr) self._get_total() @internal @view def _gauge_relative_weight(addr: address, time: uint256) -> uint256: """ @notice Get Gauge relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18). Inflation which will be received by it is inflation_rate * relative_weight / 1e18 @param addr Gauge address @param time Relative weight at the specified timestamp in the past or present @return Value of relative weight normalized to 1e18 """ t: uint256 = time / WEEK * WEEK _total_weight: uint256 = self.points_total[t] if _total_weight > 0: gauge_type: int128 = self.gauge_types_[addr] - 1 _type_weight: uint256 = self.points_type_weight[gauge_type][t] _gauge_weight: uint256 = self.points_weight[addr][t].bias return MULTIPLIER * _type_weight * _gauge_weight / _total_weight else: return 0 @external @view def gauge_relative_weight(addr: address, time: uint256 = block.timestamp) -> uint256: """ @notice Get Gauge relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18). Inflation which will be received by it is inflation_rate * relative_weight / 1e18 @param addr Gauge address @param time Relative weight at the specified timestamp in the past or present @return Value of relative weight normalized to 1e18 """ return self._gauge_relative_weight(addr, time) @external def gauge_relative_weight_write(addr: address, time: uint256 = block.timestamp) -> uint256: """ @notice Get gauge weight normalized to 1e18 and also fill all the unfilled values for type and gauge records @dev Any address can call, however nothing is recorded if the values are filled already @param addr Gauge address @param time Relative weight at the specified timestamp in the past or present @return Value of relative weight normalized to 1e18 """ self._get_weight(addr) self._get_total() # Also calculates get_sum return self._gauge_relative_weight(addr, time) @internal def _change_type_weight(type_id: int128, weight: uint256): """ @notice Change type weight @param type_id Type id @param weight New type weight """ old_weight: uint256 = self._get_type_weight(type_id) old_sum: uint256 = self._get_sum(type_id) _total_weight: uint256 = self._get_total() next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK _total_weight = _total_weight + old_sum * weight - old_sum * old_weight self.points_total[next_time] = _total_weight self.points_type_weight[type_id][next_time] = weight self.time_total = next_time self.time_type_weight[type_id] = next_time log NewTypeWeight(type_id, next_time, weight, _total_weight) @external def add_type(_name: String[64], weight: uint256 = 0): """ @notice Add gauge type with name `_name` and weight `weight` @param _name Name of gauge type @param weight Weight of gauge type """ assert msg.sender == self.admin type_id: int128 = self.n_gauge_types self.gauge_type_names[type_id] = _name self.n_gauge_types = type_id + 1 if weight != 0: self._change_type_weight(type_id, weight) log AddType(_name, type_id) @external def change_type_weight(type_id: int128, weight: uint256): """ @notice Change gauge type `type_id` weight to `weight` @param type_id Gauge type id @param weight New Gauge weight """ assert msg.sender == self.admin self._change_type_weight(type_id, weight) @internal def _change_gauge_weight(addr: address, weight: uint256): # Change gauge weight # Only needed when testing in reality gauge_type: int128 = self.gauge_types_[addr] - 1 old_gauge_weight: uint256 = self._get_weight(addr) type_weight: uint256 = self._get_type_weight(gauge_type) old_sum: uint256 = self._get_sum(gauge_type) _total_weight: uint256 = self._get_total() next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK self.points_weight[addr][next_time].bias = weight self.time_weight[addr] = next_time new_sum: uint256 = old_sum + weight - old_gauge_weight self.points_sum[gauge_type][next_time].bias = new_sum self.time_sum[gauge_type] = next_time _total_weight = _total_weight + new_sum * type_weight - old_sum * type_weight self.points_total[next_time] = _total_weight self.time_total = next_time log NewGaugeWeight(addr, block.timestamp, weight, _total_weight) @external def change_gauge_weight(addr: address, weight: uint256): """ @notice Change weight of gauge `addr` to `weight` @param addr `GaugeController` contract address @param weight New Gauge weight """ assert msg.sender == self.admin self._change_gauge_weight(addr, weight) @external def vote_for_gauge_weights(_gauge_addr: address, _user_weight: uint256): """ @notice Allocate voting power for changing pool weights @param _gauge_addr Gauge which `msg.sender` votes for @param _user_weight Weight for a gauge in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0 """ escrow: address = self.voting_escrow slope: uint256 = convert(VotingEscrow(escrow).get_last_user_slope(msg.sender), uint256) lock_end: uint256 = VotingEscrow(escrow).locked__end(msg.sender) _n_gauges: int128 = self.n_gauges next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK assert lock_end > next_time, "Your token lock expires too soon" assert (_user_weight >= 0) and (_user_weight <= 10000), "You used all your voting power" assert block.timestamp >= self.last_user_vote[msg.sender][_gauge_addr] + WEIGHT_VOTE_DELAY, "Cannot vote so often" gauge_type: int128 = self.gauge_types_[_gauge_addr] - 1 assert gauge_type >= 0, "Gauge not added" # Prepare slopes and biases in memory old_slope: VotedSlope = self.vote_user_slopes[msg.sender][_gauge_addr] old_dt: uint256 = 0 if old_slope.end > next_time: old_dt = old_slope.end - next_time old_bias: uint256 = old_slope.slope * old_dt new_slope: VotedSlope = VotedSlope({ slope: slope * _user_weight / 10000, end: lock_end, power: _user_weight }) new_dt: uint256 = lock_end - next_time # dev: raises when expired new_bias: uint256 = new_slope.slope * new_dt # Check and update powers (weights) used power_used: uint256 = self.vote_user_power[msg.sender] power_used = power_used + new_slope.power - old_slope.power self.vote_user_power[msg.sender] = power_used assert (power_used >= 0) and (power_used <= 10000), 'Used too much power' ## Remove old and schedule new slope changes # Remove slope changes for old slopes # Schedule recording of initial slope for next_time old_weight_bias: uint256 = self._get_weight(_gauge_addr) old_weight_slope: uint256 = self.points_weight[_gauge_addr][next_time].slope old_sum_bias: uint256 = self._get_sum(gauge_type) old_sum_slope: uint256 = self.points_sum[gauge_type][next_time].slope self.points_weight[_gauge_addr][next_time].bias = max(old_weight_bias + new_bias, old_bias) - old_bias self.points_sum[gauge_type][next_time].bias = max(old_sum_bias + new_bias, old_bias) - old_bias if old_slope.end > next_time: self.points_weight[_gauge_addr][next_time].slope = max(old_weight_slope + new_slope.slope, old_slope.slope) - old_slope.slope self.points_sum[gauge_type][next_time].slope = max(old_sum_slope + new_slope.slope, old_slope.slope) - old_slope.slope else: self.points_weight[_gauge_addr][next_time].slope += new_slope.slope self.points_sum[gauge_type][next_time].slope += new_slope.slope if old_slope.end > block.timestamp: # Cancel old slope changes if they still didn't happen self.changes_weight[_gauge_addr][old_slope.end] -= old_slope.slope self.changes_sum[gauge_type][old_slope.end] -= old_slope.slope # Add slope changes for new slopes self.changes_weight[_gauge_addr][new_slope.end] += new_slope.slope self.changes_sum[gauge_type][new_slope.end] += new_slope.slope self._get_total() self.vote_user_slopes[msg.sender][_gauge_addr] = new_slope # Record last action time self.last_user_vote[msg.sender][_gauge_addr] = block.timestamp log VoteForGauge(block.timestamp, msg.sender, _gauge_addr, _user_weight) @external @view def get_gauge_weight(addr: address) -> uint256: """ @notice Get current gauge weight @param addr Gauge address @return Gauge weight """ return self.points_weight[addr][self.time_weight[addr]].bias @external @view def get_type_weight(type_id: int128) -> uint256: """ @notice Get current type weight @param type_id Type id @return Type weight """ return self.points_type_weight[type_id][self.time_type_weight[type_id]] @external @view def get_total_weight() -> uint256: """ @notice Get current total (type-weighted) weight @return Total weight """ return self.points_total[self.time_total] @external @view def get_weights_sum_per_type(type_id: int128) -> uint256: """ @notice Get sum of gauge weights per type @param type_id Type id @return Sum of gauge weights """ return self.points_sum[type_id][self.time_sum[type_id]].bias