ETH Price: $2,508.10 (+1.01%)

Transaction Decoder

Block:
22847623 at Jul-04-2025 06:23:59 PM +UTC
Transaction Fee:
0.000118490119052424 ETH $0.30
Gas Used:
164,568 Gas / 0.720007043 Gwei

Emitted Events:

237 Curve Fee Distribution.Claimed( recipient=[Receiver] 0x4209c9ea64fb4fa437eb950b3839a43c99d96c06, amount=4497451453688833854, claim_epoch=7, max_epoch=7 )
238 crvUSD Stablecoin.Transfer( sender=Curve Fee Distribution, receiver=[Receiver] 0x4209c9ea64fb4fa437eb950b3839a43c99d96c06, value=4497451453688833854 )
239 crvUSD Stablecoin.Transfer( sender=[Receiver] 0x4209c9ea64fb4fa437eb950b3839a43c99d96c06, receiver=0x575a45F4361e937551b05F5287E21069532B2f0E, value=3850000000000000000 )
240 crvUSD Stablecoin.Transfer( sender=[Receiver] 0x4209c9ea64fb4fa437eb950b3839a43c99d96c06, receiver=0xCEe6CF4ca95dd8880e31D974B56a1a5a0c787057, value=1100000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
0x4209C9eA...C99d96c06 From: 22892026899652070454194934764186000560510438816284993843 To: 22892027439715160471879579678024193261900852513025214993
(Titan Builder)
10.794010978196646275 Eth10.794019123868806379 Eth0.000008145672160104
0x63556E77...0f4Ce89D5
(Fake_Phishing1200099)
0.0020441 Eth
Nonce: 184
0.001925609880947576 Eth
Nonce: 185
0.000118490119052424
0xD16d5eC3...0F1027914
(Curve.fi: FeeDistributor)
0xf939E0A0...E57ac1b4E

Execution Trace

0x4209c9ea64fb4fa437eb950b3839a43c99d96c06.252dba42( )
  • Fee.claim( _addr=0x4209C9eA64fb4fA437eb950B3839a43C99d96c06 ) => ( 4497451453688833854 )
    • Vyper_contract.user_point_epoch( arg0=0x4209C9eA64fb4fA437eb950B3839a43C99d96c06 ) => ( 7 )
    • Vyper_contract.user_point_history( arg0=0x4209C9eA64fb4fA437eb950B3839a43C99d96c06, arg1=7 ) => ( bias=32970252070815882309199, slope=1036188800101471, ts=1736616431, blk=21602727 )
    • Stablecoin.transfer( _to=0x4209C9eA64fb4fA437eb950B3839a43C99d96c06, _value=4497451453688833854 ) => ( True )
    • Stablecoin.transfer( _to=0x575a45F4361e937551b05F5287E21069532B2f0E, _value=3850000000000000000 ) => ( True )
    • Stablecoin.transfer( _to=0xCEe6CF4ca95dd8880e31D974B56a1a5a0c787057, _value=1100000000000000000 ) => ( True )
      File 1 of 3: Curve Fee Distribution
      # @version 0.3.7
      """
      @title Curve Fee Distribution
      @author Curve Finance
      @license MIT
      """
      
      from vyper.interfaces import ERC20
      
      
      interface VotingEscrow:
          def user_point_epoch(addr: address) -> uint256: view
          def epoch() -> uint256: view
          def user_point_history(addr: address, loc: uint256) -> Point: view
          def point_history(loc: uint256) -> Point: view
          def checkpoint(): nonpayable
      
      
      event CommitAdmin:
          admin: address
      
      event ApplyAdmin:
          admin: address
      
      event ToggleAllowCheckpointToken:
          toggle_flag: bool
      
      event CheckpointToken:
          time: uint256
          tokens: uint256
      
      event Claimed:
          recipient: indexed(address)
          amount: uint256
          claim_epoch: uint256
          max_epoch: uint256
      
      
      struct Point:
          bias: int128
          slope: int128  # - dweight / dt
          ts: uint256
          blk: uint256  # block
      
      
      WEEK: constant(uint256) = 7 * 86400
      TOKEN_CHECKPOINT_DEADLINE: constant(uint256) = 86400
      
      start_time: public(uint256)
      time_cursor: public(uint256)
      time_cursor_of: public(HashMap[address, uint256])
      user_epoch_of: public(HashMap[address, uint256])
      
      last_token_time: public(uint256)
      tokens_per_week: public(uint256[1000000000000000])
      
      voting_escrow: public(address)
      token: public(address)
      total_received: public(uint256)
      token_last_balance: public(uint256)
      
      ve_supply: public(uint256[1000000000000000])  # VE total supply at week bounds
      
      admin: public(address)
      future_admin: public(address)
      can_checkpoint_token: public(bool)
      emergency_return: public(address)
      is_killed: public(bool)
      
      
      @external
      def __init__(
          _voting_escrow: address,
          _start_time: uint256,
          _token: address,
          _admin: address,
          _emergency_return: address
      ):
          """
          @notice Contract constructor
          @param _voting_escrow VotingEscrow contract address
          @param _start_time Epoch time for fee distribution to start
          @param _token Fee token address (crvUSD)
          @param _admin Admin address
          @param _emergency_return Address to transfer `_token` balance to
                                   if this contract is killed
          """
          t: uint256 = _start_time / WEEK * WEEK
          self.start_time = t
          self.last_token_time = t
          self.time_cursor = t
          self.token = _token
          self.voting_escrow = _voting_escrow
          self.admin = _admin
          self.emergency_return = _emergency_return
      
      
      @internal
      def _checkpoint_token():
          token_balance: uint256 = ERC20(self.token).balanceOf(self)
          to_distribute: uint256 = token_balance - self.token_last_balance
          self.token_last_balance = token_balance
      
          t: uint256 = self.last_token_time
          since_last: uint256 = block.timestamp - t
          self.last_token_time = block.timestamp
          this_week: uint256 = t / WEEK * WEEK
          next_week: uint256 = 0
      
          for i in range(20):
              next_week = this_week + WEEK
              if block.timestamp < next_week:
                  if since_last == 0 and block.timestamp == t:
                      self.tokens_per_week[this_week] += to_distribute
                  else:
                      self.tokens_per_week[this_week] += to_distribute * (block.timestamp - t) / since_last
                  break
              else:
                  if since_last == 0 and next_week == t:
                      self.tokens_per_week[this_week] += to_distribute
                  else:
                      self.tokens_per_week[this_week] += to_distribute * (next_week - t) / since_last
              t = next_week
              this_week = next_week
      
          log CheckpointToken(block.timestamp, to_distribute)
      
      
      @external
      def checkpoint_token():
          """
          @notice Update the token checkpoint
          @dev Calculates the total number of tokens to be distributed in a given week.
               During setup for the initial distribution this function is only callable
               by the contract owner. Beyond initial distro, it can be enabled for anyone
               to call.
          """
          assert (msg.sender == self.admin) or\
                 (self.can_checkpoint_token and (block.timestamp > self.last_token_time + TOKEN_CHECKPOINT_DEADLINE))
          self._checkpoint_token()
      
      
      @internal
      def _find_timestamp_epoch(ve: address, _timestamp: uint256) -> uint256:
          _min: uint256 = 0
          _max: uint256 = VotingEscrow(ve).epoch()
          for i in range(128):
              if _min >= _max:
                  break
              _mid: uint256 = (_min + _max + 2) / 2
              pt: Point = VotingEscrow(ve).point_history(_mid)
              if pt.ts <= _timestamp:
                  _min = _mid
              else:
                  _max = _mid - 1
          return _min
      
      
      @view
      @internal
      def _find_timestamp_user_epoch(ve: address, user: address, _timestamp: uint256, max_user_epoch: uint256) -> uint256:
          _min: uint256 = 0
          _max: uint256 = max_user_epoch
          for i in range(128):
              if _min >= _max:
                  break
              _mid: uint256 = (_min + _max + 2) / 2
              pt: Point = VotingEscrow(ve).user_point_history(user, _mid)
              if pt.ts <= _timestamp:
                  _min = _mid
              else:
                  _max = _mid - 1
          return _min
      
      
      @view
      @external
      def ve_for_at(_user: address, _timestamp: uint256) -> uint256:
          """
          @notice Get the veCRV balance for `_user` at `_timestamp`
          @param _user Address to query balance for
          @param _timestamp Epoch time
          @return uint256 veCRV balance
          """
          ve: address = self.voting_escrow
          max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(_user)
          epoch: uint256 = self._find_timestamp_user_epoch(ve, _user, _timestamp, max_user_epoch)
          pt: Point = VotingEscrow(ve).user_point_history(_user, epoch)
          return convert(max(pt.bias - pt.slope * convert(_timestamp - pt.ts, int128), 0), uint256)
      
      
      @internal
      def _checkpoint_total_supply():
          ve: address = self.voting_escrow
          t: uint256 = self.time_cursor
          rounded_timestamp: uint256 = (block.timestamp - 1) / WEEK * WEEK
          VotingEscrow(ve).checkpoint()
      
          for i in range(20):
              if t > rounded_timestamp:
                  break
              else:
                  epoch: uint256 = self._find_timestamp_epoch(ve, t)
                  pt: Point = VotingEscrow(ve).point_history(epoch)
                  dt: int128 = 0
                  if t > pt.ts:
                      # If the point is at 0 epoch, it can actually be earlier than the first deposit
                      # Then make dt 0
                      dt = convert(t - pt.ts, int128)
                  self.ve_supply[t] = convert(max(pt.bias - pt.slope * dt, 0), uint256)
              t += WEEK
      
          self.time_cursor = t
      
      
      @external
      def checkpoint_total_supply():
          """
          @notice Update the veCRV total supply checkpoint
          @dev The checkpoint is also updated by the first claimant each
               new epoch week. This function may be called independently
               of a claim, to reduce claiming gas costs.
          """
          self._checkpoint_total_supply()
      
      
      @internal
      def _claim(addr: address, ve: address, _last_token_time: uint256) -> uint256:
          # Minimal user_epoch is 0 (if user had no point)
          user_epoch: uint256 = 0
          to_distribute: uint256 = 0
      
          max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(addr)
          _start_time: uint256 = self.start_time
      
          if max_user_epoch == 0:
              # No lock = no fees
              return 0
      
          week_cursor: uint256 = self.time_cursor_of[addr]
          if week_cursor == 0:
              # Need to do the initial binary search
              user_epoch = self._find_timestamp_user_epoch(ve, addr, _start_time, max_user_epoch)
          else:
              user_epoch = self.user_epoch_of[addr]
      
          if user_epoch == 0:
              user_epoch = 1
      
          user_point: Point = VotingEscrow(ve).user_point_history(addr, user_epoch)
      
          if week_cursor == 0:
              week_cursor = (user_point.ts + WEEK - 1) / WEEK * WEEK
      
          if week_cursor >= _last_token_time:
              return 0
      
          if week_cursor < _start_time:
              week_cursor = _start_time
          old_user_point: Point = empty(Point)
      
          # Iterate over weeks
          for i in range(50):
              if week_cursor >= _last_token_time:
                  break
      
              if week_cursor >= user_point.ts and user_epoch <= max_user_epoch:
                  user_epoch += 1
                  old_user_point = user_point
                  if user_epoch > max_user_epoch:
                      user_point = empty(Point)
                  else:
                      user_point = VotingEscrow(ve).user_point_history(addr, user_epoch)
      
              else:
                  # Calc
                  # + i * 2 is for rounding errors
                  dt: int128 = convert(week_cursor - old_user_point.ts, int128)
                  balance_of: uint256 = convert(max(old_user_point.bias - dt * old_user_point.slope, 0), uint256)
                  if balance_of == 0 and user_epoch > max_user_epoch:
                      break
                  if balance_of > 0:
                      to_distribute += balance_of * self.tokens_per_week[week_cursor] / self.ve_supply[week_cursor]
      
                  week_cursor += WEEK
      
          user_epoch = min(max_user_epoch, user_epoch - 1)
          self.user_epoch_of[addr] = user_epoch
          self.time_cursor_of[addr] = week_cursor
      
          log Claimed(addr, to_distribute, user_epoch, max_user_epoch)
      
          return to_distribute
      
      
      @external
      @nonreentrant('lock')
      def claim(_addr: address = msg.sender) -> uint256:
          """
          @notice Claim fees for `_addr`
          @dev Each call to claim look at a maximum of 50 user veCRV points.
               For accounts with many veCRV related actions, this function
               may need to be called more than once to claim all available
               fees. In the `Claimed` event that fires, if `claim_epoch` is
               less than `max_epoch`, the account may claim again.
          @param _addr Address to claim fees for
          @return uint256 Amount of fees claimed in the call
          """
          assert not self.is_killed
      
          if block.timestamp >= self.time_cursor:
              self._checkpoint_total_supply()
      
          last_token_time: uint256 = self.last_token_time
      
          if self.can_checkpoint_token and (block.timestamp > last_token_time + TOKEN_CHECKPOINT_DEADLINE):
              self._checkpoint_token()
              last_token_time = block.timestamp
      
          last_token_time = last_token_time / WEEK * WEEK
      
          amount: uint256 = self._claim(_addr, self.voting_escrow, last_token_time)
          if amount != 0:
              token: address = self.token
              assert ERC20(token).transfer(_addr, amount)
              self.token_last_balance -= amount
      
          return amount
      
      
      @external
      @nonreentrant('lock')
      def claim_many(_receivers: address[20]) -> bool:
          """
          @notice Make multiple fee claims in a single call
          @dev Used to claim for many accounts at once, or to make
               multiple claims for the same address when that address
               has significant veCRV history
          @param _receivers List of addresses to claim for. Claiming
                            terminates at the first `ZERO_ADDRESS`.
          @return bool success
          """
          assert not self.is_killed
      
          if block.timestamp >= self.time_cursor:
              self._checkpoint_total_supply()
      
          last_token_time: uint256 = self.last_token_time
      
          if self.can_checkpoint_token and (block.timestamp > last_token_time + TOKEN_CHECKPOINT_DEADLINE):
              self._checkpoint_token()
              last_token_time = block.timestamp
      
          last_token_time = last_token_time / WEEK * WEEK
          voting_escrow: address = self.voting_escrow
          token: address = self.token
          total: uint256 = 0
      
          for addr in _receivers:
              if addr == ZERO_ADDRESS:
                  break
      
              amount: uint256 = self._claim(addr, voting_escrow, last_token_time)
              if amount != 0:
                  assert ERC20(token).transfer(addr, amount)
                  total += amount
      
          if total != 0:
              self.token_last_balance -= total
      
          return True
      
      
      @external
      def burn(_coin: address) -> bool:
          """
          @notice Receive crvUSD into the contract and trigger a token checkpoint
          @param _coin Address of the coin being received (must be crvUSD)
          @return bool success
          """
          assert _coin == self.token
          assert not self.is_killed
      
          amount: uint256 = ERC20(_coin).balanceOf(msg.sender)
          if amount != 0:
              ERC20(_coin).transferFrom(msg.sender, self, amount)
              if self.can_checkpoint_token and (block.timestamp > self.last_token_time + TOKEN_CHECKPOINT_DEADLINE):
                  self._checkpoint_token()
      
          return True
      
      
      @external
      def commit_admin(_addr: address):
          """
          @notice Commit transfer of ownership
          @param _addr New admin address
          """
          assert msg.sender == self.admin  # dev: access denied
          self.future_admin = _addr
          log CommitAdmin(_addr)
      
      
      @external
      def apply_admin():
          """
          @notice Apply transfer of ownership
          """
          assert msg.sender == self.admin
          assert self.future_admin != ZERO_ADDRESS
          future_admin: address = self.future_admin
          self.admin = future_admin
          log ApplyAdmin(future_admin)
      
      
      @external
      def toggle_allow_checkpoint_token():
          """
          @notice Toggle permission for checkpointing by any account
          """
          assert msg.sender == self.admin
          flag: bool = not self.can_checkpoint_token
          self.can_checkpoint_token = flag
          log ToggleAllowCheckpointToken(flag)
      
      
      @external
      def kill_me():
          """
          @notice Kill the contract
          @dev Killing transfers the entire crvUSD balance to the emergency return address
               and blocks the ability to claim or burn. The contract cannot be unkilled.
          """
          assert msg.sender == self.admin
      
          self.is_killed = True
      
          token: address = self.token
          assert ERC20(token).transfer(self.emergency_return, ERC20(token).balanceOf(self))
      
      
      @external
      def recover_balance(_coin: address) -> bool:
          """
          @notice Recover ERC20 tokens from this contract
          @dev Tokens are sent to the emergency return address.
          @param _coin Token address
          @return bool success
          """
          assert msg.sender == self.admin
          assert _coin != self.token
      
          amount: uint256 = ERC20(_coin).balanceOf(self)
          response: Bytes[32] = raw_call(
              _coin,
              concat(
                  method_id("transfer(address,uint256)"),
                  convert(self.emergency_return, bytes32),
                  convert(amount, bytes32),
              ),
              max_outsize=32,
          )
          if len(response) != 0:
              assert convert(response, bool)
      
          return True

      File 2 of 3: crvUSD Stablecoin
      # @version 0.3.7
      """
      @title crvUSD Stablecoin
      @author Curve.Fi
      @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved
      """
      from vyper.interfaces import ERC20
      
      implements: ERC20
      
      
      interface ERC1271:
          def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes4: view
      
      
      event Approval:
          owner: indexed(address)
          spender: indexed(address)
          value: uint256
      
      event Transfer:
          sender: indexed(address)
          receiver: indexed(address)
          value: uint256
      
      event SetMinter:
          minter: indexed(address)
      
      
      decimals: public(constant(uint8)) = 18
      version: public(constant(String[8])) = "v1.0.0"
      
      ERC1271_MAGIC_VAL: constant(bytes4) = 0x1626ba7e
      EIP712_TYPEHASH: constant(bytes32) = keccak256(
          "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"
      )
      EIP2612_TYPEHASH: constant(bytes32) = keccak256(
          "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
      )
      VERSION_HASH: constant(bytes32) = keccak256(version)
      
      
      name: public(immutable(String[64]))
      symbol: public(immutable(String[32]))
      salt: public(immutable(bytes32))
      
      NAME_HASH: immutable(bytes32)
      CACHED_CHAIN_ID: immutable(uint256)
      CACHED_DOMAIN_SEPARATOR: immutable(bytes32)
      
      
      allowance: public(HashMap[address, HashMap[address, uint256]])
      balanceOf: public(HashMap[address, uint256])
      totalSupply: public(uint256)
      
      nonces: public(HashMap[address, uint256])
      minter: public(address)
      
      
      @external
      def __init__(_name: String[64], _symbol: String[32]):
          name = _name
          symbol = _symbol
      
          NAME_HASH = keccak256(_name)
          CACHED_CHAIN_ID = chain.id
          salt = block.prevhash
          CACHED_DOMAIN_SEPARATOR = keccak256(
              _abi_encode(
                  EIP712_TYPEHASH,
                  keccak256(_name),
                  VERSION_HASH,
                  chain.id,
                  self,
                  block.prevhash,
              )
          )
      
          self.minter = msg.sender
          log SetMinter(msg.sender)
      
      
      @internal
      def _approve(_owner: address, _spender: address, _value: uint256):
          self.allowance[_owner][_spender] = _value
      
          log Approval(_owner, _spender, _value)
      
      
      @internal
      def _burn(_from: address, _value: uint256):
          self.balanceOf[_from] -= _value
          self.totalSupply -= _value
      
          log Transfer(_from, empty(address), _value)
      
      
      @internal
      def _transfer(_from: address, _to: address, _value: uint256):
          assert _to not in [self, empty(address)]
      
          self.balanceOf[_from] -= _value
          self.balanceOf[_to] += _value
      
          log Transfer(_from, _to, _value)
      
      
      @view
      @internal
      def _domain_separator() -> bytes32:
          if chain.id != CACHED_CHAIN_ID:
              return keccak256(
                  _abi_encode(
                      EIP712_TYPEHASH,
                      NAME_HASH,
                      VERSION_HASH,
                      chain.id,
                      self,
                      salt,
                  )
              )
          return CACHED_DOMAIN_SEPARATOR
      
      
      @external
      def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
          """
          @notice Transfer tokens from one account to another.
          @dev The caller needs to have an allowance from account `_from` greater than or
              equal to the value being transferred. An allowance equal to the uint256 type's
              maximum, is considered infinite and does not decrease.
          @param _from The account which tokens will be spent from.
          @param _to The account which tokens will be sent to.
          @param _value The amount of tokens to be transferred.
          """
          allowance: uint256 = self.allowance[_from][msg.sender]
          if allowance != max_value(uint256):
              self._approve(_from, msg.sender, allowance - _value)
      
          self._transfer(_from, _to, _value)
          return True
      
      
      @external
      def transfer(_to: address, _value: uint256) -> bool:
          """
          @notice Transfer tokens to `_to`.
          @param _to The account to transfer tokens to.
          @param _value The amount of tokens to transfer.
          """
          self._transfer(msg.sender, _to, _value)
          return True
      
      
      @external
      def approve(_spender: address, _value: uint256) -> bool:
          """
          @notice Allow `_spender` to transfer up to `_value` amount of tokens from the caller's account.
          @dev Non-zero to non-zero approvals are allowed, but should be used cautiously. The methods
              increaseAllowance + decreaseAllowance are available to prevent any front-running that
              may occur.
          @param _spender The account permitted to spend up to `_value` amount of caller's funds.
          @param _value The amount of tokens `_spender` is allowed to spend.
          """
          self._approve(msg.sender, _spender, _value)
          return True
      
      
      @external
      def permit(
          _owner: address,
          _spender: address,
          _value: uint256,
          _deadline: uint256,
          _v: uint8,
          _r: bytes32,
          _s: bytes32,
      ) -> bool:
          """
          @notice Permit `_spender` to spend up to `_value` amount of `_owner`'s tokens via a signature.
          @dev In the event of a chain fork, replay attacks are prevented as domain separator is recalculated.
              However, this is only if the resulting chains update their chainId.
          @param _owner The account which generated the signature and is granting an allowance.
          @param _spender The account which will be granted an allowance.
          @param _value The approval amount.
          @param _deadline The deadline by which the signature must be submitted.
          @param _v The last byte of the ECDSA signature.
          @param _r The first 32 bytes of the ECDSA signature.
          @param _s The second 32 bytes of the ECDSA signature.
          """
          assert _owner != empty(address) and block.timestamp <= _deadline
      
          nonce: uint256 = self.nonces[_owner]
          digest: bytes32 = keccak256(
              concat(
                  b"\x19\x01",
                  self._domain_separator(),
                  keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline)),
              )
          )
      
          if _owner.is_contract:
              sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1))
              assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL
          else:
              assert ecrecover(digest, _v, _r, _s) == _owner
      
          self.nonces[_owner] = nonce + 1
          self._approve(_owner, _spender, _value)
          return True
      
      
      @external
      def increaseAllowance(_spender: address, _add_value: uint256) -> bool:
          """
          @notice Increase the allowance granted to `_spender`.
          @dev This function will never overflow, and instead will bound
              allowance to MAX_UINT256. This has the potential to grant an
              infinite approval.
          @param _spender The account to increase the allowance of.
          @param _add_value The amount to increase the allowance by.
          """
          cached_allowance: uint256 = self.allowance[msg.sender][_spender]
          allowance: uint256 = unsafe_add(cached_allowance, _add_value)
      
          # check for an overflow
          if allowance < cached_allowance:
              allowance = max_value(uint256)
      
          if allowance != cached_allowance:
              self._approve(msg.sender, _spender, allowance)
      
          return True
      
      
      @external
      def decreaseAllowance(_spender: address, _sub_value: uint256) -> bool:
          """
          @notice Decrease the allowance granted to `_spender`.
          @dev This function will never underflow, and instead will bound
              allowance to 0.
          @param _spender The account to decrease the allowance of.
          @param _sub_value The amount to decrease the allowance by.
          """
          cached_allowance: uint256 = self.allowance[msg.sender][_spender]
          allowance: uint256 = unsafe_sub(cached_allowance, _sub_value)
      
          # check for an underflow
          if cached_allowance < allowance:
              allowance = 0
      
          if allowance != cached_allowance:
              self._approve(msg.sender, _spender, allowance)
      
          return True
      
      
      @external
      def burnFrom(_from: address, _value: uint256) -> bool:
          """
          @notice Burn `_value` amount of tokens from `_from`.
          @dev The caller must have previously been given an allowance by `_from`.
          @param _from The account to burn the tokens from.
          @param _value The amount of tokens to burn.
          """
          allowance: uint256 = self.allowance[_from][msg.sender]
          if allowance != max_value(uint256):
              self._approve(_from, msg.sender, allowance - _value)
      
          self._burn(_from, _value)
          return True
      
      
      @external
      def burn(_value: uint256) -> bool:
          """
          @notice Burn `_value` amount of tokens.
          @param _value The amount of tokens to burn.
          """
          self._burn(msg.sender, _value)
          return True
      
      
      @external
      def mint(_to: address, _value: uint256) -> bool:
          """
          @notice Mint `_value` amount of tokens to `_to`.
          @dev Only callable by an account with minter privileges.
          @param _to The account newly minted tokens are credited to.
          @param _value The amount of tokens to mint.
          """
          assert msg.sender == self.minter
          assert _to not in [self, empty(address)]
      
          self.balanceOf[_to] += _value
          self.totalSupply += _value
      
          log Transfer(empty(address), _to, _value)
          return True
      
      
      @external
      def set_minter(_minter: address):
          assert msg.sender == self.minter
      
          self.minter = _minter
          log SetMinter(_minter)
      
      
      @view
      @external
      def DOMAIN_SEPARATOR() -> bytes32:
          """
          @notice EIP712 domain separator.
          """
          return self._domain_separator()

      File 3 of 3: Vyper_contract
      # @version 0.2.4
      """
      @title Voting Escrow
      @author Curve Finance
      @license MIT
      @notice Votes have a weight depending on time, so that users are
              committed to the future of (whatever they are voting for)
      @dev Vote weight decays linearly over time. Lock time cannot be
           more than `MAXTIME` (4 years).
      """
      
      # Voting escrow to have time-weighted votes
      # Votes have a weight depending on time, so that users are committed
      # to the future of (whatever they are voting for).
      # The weight in this implementation is linear, and lock cannot be more than maxtime:
      # w ^
      # 1 +        /
      #   |      /
      #   |    /
      #   |  /
      #   |/
      # 0 +--------+------> time
      #       maxtime (4 years?)
      
      struct Point:
          bias: int128
          slope: int128  # - dweight / dt
          ts: uint256
          blk: uint256  # block
      # We cannot really do block numbers per se b/c slope is per time, not per block
      # and per block could be fairly bad b/c Ethereum changes blocktimes.
      # What we can do is to extrapolate ***At functions
      
      struct LockedBalance:
          amount: int128
          end: uint256
      
      
      interface ERC20:
          def decimals() -> uint256: view
          def name() -> String[64]: view
          def symbol() -> String[32]: view
          def transfer(to: address, amount: uint256) -> bool: nonpayable
          def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable
      
      
      # Interface for checking whether address belongs to a whitelisted
      # type of a smart wallet.
      # When new types are added - the whole contract is changed
      # The check() method is modifying to be able to use caching
      # for individual wallet addresses
      interface SmartWalletChecker:
          def check(addr: address) -> bool: nonpayable
      
      DEPOSIT_FOR_TYPE: constant(int128) = 0
      CREATE_LOCK_TYPE: constant(int128) = 1
      INCREASE_LOCK_AMOUNT: constant(int128) = 2
      INCREASE_UNLOCK_TIME: constant(int128) = 3
      
      
      event CommitOwnership:
          admin: address
      
      event ApplyOwnership:
          admin: address
      
      event Deposit:
          provider: indexed(address)
          value: uint256
          locktime: indexed(uint256)
          type: int128
          ts: uint256
      
      event Withdraw:
          provider: indexed(address)
          value: uint256
          ts: uint256
      
      event Supply:
          prevSupply: uint256
          supply: uint256
      
      
      WEEK: constant(uint256) = 7 * 86400  # all future times are rounded by week
      MAXTIME: constant(uint256) = 4 * 365 * 86400  # 4 years
      MULTIPLIER: constant(uint256) = 10 ** 18
      
      token: public(address)
      supply: public(uint256)
      
      locked: public(HashMap[address, LockedBalance])
      
      epoch: public(uint256)
      point_history: public(Point[100000000000000000000000000000])  # epoch -> unsigned point
      user_point_history: public(HashMap[address, Point[1000000000]])  # user -> Point[user_epoch]
      user_point_epoch: public(HashMap[address, uint256])
      slope_changes: public(HashMap[uint256, int128])  # time -> signed slope change
      
      # Aragon's view methods for compatibility
      controller: public(address)
      transfersEnabled: public(bool)
      
      name: public(String[64])
      symbol: public(String[32])
      version: public(String[32])
      decimals: public(uint256)
      
      # Checker for whitelisted (smart contract) wallets which are allowed to deposit
      # The goal is to prevent tokenizing the escrow
      future_smart_wallet_checker: public(address)
      smart_wallet_checker: public(address)
      
      admin: public(address)  # Can and will be a smart contract
      future_admin: public(address)
      
      
      @external
      def __init__(token_addr: address, _name: String[64], _symbol: String[32], _version: String[32]):
          """
          @notice Contract constructor
          @param token_addr `ERC20CRV` token address
          @param _name Token name
          @param _symbol Token symbol
          @param _version Contract version - required for Aragon compatibility
          """
          self.admin = msg.sender
          self.token = token_addr
          self.point_history[0].blk = block.number
          self.point_history[0].ts = block.timestamp
          self.controller = msg.sender
          self.transfersEnabled = True
      
          _decimals: uint256 = ERC20(token_addr).decimals()
          assert _decimals <= 255
          self.decimals = _decimals
      
          self.name = _name
          self.symbol = _symbol
          self.version = _version
      
      
      @external
      def commit_transfer_ownership(addr: address):
          """
          @notice Transfer ownership of VotingEscrow contract 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 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
      def commit_smart_wallet_checker(addr: address):
          """
          @notice Set an external contract to check for approved smart contract wallets
          @param addr Address of Smart contract checker
          """
          assert msg.sender == self.admin
          self.future_smart_wallet_checker = addr
      
      
      @external
      def apply_smart_wallet_checker():
          """
          @notice Apply setting external contract to check approved smart contract wallets
          """
          assert msg.sender == self.admin
          self.smart_wallet_checker = self.future_smart_wallet_checker
      
      
      @internal
      def assert_not_contract(addr: address):
          """
          @notice Check if the call is from a whitelisted smart contract, revert if not
          @param addr Address to be checked
          """
          if addr != tx.origin:
              checker: address = self.smart_wallet_checker
              if checker != ZERO_ADDRESS:
                  if SmartWalletChecker(checker).check(addr):
                      return
              raise "Smart contract depositors not allowed"
      
      
      @external
      @view
      def get_last_user_slope(addr: address) -> int128:
          """
          @notice Get the most recently recorded rate of voting power decrease for `addr`
          @param addr Address of the user wallet
          @return Value of the slope
          """
          uepoch: uint256 = self.user_point_epoch[addr]
          return self.user_point_history[addr][uepoch].slope
      
      
      @external
      @view
      def user_point_history__ts(_addr: address, _idx: uint256) -> uint256:
          """
          @notice Get the timestamp for checkpoint `_idx` for `_addr`
          @param _addr User wallet address
          @param _idx User epoch number
          @return Epoch time of the checkpoint
          """
          return self.user_point_history[_addr][_idx].ts
      
      
      @external
      @view
      def locked__end(_addr: address) -> uint256:
          """
          @notice Get timestamp when `_addr`'s lock finishes
          @param _addr User wallet
          @return Epoch time of the lock end
          """
          return self.locked[_addr].end
      
      
      @internal
      def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance):
          """
          @notice Record global and per-user data to checkpoint
          @param addr User's wallet address. No user checkpoint if 0x0
          @param old_locked Pevious locked amount / end lock time for the user
          @param new_locked New locked amount / end lock time for the user
          """
          u_old: Point = empty(Point)
          u_new: Point = empty(Point)
          old_dslope: int128 = 0
          new_dslope: int128 = 0
          _epoch: uint256 = self.epoch
      
          if addr != ZERO_ADDRESS:
              # Calculate slopes and biases
              # Kept at zero when they have to
              if old_locked.end > block.timestamp and old_locked.amount > 0:
                  u_old.slope = old_locked.amount / MAXTIME
                  u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128)
              if new_locked.end > block.timestamp and new_locked.amount > 0:
                  u_new.slope = new_locked.amount / MAXTIME
                  u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128)
      
              # Read values of scheduled changes in the slope
              # old_locked.end can be in the past and in the future
              # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros
              old_dslope = self.slope_changes[old_locked.end]
              if new_locked.end != 0:
                  if new_locked.end == old_locked.end:
                      new_dslope = old_dslope
                  else:
                      new_dslope = self.slope_changes[new_locked.end]
      
          last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number})
          if _epoch > 0:
              last_point = self.point_history[_epoch]
          last_checkpoint: uint256 = last_point.ts
          # initial_last_point is used for extrapolation to calculate block number
          # (approximately, for *At methods) and save them
          # as we cannot figure that out exactly from inside the contract
          initial_last_point: Point = last_point
          block_slope: uint256 = 0  # dblock/dt
          if block.timestamp > last_point.ts:
              block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts)
          # If last point is already recorded in this block, slope=0
          # But that's ok b/c we know the block in such case
      
          # Go over weeks to fill history and calculate what the current point is
          t_i: uint256 = (last_checkpoint / WEEK) * WEEK
          for i in range(255):
              # Hopefully it won't happen that this won't get used in 5 years!
              # If it does, users will be able to withdraw but vote weight will be broken
              t_i += WEEK
              d_slope: int128 = 0
              if t_i > block.timestamp:
                  t_i = block.timestamp
              else:
                  d_slope = self.slope_changes[t_i]
              last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128)
              last_point.slope += d_slope
              if last_point.bias < 0:  # This can happen
                  last_point.bias = 0
              if last_point.slope < 0:  # This cannot happen - just in case
                  last_point.slope = 0
              last_checkpoint = t_i
              last_point.ts = t_i
              last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER
              _epoch += 1
              if t_i == block.timestamp:
                  last_point.blk = block.number
                  break
              else:
                  self.point_history[_epoch] = last_point
      
          self.epoch = _epoch
          # Now point_history is filled until t=now
      
          if addr != ZERO_ADDRESS:
              # If last point was in this block, the slope change has been applied already
              # But in such case we have 0 slope(s)
              last_point.slope += (u_new.slope - u_old.slope)
              last_point.bias += (u_new.bias - u_old.bias)
              if last_point.slope < 0:
                  last_point.slope = 0
              if last_point.bias < 0:
                  last_point.bias = 0
      
          # Record the changed point into history
          self.point_history[_epoch] = last_point
      
          if addr != ZERO_ADDRESS:
              # Schedule the slope changes (slope is going down)
              # We subtract new_user_slope from [new_locked.end]
              # and add old_user_slope to [old_locked.end]
              if old_locked.end > block.timestamp:
                  # old_dslope was <something> - u_old.slope, so we cancel that
                  old_dslope += u_old.slope
                  if new_locked.end == old_locked.end:
                      old_dslope -= u_new.slope  # It was a new deposit, not extension
                  self.slope_changes[old_locked.end] = old_dslope
      
              if new_locked.end > block.timestamp:
                  if new_locked.end > old_locked.end:
                      new_dslope -= u_new.slope  # old slope disappeared at this point
                      self.slope_changes[new_locked.end] = new_dslope
                  # else: we recorded it already in old_dslope
      
              # Now handle user history
              user_epoch: uint256 = self.user_point_epoch[addr] + 1
      
              self.user_point_epoch[addr] = user_epoch
              u_new.ts = block.timestamp
              u_new.blk = block.number
              self.user_point_history[addr][user_epoch] = u_new
      
      
      @internal
      def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128):
          """
          @notice Deposit and lock tokens for a user
          @param _addr User's wallet address
          @param _value Amount to deposit
          @param unlock_time New time when to unlock the tokens, or 0 if unchanged
          @param locked_balance Previous locked amount / timestamp
          """
          _locked: LockedBalance = locked_balance
          supply_before: uint256 = self.supply
      
          self.supply = supply_before + _value
          old_locked: LockedBalance = _locked
          # Adding to existing lock, or if a lock is expired - creating a new one
          _locked.amount += convert(_value, int128)
          if unlock_time != 0:
              _locked.end = unlock_time
          self.locked[_addr] = _locked
      
          # Possibilities:
          # Both old_locked.end could be current or expired (>/< block.timestamp)
          # value == 0 (extend lock) or value > 0 (add to lock or extend lock)
          # _locked.end > block.timestamp (always)
          self._checkpoint(_addr, old_locked, _locked)
      
          if _value != 0:
              assert ERC20(self.token).transferFrom(_addr, self, _value)
      
          log Deposit(_addr, _value, _locked.end, type, block.timestamp)
          log Supply(supply_before, supply_before + _value)
      
      
      @external
      def checkpoint():
          """
          @notice Record global data to checkpoint
          """
          self._checkpoint(ZERO_ADDRESS, empty(LockedBalance), empty(LockedBalance))
      
      
      @external
      @nonreentrant('lock')
      def deposit_for(_addr: address, _value: uint256):
          """
          @notice Deposit `_value` tokens for `_addr` and add to the lock
          @dev Anyone (even a smart contract) can deposit for someone else, but
               cannot extend their locktime and deposit for a brand new user
          @param _addr User's wallet address
          @param _value Amount to add to user's lock
          """
          _locked: LockedBalance = self.locked[_addr]
      
          assert _value > 0  # dev: need non-zero value
          assert _locked.amount > 0, "No existing lock found"
          assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"
      
          self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE)
      
      
      @external
      @nonreentrant('lock')
      def create_lock(_value: uint256, _unlock_time: uint256):
          """
          @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time`
          @param _value Amount to deposit
          @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks
          """
          self.assert_not_contract(msg.sender)
          unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks
          _locked: LockedBalance = self.locked[msg.sender]
      
          assert _value > 0  # dev: need non-zero value
          assert _locked.amount == 0, "Withdraw old tokens first"
          assert unlock_time > block.timestamp, "Can only lock until time in the future"
          assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max"
      
          self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE)
      
      
      @external
      @nonreentrant('lock')
      def increase_amount(_value: uint256):
          """
          @notice Deposit `_value` additional tokens for `msg.sender`
                  without modifying the unlock time
          @param _value Amount of tokens to deposit and add to the lock
          """
          self.assert_not_contract(msg.sender)
          _locked: LockedBalance = self.locked[msg.sender]
      
          assert _value > 0  # dev: need non-zero value
          assert _locked.amount > 0, "No existing lock found"
          assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"
      
          self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT)
      
      
      @external
      @nonreentrant('lock')
      def increase_unlock_time(_unlock_time: uint256):
          """
          @notice Extend the unlock time for `msg.sender` to `_unlock_time`
          @param _unlock_time New epoch time for unlocking
          """
          self.assert_not_contract(msg.sender)
          _locked: LockedBalance = self.locked[msg.sender]
          unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks
      
          assert _locked.end > block.timestamp, "Lock expired"
          assert _locked.amount > 0, "Nothing is locked"
          assert unlock_time > _locked.end, "Can only increase lock duration"
          assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max"
      
          self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME)
      
      
      @external
      @nonreentrant('lock')
      def withdraw():
          """
          @notice Withdraw all tokens for `msg.sender`
          @dev Only possible if the lock has expired
          """
          _locked: LockedBalance = self.locked[msg.sender]
          assert block.timestamp >= _locked.end, "The lock didn't expire"
          value: uint256 = convert(_locked.amount, uint256)
      
          old_locked: LockedBalance = _locked
          _locked.end = 0
          _locked.amount = 0
          self.locked[msg.sender] = _locked
          supply_before: uint256 = self.supply
          self.supply = supply_before - value
      
          # old_locked can have either expired <= timestamp or zero end
          # _locked has only 0 end
          # Both can have >= 0 amount
          self._checkpoint(msg.sender, old_locked, _locked)
      
          assert ERC20(self.token).transfer(msg.sender, value)
      
          log Withdraw(msg.sender, value, block.timestamp)
          log Supply(supply_before, supply_before - value)
      
      
      # The following ERC20/minime-compatible methods are not real balanceOf and supply!
      # They measure the weights for the purpose of voting, so they don't represent
      # real coins.
      
      @internal
      @view
      def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256:
          """
          @notice Binary search to estimate timestamp for block number
          @param _block Block to find
          @param max_epoch Don't go beyond this epoch
          @return Approximate timestamp for block
          """
          # Binary search
          _min: uint256 = 0
          _max: uint256 = max_epoch
          for i in range(128):  # Will be always enough for 128-bit numbers
              if _min >= _max:
                  break
              _mid: uint256 = (_min + _max + 1) / 2
              if self.point_history[_mid].blk <= _block:
                  _min = _mid
              else:
                  _max = _mid - 1
          return _min
      
      
      @external
      @view
      def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256:
          """
          @notice Get the current voting power for `msg.sender`
          @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
          @param addr User wallet address
          @param _t Epoch time to return voting power at
          @return User voting power
          """
          _epoch: uint256 = self.user_point_epoch[addr]
          if _epoch == 0:
              return 0
          else:
              last_point: Point = self.user_point_history[addr][_epoch]
              last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128)
              if last_point.bias < 0:
                  last_point.bias = 0
              return convert(last_point.bias, uint256)
      
      
      @external
      @view
      def balanceOfAt(addr: address, _block: uint256) -> uint256:
          """
          @notice Measure voting power of `addr` at block height `_block`
          @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
          @param addr User's wallet address
          @param _block Block to calculate the voting power at
          @return Voting power
          """
          # Copying and pasting totalSupply code because Vyper cannot pass by
          # reference yet
          assert _block <= block.number
      
          # Binary search
          _min: uint256 = 0
          _max: uint256 = self.user_point_epoch[addr]
          for i in range(128):  # Will be always enough for 128-bit numbers
              if _min >= _max:
                  break
              _mid: uint256 = (_min + _max + 1) / 2
              if self.user_point_history[addr][_mid].blk <= _block:
                  _min = _mid
              else:
                  _max = _mid - 1
      
          upoint: Point = self.user_point_history[addr][_min]
      
          max_epoch: uint256 = self.epoch
          _epoch: uint256 = self.find_block_epoch(_block, max_epoch)
          point_0: Point = self.point_history[_epoch]
          d_block: uint256 = 0
          d_t: uint256 = 0
          if _epoch < max_epoch:
              point_1: Point = self.point_history[_epoch + 1]
              d_block = point_1.blk - point_0.blk
              d_t = point_1.ts - point_0.ts
          else:
              d_block = block.number - point_0.blk
              d_t = block.timestamp - point_0.ts
          block_time: uint256 = point_0.ts
          if d_block != 0:
              block_time += d_t * (_block - point_0.blk) / d_block
      
          upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128)
          if upoint.bias >= 0:
              return convert(upoint.bias, uint256)
          else:
              return 0
      
      
      @internal
      @view
      def supply_at(point: Point, t: uint256) -> uint256:
          """
          @notice Calculate total voting power at some point in the past
          @param point The point (bias/slope) to start search from
          @param t Time to calculate the total voting power at
          @return Total voting power at that time
          """
          last_point: Point = point
          t_i: uint256 = (last_point.ts / WEEK) * WEEK
          for i in range(255):
              t_i += WEEK
              d_slope: int128 = 0
              if t_i > t:
                  t_i = t
              else:
                  d_slope = self.slope_changes[t_i]
              last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128)
              if t_i == t:
                  break
              last_point.slope += d_slope
              last_point.ts = t_i
      
          if last_point.bias < 0:
              last_point.bias = 0
          return convert(last_point.bias, uint256)
      
      
      @external
      @view
      def totalSupply(t: uint256 = block.timestamp) -> uint256:
          """
          @notice Calculate total voting power
          @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
          @return Total voting power
          """
          _epoch: uint256 = self.epoch
          last_point: Point = self.point_history[_epoch]
          return self.supply_at(last_point, t)
      
      
      @external
      @view
      def totalSupplyAt(_block: uint256) -> uint256:
          """
          @notice Calculate total voting power at some point in the past
          @param _block Block to calculate the total voting power at
          @return Total voting power at `_block`
          """
          assert _block <= block.number
          _epoch: uint256 = self.epoch
          target_epoch: uint256 = self.find_block_epoch(_block, _epoch)
      
          point: Point = self.point_history[target_epoch]
          dt: uint256 = 0
          if target_epoch < _epoch:
              point_next: Point = self.point_history[target_epoch + 1]
              if point.blk != point_next.blk:
                  dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk)
          else:
              if point.blk != block.number:
                  dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk)
          # Now dt contains info on how far are we beyond point
      
          return self.supply_at(point, point.ts + dt)
      
      
      # Dummy methods for compatibility with Aragon
      
      @external
      def changeController(_newController: address):
          """
          @dev Dummy method required for Aragon compatibility
          """
          assert msg.sender == self.controller
          self.controller = _newController