Open 0xtekgrinder opened 7 months ago
I described the cause in: https://github.com/vyperlang/vyper/pull/3789#issuecomment-2132795606
The overrides don't use intervals for tracking allocations (as opposed to eg SimpleAllocator
- this is bc we don't have the guarantee that the overridden addresses will only grow). Instead, we do O(n) allocations where n is determined by the size of storage variables. Also, we do an O(n) traversal which causes the long comp. times.
Version Information
vyper --version
): 0.3.10python --version
): 3.12.3What's your issue about?
When I am compiling my contract with a storage layout override it justs kill the process after some time without any more information
Please include information like:
[1] 229387 killed vyper contracts/dao/veANGLE.vy --storage-layout-file storage.json
what command you ran vyper contracts/dao/veANGLE.vy --storage-layout-file storage.json
the code that caused the failure (see this link for help with formatting code)
Original idea and credit:
Curve Finance's veCRV
https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy
veANGLE is a fork with only one view functions added to it to make veANGLE compatible
with Compound governance system. The references to the controller have also been removed
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
name: public(String[64]) symbol: 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)
initialized: public(bool)
emergency_withdrawal: public(bool)
@external def initialize(_admin: address, token_addr: address, _smart_wallet_checker: address, _name: String[64], _symbol: String[32]): """ @notice Contract initializer @param _admin Future veANGLE admin @param token_addr
ERC20ANGLE
token address @param _smart_wallet_checker Future smart wallet checker contract @param _name Token name @param _symbol Token symbol """ assert self.initialized == False #dev: contract is already initialized self.initialized = True assert _admin!= ZERO_ADDRESS #dev: admin cannot be the 0 address self.admin = _admin self.token = token_addr self.smart_wallet_checker = _smart_wallet_checker self.point_history[0].blk = block.number self.point_history[0].ts = block.timestamp@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 assert addr != ZERO_ADDRESS # dev: future admin cannot be the 0 address self.future_admin = addr log CommitOwnership(addr)@external def accept_transfer_ownership(): """ @notice Accept a pending ownership transfer """ _admin: address = self.future_admin assert msg.sender == _admin # dev: future admin only
@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 set_emergency_withdrawal(): assert msg.sender == self.admin self.emergency_withdrawal = True
@external @nonreentrant('lock') def withdraw_fast(): """ @notice withdraw all tokens when in emergency states """ assert self.emergency_withdrawal, "Emergency withdrawal not enabled"
@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
@internal def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128, sender: address): """ @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
@external def checkpoint(): """ @notice Record global data to checkpoint """ assert not self.emergency_withdrawal, "Emergency withdrawal enabled" 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 """ assert not self.emergency_withdrawal, "Emergency withdrawal enabled" _locked: LockedBalance = self.locked[_addr]@external @nonreentrant('lock') def create_lock(_value: uint256, _unlock_time: uint256): """ @notice Deposit
_value
tokens formsg.sender
and lock until_unlock_time
@param _value Amount to deposit @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks """ assert not self.emergency_withdrawal, "Emergency withdrawal enabled" 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]@external @nonreentrant('lock') def increase_amount(_value: uint256): """ @notice Deposit
_value
additional tokens formsg.sender
without modifying the unlock time @param _value Amount of tokens to deposit and add to the lock """ assert not self.emergency_withdrawal, "Emergency withdrawal enabled" self.assert_not_contract(msg.sender) _locked: LockedBalance = self.locked[msg.sender]@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 """ assert not self.emergency_withdrawal, "Emergency withdrawal enabled" 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@external @nonreentrant('lock') def withdraw(): """ @notice Withdraw all tokens for
msg.sender
@dev Only possible if the lock has expired """ assert not self.emergency_withdrawal, "Emergency withdrawal enabled" _locked: LockedBalance = self.locked[msg.sender] assert block.timestamp >= _locked.end, "The lock didn't expire" value: uint256 = convert(_locked.amount, uint256)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
@internal @view def _find_user_timestamp_epoch(addr: address, ts: uint256) -> uint256: """ @notice Find the epoch for a user's timestamp @param addr User wallet address @param ts Epoch time to find @return User epoch number """ minimum_value: uint256 = 0 maximum_value: uint256 = self.user_point_epoch[addr]
@external @view def find_user_timestamp_epoch(addr: address, ts: uint256) -> uint256: """ @notice Find the epoch for a user's timestamp @param addr User wallet address @param ts Epoch time to find @return User epoch number """ return self._find_user_timestamp_epoch(addr, ts)
@external @view def balanceOf(addr: address, ts: uint256 = block.timestamp) -> uint256: """ @notice Get the current voting power for
msg.sender
@dev Adheres to the ERC20balanceOf
interface for Aragon compatibility @param addr User wallet address @param ts Epoch time to return voting power at @return User voting power """ _epoch: uint256 = self._find_user_timestamp_epoch(addr, ts) if _epoch == 0: return 0 else: last_point: Point = self.user_point_history[addr][_epoch] last_point.bias -= last_point.slope * convert(ts - last_point.ts, int128) if last_point.bias < 0: last_point.bias = 0 return convert(last_point.bias, uint256)@internal @view def _balanceOfAt(addr: address, _block: uint256) -> uint256: """ @notice measure voting power of
addr
at block height_block
@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
@external @view def balanceOfAt(addr: address, _block: uint256) -> uint256: """ @notice Measure voting power of
addr
at block height_block
@dev Adheres to MiniMebalanceOfAt
interface: https://github.com/Giveth/minime @param addr User's wallet address @param _block Block to calculate the voting power at @return Voting power """ return self._balanceOfAt(addr,_block)@external @view def getPastVotes(addr: address, _block: uint256) -> uint256: """ @notice Measure voting power of
addr
at block height_block
@dev Adheres to ERC20VotesgetPastVotes
interface: @openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC20/extensions/ERC20VotesCompUpgradeable.sol @param addr User's wallet address @param _block Block to calculate the voting power at @return Voting power """ return self._balanceOfAt(addr,_block)@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
@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)--verbose
flag turned onIt have the same output
How can it be fixed?
I have no idea on how it can be fixed