ETH Price: $2,146.42 (+2.45%)

Transaction Decoder

Block:
10760701 at Aug-30-2020 06:51:00 AM +UTC
Transaction Fee:
0.00742182465 ETH $15.93
Gas Used:
67,465 Gas / 110.01 Gwei

Emitted Events:

250 Vyper_contract.Transfer( _from=[Receiver] Vyper_contract, _to=[Sender] 0xd026bfdb74fe1baf1e1f1058f0d008cd1eeed8b5, _value=160131344082175901104 )
251 Vyper_contract.Claim( recipient=[Sender] 0xd026bfdb74fe1baf1e1f1058f0d008cd1eeed8b5, claimed=160131344082175901104 )

Account State Difference:

  Address   Before After State Difference Code
0x575CCD8e...000318E2c
(Curve: pre-CRV Liquidity Providers)
49.968718894714414423 Eth49.976140719364414423 Eth0.00742182465
0xd026bFdB...D1EEEd8B5
0.566197147527992858 Eth
Nonce: 817
0.558775322877992858 Eth
Nonce: 818
0.00742182465
0xD533a949...bA034cd52

Execution Trace

Vyper_contract.claim( addr=0xd026bFdB74fe1bAF1E1F1058f0d008cD1EEEd8B5 )
  • Vyper_contract.transfer( _to=0xd026bFdB74fe1bAF1E1F1058f0d008cD1EEEd8B5, _value=160131344082175901104 ) => ( True )
    File 1 of 2: Vyper_contract
    # @version 0.2.4
    """
    @title Vesting Escrow
    @author Curve Finance
    @license MIT
    @notice Vests `ERC20CRV` tokens for multiple addresses over multiple vesting periods
    """
    
    
    from vyper.interfaces import ERC20
    
    event Fund:
        recipient: indexed(address)
        amount: uint256
    
    event Claim:
        recipient: indexed(address)
        claimed: uint256
    
    event ToggleDisable:
        recipient: address
        disabled: bool
    
    event CommitOwnership:
        admin: address
    
    event ApplyOwnership:
        admin: address
    
    
    token: public(address)
    start_time: public(uint256)
    end_time: public(uint256)
    initial_locked: public(HashMap[address, uint256])
    total_claimed: public(HashMap[address, uint256])
    
    initial_locked_supply: public(uint256)
    unallocated_supply: public(uint256)
    
    can_disable: public(bool)
    disabled_at: public(HashMap[address, uint256])
    
    admin: public(address)
    future_admin: public(address)
    
    fund_admins_enabled: public(bool)
    fund_admins: public(HashMap[address, bool])
    
    
    @external
    def __init__(
        _token: address,
        _start_time: uint256,
        _end_time: uint256,
        _can_disable: bool,
        _fund_admins: address[4]
    ):
        """
        @param _token Address of the ERC20 token being distributed
        @param _start_time Timestamp at which the distribution starts. Should be in
            the future, so that we have enough time to VoteLock everyone
        @param _end_time Time until everything should be vested
        @param _can_disable Whether admin can disable accounts in this deployment
        @param _fund_admins Temporary admin accounts used only for funding
        """
        assert _start_time >= block.timestamp
        assert _end_time > _start_time
    
        self.token = _token
        self.admin = msg.sender
        self.start_time = _start_time
        self.end_time = _end_time
        self.can_disable = _can_disable
    
        _fund_admins_enabled: bool = False
        for addr in _fund_admins:
            if addr != ZERO_ADDRESS:
                self.fund_admins[addr] = True
                if not _fund_admins_enabled:
                    _fund_admins_enabled = True
                    self.fund_admins_enabled = True
    
    
    
    @external
    def add_tokens(_amount: uint256):
        """
        @notice Transfer vestable tokens into the contract
        @dev Handled separate from `fund` to reduce transaction count when using funding admins
        @param _amount Number of tokens to transfer
        """
        assert msg.sender == self.admin  # dev: admin only
        assert ERC20(self.token).transferFrom(msg.sender, self, _amount)  # dev: transfer failed
        self.unallocated_supply += _amount
    
    
    @external
    @nonreentrant('lock')
    def fund(_recipients: address[100], _amounts: uint256[100]):
        """
        @notice Vest tokens for multiple recipients
        @param _recipients List of addresses to fund
        @param _amounts Amount of vested tokens for each address
        """
        if msg.sender != self.admin:
            assert self.fund_admins[msg.sender]  # dev: admin only
            assert self.fund_admins_enabled  # dev: fund admins disabled
    
        _total_amount: uint256 = 0
        for i in range(100):
            amount: uint256 = _amounts[i]
            recipient: address = _recipients[i]
            if recipient == ZERO_ADDRESS:
                break
            _total_amount += amount
            self.initial_locked[recipient] += amount
            log Fund(recipient, amount)
    
        self.initial_locked_supply += _total_amount
        self.unallocated_supply -= _total_amount
    
    
    @external
    def toggle_disable(_recipient: address):
        """
        @notice Disable or re-enable a vested address's ability to claim tokens
        @dev When disabled, the address is only unable to claim tokens which are still
             locked at the time of this call. It is not possible to block the claim
             of tokens which have already vested.
        @param _recipient Address to disable or enable
        """
        assert msg.sender == self.admin  # dev: admin only
        assert self.can_disable, "Cannot disable"
    
        is_disabled: bool = self.disabled_at[_recipient] == 0
        if is_disabled:
            self.disabled_at[_recipient] = block.timestamp
        else:
            self.disabled_at[_recipient] = 0
    
        log ToggleDisable(_recipient, is_disabled)
    
    
    @external
    def disable_can_disable():
        """
        @notice Disable the ability to call `toggle_disable`
        """
        assert msg.sender == self.admin  # dev: admin only
        self.can_disable = False
    
    
    @external
    def disable_fund_admins():
        """
        @notice Disable the funding admin accounts
        """
        assert msg.sender == self.admin  # dev: admin only
        self.fund_admins_enabled = False
    
    
    @internal
    @view
    def _total_vested_of(_recipient: address, _time: uint256 = block.timestamp) -> uint256:
        start: uint256 = self.start_time
        end: uint256 = self.end_time
        locked: uint256 = self.initial_locked[_recipient]
        if _time < start:
            return 0
        return min(locked * (_time - start) / (end - start), locked)
    
    
    @internal
    @view
    def _total_vested() -> uint256:
        start: uint256 = self.start_time
        end: uint256 = self.end_time
        locked: uint256 = self.initial_locked_supply
        if block.timestamp < start:
            return 0
        return min(locked * (block.timestamp - start) / (end - start), locked)
    
    
    @external
    @view
    def vestedSupply() -> uint256:
        """
        @notice Get the total number of tokens which have vested, that are held
                by this contract
        """
        return self._total_vested()
    
    
    @external
    @view
    def lockedSupply() -> uint256:
        """
        @notice Get the total number of tokens which are still locked
                (have not yet vested)
        """
        return self.initial_locked_supply - self._total_vested()
    
    
    @external
    @view
    def vestedOf(_recipient: address) -> uint256:
        """
        @notice Get the number of tokens which have vested for a given address
        @param _recipient address to check
        """
        return self._total_vested_of(_recipient)
    
    
    @external
    @view
    def balanceOf(_recipient: address) -> uint256:
        """
        @notice Get the number of unclaimed, vested tokens for a given address
        @param _recipient address to check
        """
        return self._total_vested_of(_recipient) - self.total_claimed[_recipient]
    
    
    @external
    @view
    def lockedOf(_recipient: address) -> uint256:
        """
        @notice Get the number of locked tokens for a given address
        @param _recipient address to check
        """
        return self.initial_locked[_recipient] - self._total_vested_of(_recipient)
    
    
    @external
    @nonreentrant('lock')
    def claim(addr: address = msg.sender):
        """
        @notice Claim tokens which have vested
        @param addr Address to claim tokens for
        """
        t: uint256 = self.disabled_at[addr]
        if t == 0:
            t = block.timestamp
        claimable: uint256 = self._total_vested_of(addr, t) - self.total_claimed[addr]
        self.total_claimed[addr] += claimable
        assert ERC20(self.token).transfer(addr, claimable)
    
        log Claim(addr, claimable)
    
    
    @external
    def commit_transfer_ownership(addr: address) -> bool:
        """
        @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)
    
        return True
    
    
    @external
    def apply_transfer_ownership() -> bool:
        """
        @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)
    
        return True

    File 2 of 2: Vyper_contract
    # @version 0.2.4
    """
    @title Curve DAO Token
    @author Curve Finance
    @license MIT
    @notice ERC20 with piecewise-linear mining supply.
    @dev Based on the ERC-20 token standard as defined at
         https://eips.ethereum.org/EIPS/eip-20
    """
    
    from vyper.interfaces import ERC20
    
    implements: ERC20
    
    
    event Transfer:
        _from: indexed(address)
        _to: indexed(address)
        _value: uint256
    
    event Approval:
        _owner: indexed(address)
        _spender: indexed(address)
        _value: uint256
    
    event UpdateMiningParameters:
        time: uint256
        rate: uint256
        supply: uint256
    
    event SetMinter:
        minter: address
    
    event SetAdmin:
        admin: address
    
    
    name: public(String[64])
    symbol: public(String[32])
    decimals: public(uint256)
    
    balanceOf: public(HashMap[address, uint256])
    allowances: HashMap[address, HashMap[address, uint256]]
    total_supply: uint256
    
    minter: public(address)
    admin: public(address)
    
    # General constants
    YEAR: constant(uint256) = 86400 * 365
    
    # Allocation:
    # =========
    # * shareholders - 30%
    # * emplyees - 3%
    # * DAO-controlled reserve - 5%
    # * Early users - 5%
    # == 43% ==
    # left for inflation: 57%
    
    # Supply parameters
    INITIAL_SUPPLY: constant(uint256) = 1_303_030_303
    INITIAL_RATE: constant(uint256) = 274_815_283 * 10 ** 18 / YEAR  # leading to 43% premine
    RATE_REDUCTION_TIME: constant(uint256) = YEAR
    RATE_REDUCTION_COEFFICIENT: constant(uint256) = 1189207115002721024  # 2 ** (1/4) * 1e18
    RATE_DENOMINATOR: constant(uint256) = 10 ** 18
    INFLATION_DELAY: constant(uint256) = 86400
    
    # Supply variables
    mining_epoch: public(int128)
    start_epoch_time: public(uint256)
    rate: public(uint256)
    
    start_epoch_supply: uint256
    
    
    @external
    def __init__(_name: String[64], _symbol: String[32], _decimals: uint256):
        """
        @notice Contract constructor
        @param _name Token full name
        @param _symbol Token symbol
        @param _decimals Number of decimals for token
        """
        init_supply: uint256 = INITIAL_SUPPLY * 10 ** _decimals
        self.name = _name
        self.symbol = _symbol
        self.decimals = _decimals
        self.balanceOf[msg.sender] = init_supply
        self.total_supply = init_supply
        self.admin = msg.sender
        log Transfer(ZERO_ADDRESS, msg.sender, init_supply)
    
        self.start_epoch_time = block.timestamp + INFLATION_DELAY - RATE_REDUCTION_TIME
        self.mining_epoch = -1
        self.rate = 0
        self.start_epoch_supply = init_supply
    
    
    @internal
    def _update_mining_parameters():
        """
        @dev Update mining rate and supply at the start of the epoch
             Any modifying mining call must also call this
        """
        _rate: uint256 = self.rate
        _start_epoch_supply: uint256 = self.start_epoch_supply
    
        self.start_epoch_time += RATE_REDUCTION_TIME
        self.mining_epoch += 1
    
        if _rate == 0:
            _rate = INITIAL_RATE
        else:
            _start_epoch_supply += _rate * RATE_REDUCTION_TIME
            self.start_epoch_supply = _start_epoch_supply
            _rate = _rate * RATE_DENOMINATOR / RATE_REDUCTION_COEFFICIENT
    
        self.rate = _rate
    
        log UpdateMiningParameters(block.timestamp, _rate, _start_epoch_supply)
    
    
    @external
    def update_mining_parameters():
        """
        @notice Update mining rate and supply at the start of the epoch
        @dev Callable by any address, but only once per epoch
             Total supply becomes slightly larger if this function is called late
        """
        assert block.timestamp >= self.start_epoch_time + RATE_REDUCTION_TIME  # dev: too soon!
        self._update_mining_parameters()
    
    
    @external
    def start_epoch_time_write() -> uint256:
        """
        @notice Get timestamp of the current mining epoch start
                while simultaneously updating mining parameters
        @return Timestamp of the epoch
        """
        _start_epoch_time: uint256 = self.start_epoch_time
        if block.timestamp >= _start_epoch_time + RATE_REDUCTION_TIME:
            self._update_mining_parameters()
            return self.start_epoch_time
        else:
            return _start_epoch_time
    
    
    @external
    def future_epoch_time_write() -> uint256:
        """
        @notice Get timestamp of the next mining epoch start
                while simultaneously updating mining parameters
        @return Timestamp of the next epoch
        """
        _start_epoch_time: uint256 = self.start_epoch_time
        if block.timestamp >= _start_epoch_time + RATE_REDUCTION_TIME:
            self._update_mining_parameters()
            return self.start_epoch_time + RATE_REDUCTION_TIME
        else:
            return _start_epoch_time + RATE_REDUCTION_TIME
    
    
    @internal
    @view
    def _available_supply() -> uint256:
        return self.start_epoch_supply + (block.timestamp - self.start_epoch_time) * self.rate
    
    
    @external
    @view
    def available_supply() -> uint256:
        """
        @notice Current number of tokens in existence (claimed or unclaimed)
        """
        return self._available_supply()
    
    
    @external
    @view
    def mintable_in_timeframe(start: uint256, end: uint256) -> uint256:
        """
        @notice How much supply is mintable from start timestamp till end timestamp
        @param start Start of the time interval (timestamp)
        @param end End of the time interval (timestamp)
        @return Tokens mintable from `start` till `end`
        """
        assert start <= end  # dev: start > end
        to_mint: uint256 = 0
        current_epoch_time: uint256 = self.start_epoch_time
        current_rate: uint256 = self.rate
    
        # Special case if end is in future (not yet minted) epoch
        if end > current_epoch_time + RATE_REDUCTION_TIME:
            current_epoch_time += RATE_REDUCTION_TIME
            current_rate = current_rate * RATE_DENOMINATOR / RATE_REDUCTION_COEFFICIENT
    
        assert end <= current_epoch_time + RATE_REDUCTION_TIME  # dev: too far in future
    
        for i in range(999):  # Curve will not work in 1000 years. Darn!
            if end >= current_epoch_time:
                current_end: uint256 = end
                if current_end > current_epoch_time + RATE_REDUCTION_TIME:
                    current_end = current_epoch_time + RATE_REDUCTION_TIME
    
                current_start: uint256 = start
                if current_start >= current_epoch_time + RATE_REDUCTION_TIME:
                    break  # We should never get here but what if...
                elif current_start < current_epoch_time:
                    current_start = current_epoch_time
    
                to_mint += current_rate * (current_end - current_start)
    
                if start >= current_epoch_time:
                    break
    
            current_epoch_time -= RATE_REDUCTION_TIME
            current_rate = current_rate * RATE_REDUCTION_COEFFICIENT / RATE_DENOMINATOR  # double-division with rounding made rate a bit less => good
            assert current_rate <= INITIAL_RATE  # This should never happen
    
        return to_mint
    
    
    @external
    def set_minter(_minter: address):
        """
        @notice Set the minter address
        @dev Only callable once, when minter has not yet been set
        @param _minter Address of the minter
        """
        assert msg.sender == self.admin  # dev: admin only
        assert self.minter == ZERO_ADDRESS  # dev: can set the minter only once, at creation
        self.minter = _minter
        log SetMinter(_minter)
    
    
    @external
    def set_admin(_admin: address):
        """
        @notice Set the new admin.
        @dev After all is set up, admin only can change the token name
        @param _admin New admin address
        """
        assert msg.sender == self.admin  # dev: admin only
        self.admin = _admin
        log SetAdmin(_admin)
    
    
    @external
    @view
    def totalSupply() -> uint256:
        """
        @notice Total number of tokens in existence.
        """
        return self.total_supply
    
    
    @external
    @view
    def allowance(_owner : address, _spender : address) -> uint256:
        """
        @notice Check the amount of tokens that an owner allowed to a spender
        @param _owner The address which owns the funds
        @param _spender The address which will spend the funds
        @return uint256 specifying the amount of tokens still available for the spender
        """
        return self.allowances[_owner][_spender]
    
    
    @external
    def transfer(_to : address, _value : uint256) -> bool:
        """
        @notice Transfer `_value` tokens from `msg.sender` to `_to`
        @dev Vyper does not allow underflows, so the subtraction in
             this function will revert on an insufficient balance
        @param _to The address to transfer to
        @param _value The amount to be transferred
        @return bool success
        """
        assert _to != ZERO_ADDRESS  # dev: transfers to 0x0 are not allowed
        self.balanceOf[msg.sender] -= _value
        self.balanceOf[_to] += _value
        log Transfer(msg.sender, _to, _value)
        return True
    
    
    @external
    def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
        """
         @notice Transfer `_value` tokens from `_from` to `_to`
         @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
         @return bool success
        """
        assert _to != ZERO_ADDRESS  # dev: transfers to 0x0 are not allowed
        # NOTE: vyper does not allow underflows
        #       so the following subtraction would revert on insufficient balance
        self.balanceOf[_from] -= _value
        self.balanceOf[_to] += _value
        self.allowances[_from][msg.sender] -= _value
        log Transfer(_from, _to, _value)
        return True
    
    
    @external
    def approve(_spender : address, _value : uint256) -> bool:
        """
        @notice Approve `_spender` to transfer `_value` tokens on behalf of `msg.sender`
        @dev Approval may only be from zero -> nonzero or from nonzero -> zero in order
            to mitigate the potential race condition described here:
            https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
        @param _spender The address which will spend the funds
        @param _value The amount of tokens to be spent
        @return bool success
        """
        assert _value == 0 or self.allowances[msg.sender][_spender] == 0
        self.allowances[msg.sender][_spender] = _value
        log Approval(msg.sender, _spender, _value)
        return True
    
    
    @external
    def mint(_to: address, _value: uint256) -> bool:
        """
        @notice Mint `_value` tokens and assign them to `_to`
        @dev Emits a Transfer event originating from 0x00
        @param _to The account that will receive the created tokens
        @param _value The amount that will be created
        @return bool success
        """
        assert msg.sender == self.minter  # dev: minter only
        assert _to != ZERO_ADDRESS  # dev: zero address
    
        if block.timestamp >= self.start_epoch_time + RATE_REDUCTION_TIME:
            self._update_mining_parameters()
    
        _total_supply: uint256 = self.total_supply + _value
        assert _total_supply <= self._available_supply()  # dev: exceeds allowable mint amount
        self.total_supply = _total_supply
    
        self.balanceOf[_to] += _value
        log Transfer(ZERO_ADDRESS, _to, _value)
    
        return True
    
    
    @external
    def burn(_value: uint256) -> bool:
        """
        @notice Burn `_value` tokens belonging to `msg.sender`
        @dev Emits a Transfer event with a destination of 0x00
        @param _value The amount that will be burned
        @return bool success
        """
        self.balanceOf[msg.sender] -= _value
        self.total_supply -= _value
    
        log Transfer(msg.sender, ZERO_ADDRESS, _value)
        return True
    
    
    @external
    def set_name(_name: String[64], _symbol: String[32]):
        """
        @notice Change the token name and symbol to `_name` and `_symbol`
        @dev Only callable by the admin account
        @param _name New token name
        @param _symbol New token symbol
        """
        assert msg.sender == self.admin, "Only admin is allowed to change name"
        self.name = _name
        self.symbol = _symbol