ETH Price: $2,484.00 (+0.64%)

Transaction Decoder

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 Code
0x090185f2...C2D37e5F6
0x7893bbb4...3FfeE1026
(bribe.crv.finance: Gauge Bribe V2)
(F2Pool Old)
1,565.225461001596016419 Eth1,565.225634323596016419 Eth0.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)]
    File 1 of 3: BribeV2
    // 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