ETH Price: $2,002.10 (-1.04%)

Transaction Decoder

Block:
20938897 at Oct-11-2024 12:47:59 AM +UTC
Transaction Fee:
0.002994585072913176 ETH $6.00
Gas Used:
422,797 Gas / 7.082796408 Gwei

Emitted Events:

514 0x222d910ef37c06774e1edb9dc9459664f73776f0.0x7ecd84343f76a23d2227290e0288da3251b045541698e575a5515af4f04197a3( 0x7ecd84343f76a23d2227290e0288da3251b045541698e575a5515af4f04197a3, 0x0000000000000000000000009f1920d0cbb63ed03376a1e09fd2851d601234c8, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000001a68023e41d85b20f80f2e1, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000001473ae1dc09fc204dba49fa )
515 0x8cf1de26729cfb7137af1a6b2a665e099ec319b5.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000222d910ef37c06774e1edb9dc9459664f73776f0, 0x0000000000000000000000009f1920d0cbb63ed03376a1e09fd2851d601234c8, 0000000000000000000000000000000000000000004fad7791e3247d3e6b47b0 )
516 0x222d910ef37c06774e1edb9dc9459664f73776f0.0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364( 0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364, 0x0000000000000000000000009f1920d0cbb63ed03376a1e09fd2851d601234c8, 0000000000000000000000000000000000000000004fad7791e3247d3e6b47b0 )
517 0x222d910ef37c06774e1edb9dc9459664f73776f0.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000009f1920d0cbb63ed03376a1e09fd2851d601234c8, 0x0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000004fad7791e3247d3e6b47b0 )

Account State Difference:

  Address   Before After State Difference Code
0x222D910e...4f73776f0
0x2F50D538...C9D5846bB
(Curve: Gauge Controller)
0x8cf1DE26...99EC319b5
(beaverbuild)
7.328613420768948111 Eth7.328642593761948111 Eth0.000029172993
0x9f1920d0...D601234C8
1.658321525434802838 Eth
Nonce: 2070
1.655326940361889662 Eth
Nonce: 2071
0.002994585072913176

Execution Trace

0x222d910ef37c06774e1edb9dc9459664f73776f0.2e1a7d4d( )
  • Vyper_contract.CALL( )
  • Vyper_contract.STATICCALL( )
  • Vyper_contract.checkpoint_gauge( addr=0x222D910ef37C06774E1eDB9DC9459664f73776f0 )
  • Vyper_contract.gauge_relative_weight( addr=0x222D910ef37C06774E1eDB9DC9459664f73776f0, time=1727743871 ) => ( 0 )
  • Vyper_contract.gauge_relative_weight( addr=0x222D910ef37C06774E1eDB9DC9459664f73776f0, time=1727913600 ) => ( 0 )
  • Vyper_contract.gauge_relative_weight( addr=0x222D910ef37C06774E1eDB9DC9459664f73776f0, time=1728518400 ) => ( 0 )
  • Vyper_contract.adjusted_balance_of( _account=0x9f1920d0cbB63Ed03376A1e09FD2851D601234C8 ) => ( 6075893454731441171078 )
    • 0xd37a6aa3d8460bd2b6536d608103d880695a23cd.bbf7408a( )
      • Vyper_contract.balanceOf( addr=0x9f1920d0cbB63Ed03376A1e09FD2851D601234C8 ) => ( 6075893454731441171078 )
      • Vyper_contract.STATICCALL( )
      • 0x8cf1de26729cfb7137af1a6b2a665e099ec319b5.a9059cbb( )
        • Vault.transfer( _to=0x9f1920d0cbB63Ed03376A1e09FD2851D601234C8, _value=96324314825923000409999280 ) => ( True )
          File 1 of 5: 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

          File 2 of 5: 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

          File 3 of 5: Vyper_contract
          # @version 0.2.15
          """
          @title Voting Escrow Delegation Proxy
          @author Curve Finance
          @license MIT
          """
          
          from vyper.interfaces import ERC20
          
          
          interface VeDelegation:
              def adjusted_balance_of(_account: address) -> uint256: view
          
          
          event CommitAdmins:
              ownership_admin: address
              emergency_admin: address
          
          event ApplyAdmins:
              ownership_admin: address
              emergency_admin: address
          
          event DelegationSet:
              delegation: address
          
          
          VOTING_ESCROW: constant(address) = 0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2
          
          
          delegation: public(address)
          
          emergency_admin: public(address)
          ownership_admin: public(address)
          future_emergency_admin: public(address)
          future_ownership_admin: public(address)
          
          
          @external
          def __init__(_delegation: address, _o_admin: address, _e_admin: address):
              self.delegation = _delegation
          
              self.ownership_admin = _o_admin
              self.emergency_admin = _e_admin
          
              log DelegationSet(_delegation)
          
          
          @view
          @external
          def adjusted_balance_of(_account: address) -> uint256:
              """
              @notice Get the adjusted veCRV balance from the active boost delegation contract
              @param _account The account to query the adjusted veCRV balance of
              @return veCRV balance
              """
              _delegation: address = self.delegation
              if _delegation == ZERO_ADDRESS:
                  return ERC20(VOTING_ESCROW).balanceOf(_account)
              return VeDelegation(_delegation).adjusted_balance_of(_account)
          
          
          @external
          def kill_delegation():
              """
              @notice Set delegation contract to 0x00, disabling boost delegation
              @dev Callable by the emergency admin in case of an issue with the delegation logic
              """
              assert msg.sender in [self.ownership_admin, self.emergency_admin]
          
              self.delegation = ZERO_ADDRESS
              log DelegationSet(ZERO_ADDRESS)
          
          
          @external
          def set_delegation(_delegation: address):
              """
              @notice Set the delegation contract
              @dev Only callable by the ownership admin
              @param _delegation `VotingEscrowDelegation` deployment address
              """
              assert msg.sender == self.ownership_admin
          
              # call `adjusted_balance_of` to make sure it works
              VeDelegation(_delegation).adjusted_balance_of(msg.sender)
          
              self.delegation = _delegation
              log DelegationSet(_delegation)
          
          
          @external
          def commit_set_admins(_o_admin: address, _e_admin: address):
              """
              @notice Set ownership admin to `_o_admin` and emergency admin to `_e_admin`
              @param _o_admin Ownership admin
              @param _e_admin Emergency admin
              """
              assert msg.sender == self.ownership_admin, "Access denied"
          
              self.future_ownership_admin = _o_admin
              self.future_emergency_admin = _e_admin
          
              log CommitAdmins(_o_admin, _e_admin)
          
          
          @external
          def apply_set_admins():
              """
              @notice Apply the effects of `commit_set_admins`
              """
              assert msg.sender == self.ownership_admin, "Access denied"
          
              _o_admin: address = self.future_ownership_admin
              _e_admin: address = self.future_emergency_admin
              self.ownership_admin = _o_admin
              self.emergency_admin = _e_admin
          
              log ApplyAdmins(_o_admin, _e_admin)

          File 4 of 5: 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

          File 5 of 5: Vault
          # @version 0.3.10
          """
          @title Vault
          @notice ERC4626+ Vault for lending with crvUSD using LLAMMA algorithm
          @author Curve.Fi
          @license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved
          """
          from vyper.interfaces import ERC20 as ERC20Spec
          from vyper.interfaces import ERC20Detailed
          
          
          implements: ERC20Spec
          implements: ERC20Detailed
          
          
          interface ERC20:
              def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable
              def transfer(_to: address, _value: uint256) -> bool: nonpayable
              def decimals() -> uint256: view
              def balanceOf(_from: address) -> uint256: view
              def symbol() -> String[32]: view
              def name() -> String[64]: view
          
          interface AMM:
              def set_admin(_admin: address): nonpayable
              def rate() -> uint256: view
          
          interface Controller:
              def total_debt() -> uint256: view
              def minted() -> uint256: view
              def redeemed() -> uint256: view
              def monetary_policy() -> address: view
              def check_lock() -> bool: view
              def save_rate(): nonpayable
          
          interface PriceOracle:
              def price() -> uint256: view
              def price_w() -> uint256: nonpayable
          
          interface Factory:
              def admin() -> address: view
          
          
          # ERC20 events
          
          event Approval:
              owner: indexed(address)
              spender: indexed(address)
              value: uint256
          
          event Transfer:
              sender: indexed(address)
              receiver: indexed(address)
              value: uint256
          
          # ERC4626 events
          
          event Deposit:
              sender: indexed(address)
              owner: indexed(address)
              assets: uint256
              shares: uint256
          
          event Withdraw:
              sender: indexed(address)
              receiver: indexed(address)
              owner: indexed(address)
              assets: uint256
              shares: uint256
          
          
          # Limits
          MIN_A: constant(uint256) = 2
          MAX_A: constant(uint256) = 10000
          MIN_FEE: constant(uint256) = 10**6  # 1e-12, still needs to be above 0
          MAX_FEE: constant(uint256) = 10**17  # 10%
          MAX_LOAN_DISCOUNT: constant(uint256) = 5 * 10**17
          MIN_LIQUIDATION_DISCOUNT: constant(uint256) = 10**16
          ADMIN_FEE: constant(uint256) = 0
          
          # These are virtual shares from method proposed by OpenZeppelin
          # see: https://blog.openzeppelin.com/a-novel-defense-against-erc4626-inflation-attacks
          # and
          # https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol
          DEAD_SHARES: constant(uint256) = 1000
          MIN_ASSETS: constant(uint256) = 10000
          
          borrowed_token: public(ERC20)
          collateral_token: public(ERC20)
          
          price_oracle: public(PriceOracle)
          amm: public(AMM)
          controller: public(Controller)
          factory: public(Factory)
          
          
          # ERC20 publics
          
          decimals: public(constant(uint8)) = 18
          name: public(String[64])
          symbol: public(String[34])
          
          NAME_PREFIX: constant(String[16]) = 'Curve Vault for '
          SYMBOL_PREFIX: constant(String[2]) = 'cv'
          
          allowance: public(HashMap[address, HashMap[address, uint256]])
          balanceOf: public(HashMap[address, uint256])
          totalSupply: public(uint256)
          
          precision: uint256
          
          
          @external
          def __init__():
              """
              @notice Template for Vault implementation
              """
              # The contract is made a "normal" template (not blueprint) so that we can get contract address before init
              # This is needed if we want to create a rehypothecation dual-market with two vaults
              # where vaults are collaterals of each other
              self.borrowed_token = ERC20(0x0000000000000000000000000000000000000001)
          
          
          @internal
          @pure
          def ln_int(_x: uint256) -> int256:
              """
              @notice Logarithm ln() function based on log2. Not very gas-efficient but brief
              """
              # adapted from: https://medium.com/coinmonks/9aef8515136e
              # and vyper log implementation
              # This can be much more optimal but that's not important here
              x: uint256 = _x
              res: uint256 = 0
              for i in range(8):
                  t: uint256 = 2**(7 - i)
                  p: uint256 = 2**t
                  if x >= p * 10**18:
                      x /= p
                      res += t * 10**18
              d: uint256 = 10**18
              for i in range(59):  # 18 decimals: math.log2(10**10) == 59.7
                  if (x >= 2 * 10**18):
                      res += d
                      x /= 2
                  x = x * x / 10**18
                  d /= 2
              # Now res = log2(x)
              # ln(x) = log2(x) / log2(e)
              return convert(res * 10**18 / 1442695040888963328, int256)
          
          
          @external
          def initialize(
                  amm_impl: address,
                  controller_impl: address,
                  borrowed_token: ERC20,
                  collateral_token: ERC20,
                  A: uint256,
                  fee: uint256,
                  price_oracle: PriceOracle,  # Factory makes from template if needed, deploying with a from_pool()
                  monetary_policy: address,  # Standard monetary policy set in factory
                  loan_discount: uint256,
                  liquidation_discount: uint256
              ) -> (address, address):
              """
              @notice Initializer for vaults
              @param amm_impl AMM implementation (blueprint)
              @param controller_impl Controller implementation (blueprint)
              @param borrowed_token Token which is being borrowed
              @param collateral_token Token used for collateral
              @param A Amplification coefficient: band size is ~1/A
              @param fee Fee for swaps in AMM (for ETH markets found to be 0.6%)
              @param price_oracle Already initialized price oracle
              @param monetary_policy Already initialized monetary policy
              @param loan_discount Maximum discount. LTV = sqrt(((A - 1) / A) ** 4) - loan_discount
              @param liquidation_discount Liquidation discount. LT = sqrt(((A - 1) / A) ** 4) - liquidation_discount
              """
              assert self.borrowed_token.address == empty(address)
          
              self.borrowed_token = borrowed_token
              self.collateral_token = collateral_token
              self.price_oracle = price_oracle
          
              assert A >= MIN_A and A <= MAX_A, "Wrong A"
              assert fee <= MAX_FEE, "Fee too high"
              assert fee >= MIN_FEE, "Fee too low"
              assert liquidation_discount >= MIN_LIQUIDATION_DISCOUNT, "Liquidation discount too low"
              assert loan_discount <= MAX_LOAN_DISCOUNT, "Loan discount too high"
              assert loan_discount > liquidation_discount, "need loan_discount>liquidation_discount"
          
              p: uint256 = price_oracle.price()  # This also validates price oracle ABI
              assert p > 0
              assert price_oracle.price_w() == p
              A_ratio: uint256 = 10**18 * A / (A - 1)
          
              borrowed_precision: uint256 = 10**(18 - borrowed_token.decimals())
          
              amm: address = create_from_blueprint(
                  amm_impl,
                  borrowed_token.address, borrowed_precision,
                  collateral_token.address, 10**(18 - collateral_token.decimals()),
                  A, isqrt(A_ratio * 10**18), self.ln_int(A_ratio),
                  p, fee, ADMIN_FEE, price_oracle.address,
                  code_offset=3)
              controller: address = create_from_blueprint(
                  controller_impl,
                  empty(address), monetary_policy, loan_discount, liquidation_discount, amm,
                  code_offset=3)
              AMM(amm).set_admin(controller)
          
              self.amm = AMM(amm)
              self.controller = Controller(controller)
              self.factory = Factory(msg.sender)
          
              # ERC20 set up
              self.precision = borrowed_precision
              borrowed_symbol: String[32] = borrowed_token.symbol()
              self.name = concat(NAME_PREFIX, borrowed_symbol)
              # Symbol must be String[32], but we do String[34]. It doesn't affect contracts which read it (they will truncate)
              # However this will be changed as soon as Vyper can *properly* manipulate strings
              self.symbol = concat(SYMBOL_PREFIX, borrowed_symbol)
          
              # No events because it's the only market we would ever create in this contract
          
              return controller, amm
          
          
          @external
          @view
          @nonreentrant('lock')
          def borrow_apr() -> uint256:
              """
              @notice Borrow APR (annualized and 1e18-based)
              """
              return self.amm.rate() * (365 * 86400)
          
          
          @external
          @view
          @nonreentrant('lock')
          def lend_apr() -> uint256:
              """
              @notice Lending APR (annualized and 1e18-based)
              """
              debt: uint256 = self.controller.total_debt()
              if debt == 0:
                  return 0
              else:
                  return self.amm.rate() * (365 * 86400) * debt / self._total_assets()
          
          
          @external
          @view
          def asset() -> ERC20:
              """
              @notice Asset which is the same as borrowed_token
              """
              return self.borrowed_token
          
          
          @internal
          @view
          def _total_assets() -> uint256:
              # admin fee should be accounted for here when enabled
              self.controller.check_lock()
              return self.borrowed_token.balanceOf(self.controller.address) + self.controller.total_debt()
          
          
          @external
          @view
          @nonreentrant('lock')
          def totalAssets() -> uint256:
              """
              @notice Total assets which can be lent out or be in reserve
              """
              return self._total_assets()
          
          
          @internal
          @view
          def _convert_to_shares(assets: uint256, is_floor: bool = True,
                                 _total_assets: uint256 = max_value(uint256)) -> uint256:
              total_assets: uint256 = _total_assets
              if total_assets == max_value(uint256):
                  total_assets = self._total_assets()
              precision: uint256 = self.precision
              numerator: uint256 = (self.totalSupply + DEAD_SHARES) * assets * precision
              denominator: uint256 = (total_assets * precision + 1)
              if is_floor:
                  return numerator / denominator
              else:
                  return (numerator + denominator - 1) / denominator
          
          
          @internal
          @view
          def _convert_to_assets(shares: uint256, is_floor: bool = True,
                                 _total_assets: uint256 = max_value(uint256)) -> uint256:
              total_assets: uint256 = _total_assets
              if total_assets == max_value(uint256):
                  total_assets = self._total_assets()
              precision: uint256 = self.precision
              numerator: uint256 = shares * (total_assets * precision + 1)
              denominator: uint256 = (self.totalSupply + DEAD_SHARES) * precision
              if is_floor:
                  return numerator / denominator
              else:
                  return (numerator + denominator - 1) / denominator
          
          
          @external
          @view
          @nonreentrant('lock')
          def pricePerShare(is_floor: bool = True) -> uint256:
              """
              @notice Method which shows how much one pool share costs in asset tokens if they are normalized to 18 decimals
              """
              supply: uint256 = self.totalSupply
              if supply == 0:
                  return 10**18 / DEAD_SHARES
              else:
                  precision: uint256 = self.precision
                  numerator: uint256 = 10**18 * (self._total_assets() * precision + 1)
                  denominator: uint256 = (supply + DEAD_SHARES)
                  pps: uint256 = 0
                  if is_floor:
                      pps = numerator / denominator
                  else:
                      pps = (numerator + denominator - 1) / denominator
                  assert pps > 0
                  return pps
          
          
          @external
          @view
          @nonreentrant('lock')
          def convertToShares(assets: uint256) -> uint256:
              """
              @notice Returns the amount of shares which the Vault would exchange for the given amount of shares provided
              """
              return self._convert_to_shares(assets)
          
          
          @external
          @view
          @nonreentrant('lock')
          def convertToAssets(shares: uint256) -> uint256:
              """
              @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided
              """
              return self._convert_to_assets(shares)
          
          
          @external
          @view
          def maxDeposit(receiver: address) -> uint256:
              """
              @notice Maximum amount of assets which a given user can deposit (inf)
              """
              return max_value(uint256)
          
          
          @external
          @view
          @nonreentrant('lock')
          def previewDeposit(assets: uint256) -> uint256:
              """
              @notice Returns the amount of shares which can be obtained upon depositing assets
              """
              return self._convert_to_shares(assets)
          
          
          @external
          @nonreentrant('lock')
          def deposit(assets: uint256, receiver: address = msg.sender) -> uint256:
              """
              @notice Deposit assets in return for whatever number of shares corresponds to the current conditions
              @param assets Amount of assets to deposit
              @param receiver Receiver of the shares who is optional. If not specified - receiver is the sender
              """
              controller: Controller = self.controller
              total_assets: uint256 = self._total_assets()
              assert total_assets + assets >= MIN_ASSETS, "Need more assets"
              to_mint: uint256 = self._convert_to_shares(assets, True, total_assets)
              assert self.borrowed_token.transferFrom(msg.sender, controller.address, assets, default_return_value=True)
              self._mint(receiver, to_mint)
              controller.save_rate()
              log Deposit(msg.sender, receiver, assets, to_mint)
              return to_mint
          
          
          @external
          @view
          def maxMint(receiver: address) -> uint256:
              """
              @notice Return maximum amount of shares which a given user can mint (inf)
              """
              return max_value(uint256)
          
          
          @external
          @view
          @nonreentrant('lock')
          def previewMint(shares: uint256) -> uint256:
              """
              @notice Calculate the amount of assets which is needed to exactly mint the given amount of shares
              """
              return self._convert_to_assets(shares, False)
          
          
          @external
          @nonreentrant('lock')
          def mint(shares: uint256, receiver: address = msg.sender) -> uint256:
              """
              @notice Mint given amount of shares taking whatever number of assets it requires
              @param shares Number of sharess to mint
              @param receiver Optional receiver for the shares. If not specified - it's the sender
              """
              controller: Controller = self.controller
              total_assets: uint256 = self._total_assets()
              assets: uint256 = self._convert_to_assets(shares, False, total_assets)
              assert total_assets + assets >= MIN_ASSETS, "Need more assets"
              assert self.borrowed_token.transferFrom(msg.sender, controller.address, assets, default_return_value=True)
              self._mint(receiver, shares)
              controller.save_rate()
              log Deposit(msg.sender, receiver, assets, shares)
              return assets
          
          
          @external
          @view
          @nonreentrant('lock')
          def maxWithdraw(owner: address) -> uint256:
              """
              @notice Maximum amount of assets which a given user can withdraw. Aware of both user's balance and available liquidity
              """
              return min(
                  self._convert_to_assets(self.balanceOf[owner]),
                  self.borrowed_token.balanceOf(self.controller.address))
          
          
          @external
          @view
          @nonreentrant('lock')
          def previewWithdraw(assets: uint256) -> uint256:
              """
              @notice Calculate number of shares which gets burned when withdrawing given amount of asset
              """
              assert assets <= self.borrowed_token.balanceOf(self.controller.address)
              return self._convert_to_shares(assets, False)
          
          
          @external
          @nonreentrant('lock')
          def withdraw(assets: uint256, receiver: address = msg.sender, owner: address = msg.sender) -> uint256:
              """
              @notice Withdraw given amount of asset and burn the corresponding amount of vault shares
              @param assets Amount of assets to withdraw
              @param receiver Receiver of the assets (optional, sender if not specified)
              @param owner Owner who's shares the caller takes. Only can take those if owner gave the approval to the sender. Optional
              """
              total_assets: uint256 = self._total_assets()
              assert total_assets - assets >= MIN_ASSETS or total_assets == assets, "Need more assets"
              shares: uint256 = self._convert_to_shares(assets, False, total_assets)
              if owner != msg.sender:
                  allowance: uint256 = self.allowance[owner][msg.sender]
                  if allowance != max_value(uint256):
                      self._approve(owner, msg.sender, allowance - shares)
          
              controller: Controller = self.controller
              self._burn(owner, shares)
              assert self.borrowed_token.transferFrom(controller.address, receiver, assets, default_return_value=True)
              controller.save_rate()
              log Withdraw(msg.sender, receiver, owner, assets, shares)
              return shares
          
          
          @external
          @view
          @nonreentrant('lock')
          def maxRedeem(owner: address) -> uint256:
              """
              @notice Calculate maximum amount of shares which a given user can redeem
              """
              return min(
                  self._convert_to_shares(self.borrowed_token.balanceOf(self.controller.address), False),
                  self.balanceOf[owner])
          
          
          @external
          @view
          @nonreentrant('lock')
          def previewRedeem(shares: uint256) -> uint256:
              """
              @notice Calculate the amount of assets which can be obtained by redeeming the given amount of shares
              """
              if self.totalSupply == 0:
                  assert shares == 0
                  return 0
          
              else:
                  assets_to_redeem: uint256 = self._convert_to_assets(shares)
                  assert assets_to_redeem <= self.borrowed_token.balanceOf(self.controller.address)
                  return assets_to_redeem
          
          
          @external
          @nonreentrant('lock')
          def redeem(shares: uint256, receiver: address = msg.sender, owner: address = msg.sender) -> uint256:
              """
              @notice Burn given amount of shares and give corresponding assets to the user
              @param shares Amount of shares to burn
              @param receiver Optional receiver of the assets
              @param owner Optional owner of the shares. Can only redeem if owner gave approval to the sender
              """
              if owner != msg.sender:
                  allowance: uint256 = self.allowance[owner][msg.sender]
                  if allowance != max_value(uint256):
                      self._approve(owner, msg.sender, allowance - shares)
          
              total_assets: uint256 = self._total_assets()
              assets_to_redeem: uint256 = self._convert_to_assets(shares, True, total_assets)
              if total_assets - assets_to_redeem < MIN_ASSETS:
                  if shares == self.totalSupply:
                      # This is the last withdrawal, so we can take everything
                      assets_to_redeem = total_assets
                  else:
                      raise "Need more assets"
              self._burn(owner, shares)
              controller: Controller = self.controller
              assert self.borrowed_token.transferFrom(controller.address, receiver, assets_to_redeem, default_return_value=True)
              controller.save_rate()
              log Withdraw(msg.sender, receiver, owner, assets_to_redeem, shares)
              return assets_to_redeem
          
          
          # ERC20 methods
          
          @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 _mint(_to: address, _value: uint256):
              self.balanceOf[_to] += _value
              self.totalSupply += _value
          
              log Transfer(empty(address), _to, _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)
          
          
          @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 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
          @view
          def admin() -> address:
              return self.factory.admin()