diff --git a/.gitignore b/.gitignore index 5bdb9e7..9b77641 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .idea .pytest_cache .python-version +.DS_Store *.pyc *.egg-info @@ -11,4 +12,5 @@ notes.txt build/ dist/ -venv/ \ No newline at end of file +venv/ +prof/ \ No newline at end of file diff --git a/casper/contracts/simple_casper.v.py b/casper/contracts/simple_casper.v.py index 69264aa..766b752 100644 --- a/casper/contracts/simple_casper.v.py +++ b/casper/contracts/simple_casper.v.py @@ -3,90 +3,94 @@ # Withdrawal address used always in _from and _to as it's unique # and validator index is removed after some events # -Deposit: event({_from: indexed(address), _validator_index: indexed(int128), _validation_address: address, _start_dyn: int128, _amount: int128(wei)}) -Vote: event({_from: indexed(address), _validator_index: indexed(int128), _target_hash: indexed(bytes32), _target_epoch: int128, _source_epoch: int128}) -Logout: event({_from: indexed(address), _validator_index: indexed(int128), _end_dyn: int128}) -Withdraw: event({_to: indexed(address), _validator_index: indexed(int128), _amount: int128(wei)}) -Slash: event({_from: indexed(address), _offender: indexed(address), _offender_index: indexed(int128), _bounty: int128(wei)}) -Epoch: event({_number: indexed(int128), _checkpoint_hash: indexed(bytes32), _is_justified: bool, _is_finalized: bool}) +Deposit: event({_from: indexed(address), _validator_index: indexed(uint256), _validation_address: address, _start_dyn: uint256, _amount: wei_value}) +Vote: event({_from: indexed(address), _validator_index: indexed(uint256), _target_hash: indexed(bytes32), _target_epoch: uint256, _source_epoch: uint256}) +Logout: event({_from: indexed(address), _validator_index: indexed(uint256), _end_dyn: uint256}) +Withdraw: event({_to: indexed(address), _validator_index: indexed(uint256), _amount: wei_value}) +Slash: event({_from: indexed(address), _offender: indexed(address), _offender_index: indexed(uint256), _bounty: wei_value}) +Epoch: event({_number: indexed(uint256), _checkpoint_hash: indexed(bytes32), _is_justified: bool, _is_finalized: bool}) + +units: { + sf: "scale_factor" +} validators: public({ # Used to determine the amount of wei the validator holds. To get the actual # amount of wei, multiply this by the deposit_scale_factor. - deposit: decimal(wei/m), - start_dynasty: int128, - end_dynasty: int128, + deposit: decimal(wei/sf), + start_dynasty: uint256, + end_dynasty: uint256, is_slashed: bool, total_deposits_at_logout: wei_value, # The address which the validator's signatures must verify against addr: address, withdrawal_addr: address -}[int128]) +}[uint256]) # Map of epoch number to checkpoint hash -checkpoint_hashes: public(bytes32[int128]) +checkpoint_hashes: public(bytes32[uint256]) # Next available validator index -next_validator_index: public(int128) +next_validator_index: public(uint256) # Mapping of validator's withdrawal address to their index number -validator_indexes: public(int128[address]) +validator_indexes: public(uint256[address]) # Current dynasty, it measures the number of finalized checkpoints # in the chain from root to the parent of current block -dynasty: public(int128) +dynasty: public(uint256) # Map of the change to total deposits for specific dynasty -dynasty_wei_delta: public(decimal(wei / m)[int128]) +dynasty_wei_delta: public(decimal(wei / sf)[uint256]) # Total scaled deposits in the current dynasty -total_curdyn_deposits: decimal(wei / m) +total_curdyn_deposits: decimal(wei / sf) # Total scaled deposits in the previous dynasty -total_prevdyn_deposits: decimal(wei / m) +total_prevdyn_deposits: decimal(wei / sf) # Mapping of dynasty to start epoch of that dynasty -dynasty_start_epoch: public(int128[int128]) +dynasty_start_epoch: public(uint256[uint256]) # Mapping of epoch to what dynasty it is -dynasty_in_epoch: public(int128[int128]) +dynasty_in_epoch: public(uint256[uint256]) checkpoints: public({ # track size of scaled deposits for use in client fork choice cur_dyn_deposits: wei_value, prev_dyn_deposits: wei_value, # track total votes for each dynasty - cur_dyn_votes: decimal(wei / m)[int128], - prev_dyn_votes: decimal(wei / m)[int128], + cur_dyn_votes: decimal(wei / sf)[uint256], + prev_dyn_votes: decimal(wei / sf)[uint256], # Bitmap of which validator IDs have already voted - vote_bitmap: uint256[int128], + vote_bitmap: uint256[uint256], # Is a vote referencing the given epoch justified? is_justified: bool, # Is a vote referencing the given epoch finalized? is_finalized: bool -}[int128]) # index: target epoch +}[uint256]) # index: target epoch # Is the current expected hash justified main_hash_justified: public(bool) # Value used to calculate the per-epoch fee that validators should be charged -deposit_scale_factor: public(decimal(m)[int128]) +deposit_scale_factor: public(decimal(sf)[uint256]) last_nonvoter_rescale: public(decimal) last_voter_rescale: public(decimal) -current_epoch: public(int128) -last_finalized_epoch: public(int128) -last_justified_epoch: public(int128) +current_epoch: public(uint256) +last_finalized_epoch: public(uint256) +last_justified_epoch: public(uint256) # Reward for voting as fraction of deposit size reward_factor: public(decimal) # Expected source epoch for a vote -expected_source_epoch: public(int128) +expected_source_epoch: public(uint256) # Running total of deposits slashed -total_slashed: public(wei_value[int128]) +total_slashed: public(wei_value[uint256]) # Flag that only allows contract initialization to happen once initialized: bool @@ -94,16 +98,16 @@ # ***** Parameters ***** # Length of an epoch in blocks -EPOCH_LENGTH: public(int128) +EPOCH_LENGTH: public(uint256) # Length of warm up period in blocks -WARM_UP_PERIOD: public(int128) +WARM_UP_PERIOD: public(uint256) # Withdrawal delay in blocks -WITHDRAWAL_DELAY: public(int128) +WITHDRAWAL_DELAY: public(uint256) # Logout delay in dynasties -DYNASTY_LOGOUT_DELAY: public(int128) +DYNASTY_LOGOUT_DELAY: public(uint256) # MSG_HASHER calculator library address # Hashes message contents but not the signature @@ -116,34 +120,34 @@ BASE_INTEREST_FACTOR: public(decimal) BASE_PENALTY_FACTOR: public(decimal) MIN_DEPOSIT_SIZE: public(wei_value) -START_EPOCH: public(int128) +START_EPOCH: public(uint256) # ****** Pre-defined Constants ****** -DEFAULT_END_DYNASTY: int128 -MSG_HASHER_GAS_LIMIT: int128 -VALIDATION_GAS_LIMIT: int128 -SLASH_FRACTION_MULTIPLIER: int128 +DEFAULT_END_DYNASTY: uint256 +MSG_HASHER_GAS_LIMIT: uint256 +VALIDATION_GAS_LIMIT: uint256 +SLASH_FRACTION_MULTIPLIER: uint256 + +ONE_WEI_UINT256: wei_value +ONE_WEI_DECIMAL: decimal(wei) + @public -def init( - epoch_length: int128, - warm_up_period: int128, - withdrawal_delay: int128, - dynasty_logout_delay: int128, - msg_hasher: address, - purity_checker: address, - null_sender: address, - base_interest_factor: decimal, - base_penalty_factor: decimal, - min_deposit_size: wei_value - ): +def init(epoch_length: uint256, + warm_up_period: uint256, + withdrawal_delay: uint256, + dynasty_logout_delay: uint256, + msg_hasher: address, + purity_checker: address, + null_sender: address, + base_interest_factor: decimal, + base_penalty_factor: decimal, + min_deposit_size: wei_value): assert not self.initialized assert epoch_length > 0 and epoch_length < 256 - assert warm_up_period >= 0 - assert withdrawal_delay >= 0 assert dynasty_logout_delay >= 2 assert base_interest_factor >= 0.0 assert base_penalty_factor >= 0.0 @@ -159,7 +163,7 @@ def init( self.BASE_PENALTY_FACTOR = base_penalty_factor self.MIN_DEPOSIT_SIZE = min_deposit_size - self.START_EPOCH = floor((block.number + warm_up_period) / self.EPOCH_LENGTH) + self.START_EPOCH = (block.number + warm_up_period) / self.EPOCH_LENGTH # helper contracts self.MSG_HASHER = msg_hasher @@ -180,6 +184,8 @@ def init( self.MSG_HASHER_GAS_LIMIT = 200000 self.VALIDATION_GAS_LIMIT = 200000 self.SLASH_FRACTION_MULTIPLIER = 3 + self.ONE_WEI_UINT256 = 1 + self.ONE_WEI_DECIMAL = 1.0 # ****** Private Constants ***** @@ -187,7 +193,7 @@ def init( # Returns number of epochs since finalization. @private @constant -def esf() -> int128: +def esf() -> uint256: return self.current_epoch - self.last_finalized_epoch @@ -195,12 +201,21 @@ def esf() -> int128: @private @constant def sqrt_of_total_deposits() -> decimal: - epoch: int128 = self.current_epoch - ether_deposited_as_number: int128 = floor(max(self.total_prevdyn_deposits, self.total_curdyn_deposits) * - self.deposit_scale_factor[epoch - 1] / as_wei_value(1, "ether")) + 1 + epoch: uint256 = self.current_epoch + one_ether_as_wei: decimal = convert(convert(as_wei_value(1, "ether"), "int128"), "decimal") + ether_deposited_as_number: decimal = convert( + 1 + as_unitless_number( + floor( + max(self.total_prevdyn_deposits, self.total_curdyn_deposits) * + self.deposit_scale_factor[epoch - 1] / + one_ether_as_wei + ) + ), + "decimal" + ) sqrt: decimal = ether_deposited_as_number / 2.0 for i in range(20): - sqrt = (sqrt + (ether_deposited_as_number / sqrt)) / 2 + sqrt = (sqrt + (ether_deposited_as_number / sqrt)) / 2.0 return sqrt @@ -212,9 +227,9 @@ def deposit_exists() -> bool: @private @constant -def in_dynasty(validator_index:int128, _dynasty:int128) -> bool: - start_dynasty: int128 = self.validators[validator_index].start_dynasty - end_dynasty: int128 = self.validators[validator_index].end_dynasty +def in_dynasty(validator_index:uint256, _dynasty:uint256) -> bool: + start_dynasty: uint256 = self.validators[validator_index].start_dynasty + end_dynasty: uint256 = self.validators[validator_index].end_dynasty return (start_dynasty <= _dynasty) and (_dynasty < end_dynasty) @@ -224,7 +239,7 @@ def in_dynasty(validator_index:int128, _dynasty:int128) -> bool: # TODO: Might want to split out the cases separately. @private def increment_dynasty(): - epoch: int128 = self.current_epoch + epoch: uint256 = self.current_epoch # Increment the dynasty if finalized if self.checkpoints[epoch - 2].is_finalized: self.dynasty += 1 @@ -239,7 +254,7 @@ def increment_dynasty(): @private def insta_finalize(): - epoch: int128 = self.current_epoch + epoch: uint256 = self.current_epoch self.main_hash_justified = True self.checkpoints[epoch - 1].is_justified = True self.checkpoints[epoch - 1].is_finalized = True @@ -252,7 +267,7 @@ def insta_finalize(): # Returns the current collective reward factor, which rewards the dynasty for high-voting levels. @private def collective_reward() -> decimal: - epoch: int128 = self.current_epoch + epoch: uint256 = self.current_epoch live: bool = self.esf() <= 2 if not self.deposit_exists() or not live: return 0.0 @@ -260,18 +275,18 @@ def collective_reward() -> decimal: cur_vote_frac: decimal = self.checkpoints[epoch - 1].cur_dyn_votes[self.expected_source_epoch] / self.total_curdyn_deposits prev_vote_frac: decimal = self.checkpoints[epoch - 1].prev_dyn_votes[self.expected_source_epoch] / self.total_prevdyn_deposits vote_frac: decimal = min(cur_vote_frac, prev_vote_frac) - return vote_frac * self.reward_factor / 2 + return vote_frac * self.reward_factor / 2.0 # Reward the given validator & miner, and reflect this in total deposit figured @private -def proc_reward(validator_index: int128, reward: int128(wei/m)): +def proc_reward(validator_index: uint256, reward: decimal(wei/sf)): # Reward validator self.validators[validator_index].deposit += reward - start_dynasty: int128 = self.validators[validator_index].start_dynasty - end_dynasty: int128 = self.validators[validator_index].end_dynasty - current_dynasty: int128 = self.dynasty - past_dynasty: int128 = current_dynasty - 1 + start_dynasty: uint256 = self.validators[validator_index].start_dynasty + end_dynasty: uint256 = self.validators[validator_index].end_dynasty + current_dynasty: uint256 = self.dynasty + past_dynasty: uint256 = current_dynasty - 1 if ((start_dynasty <= current_dynasty) and (current_dynasty < end_dynasty)): self.total_curdyn_deposits += reward if ((start_dynasty <= past_dynasty) and (past_dynasty < end_dynasty)): @@ -279,12 +294,16 @@ def proc_reward(validator_index: int128, reward: int128(wei/m)): if end_dynasty < self.DEFAULT_END_DYNASTY: # validator has submit `logout` self.dynasty_wei_delta[end_dynasty] -= reward # Reward miner - send(block.coinbase, floor(reward * self.deposit_scale_factor[self.current_epoch] / 8)) + miner_reward: wei_value = convert( + floor(reward * self.deposit_scale_factor[self.current_epoch] / 8.0), + "uint256" + ) * self.ONE_WEI_UINT256 + send(block.coinbase, miner_reward) # Removes a validator from the validator pool @private -def delete_validator(validator_index: int128): +def delete_validator(validator_index: uint256): self.validator_indexes[self.validators[validator_index].withdrawal_addr] = 0 self.validators[validator_index] = { deposit: 0, @@ -300,7 +319,7 @@ def delete_validator(validator_index: int128): # cannot be labeled @constant because of external call # even though the call is to a pure contract call @private -def validate_signature(msg_hash: bytes32, sig: bytes[1024], validator_index: int128) -> bool: +def validate_signature(msg_hash: bytes32, sig: bytes[1024], validator_index: uint256) -> bool: return extract32(raw_call(self.validators[validator_index].addr, concat(msg_hash, sig), gas=self.VALIDATION_GAS_LIMIT, outsize=32), 0) == convert(1, 'bytes32') @@ -315,20 +334,29 @@ def main_hash_voted_frac() -> decimal: @public @constant -def deposit_size(validator_index: int128) -> int128(wei): - return floor(self.validators[validator_index].deposit * self.deposit_scale_factor[self.current_epoch]) +def deposit_size(validator_index: uint256) -> wei_value: + return self.ONE_WEI_UINT256 * convert( + floor(self.validators[validator_index].deposit * self.deposit_scale_factor[self.current_epoch]), + "uint256" + ) @public @constant def total_curdyn_deposits_in_wei() -> wei_value: - return floor(self.total_curdyn_deposits * self.deposit_scale_factor[self.current_epoch]) + return self.ONE_WEI_UINT256 * convert( + floor(self.total_curdyn_deposits * self.deposit_scale_factor[self.current_epoch]), + "uint256" + ) @public @constant def total_prevdyn_deposits_in_wei() -> wei_value: - return floor(self.total_prevdyn_deposits * self.deposit_scale_factor[self.current_epoch]) + return self.ONE_WEI_UINT256 * convert( + floor(self.total_prevdyn_deposits * self.deposit_scale_factor[self.current_epoch]), + "uint256" + ) @public @@ -339,8 +367,8 @@ def validate_vote_signature(vote_msg: bytes[1024]) -> bool: 0 ) # Extract parameters - values = RLPList(vote_msg, [int128, bytes32, int128, int128, bytes]) - validator_index: int128 = values[0] + values = RLPList(vote_msg, [uint256, bytes32, uint256, uint256, bytes]) + validator_index: uint256 = values[0] sig: bytes[1024] = values[4] return self.validate_signature(msg_hash, sig, validator_index) @@ -354,10 +382,10 @@ def slashable(vote_msg_1: bytes[1024], vote_msg_2: bytes[1024]) -> bool: raw_call(self.MSG_HASHER, vote_msg_1, gas=self.MSG_HASHER_GAS_LIMIT, outsize=32), 0 ) - values_1 = RLPList(vote_msg_1, [int128, bytes32, int128, int128, bytes]) - validator_index_1: int128 = values_1[0] - target_epoch_1: int128 = values_1[2] - source_epoch_1: int128 = values_1[3] + values_1 = RLPList(vote_msg_1, [uint256, bytes32, uint256, uint256, bytes]) + validator_index_1: uint256 = values_1[0] + target_epoch_1: uint256 = values_1[2] + source_epoch_1: uint256 = values_1[3] sig_1: bytes[1024] = values_1[4] # Message 2: Extract parameters @@ -365,10 +393,10 @@ def slashable(vote_msg_1: bytes[1024], vote_msg_2: bytes[1024]) -> bool: raw_call(self.MSG_HASHER, vote_msg_2, gas=self.MSG_HASHER_GAS_LIMIT, outsize=32), 0 ) - values_2 = RLPList(vote_msg_2, [int128, bytes32, int128, int128, bytes]) - validator_index_2: int128 = values_2[0] - target_epoch_2: int128 = values_2[2] - source_epoch_2: int128 = values_2[3] + values_2 = RLPList(vote_msg_2, [uint256, bytes32, uint256, uint256, bytes]) + validator_index_2: uint256 = values_2[0] + target_epoch_2: uint256 = values_2[2] + source_epoch_2: uint256 = values_2[3] sig_2: bytes[1024] = values_2[4] if not self.validate_signature(msg_hash_1, sig_1, validator_index_1): @@ -397,7 +425,7 @@ def slashable(vote_msg_1: bytes[1024], vote_msg_2: bytes[1024]) -> bool: @public @constant -def recommended_source_epoch() -> int128: +def recommended_source_epoch() -> uint256: return self.expected_source_epoch @@ -419,10 +447,10 @@ def recommended_target_hash() -> bytes32: @public @constant -def highest_justified_epoch(min_total_deposits: wei_value) -> int128: - epoch: int128 +def highest_justified_epoch(min_total_deposits: wei_value) -> uint256: + epoch: uint256 for i in range(1000000000000000000000000000000): - epoch = self.current_epoch - i + epoch = self.current_epoch - convert(i, "uint256") is_justified: bool = self.checkpoints[epoch].is_justified enough_cur_dyn_deposits: bool = self.checkpoints[epoch].cur_dyn_deposits >= min_total_deposits enough_prev_dyn_deposits: bool = self.checkpoints[epoch].prev_dyn_deposits >= min_total_deposits @@ -437,18 +465,19 @@ def highest_justified_epoch(min_total_deposits: wei_value) -> int128: # to 0 out the affect of casper on fork choice return 0 + @public @constant def highest_finalized_epoch(min_total_deposits: wei_value) -> int128: - epoch: int128 + epoch: uint256 for i in range(1000000000000000000000000000000): - epoch = self.current_epoch - i + epoch = self.current_epoch - convert(i, "uint256") is_finalized: bool = self.checkpoints[epoch].is_finalized enough_cur_dyn_deposits: bool = self.checkpoints[epoch].cur_dyn_deposits >= min_total_deposits enough_prev_dyn_deposits: bool = self.checkpoints[epoch].prev_dyn_deposits >= min_total_deposits if is_finalized and (enough_cur_dyn_deposits and enough_prev_dyn_deposits): - return epoch + return convert(epoch, "int128") if epoch == self.START_EPOCH: break @@ -460,14 +489,16 @@ def highest_finalized_epoch(min_total_deposits: wei_value) -> int128: @private @constant -def _votable( - validator_index:int128, - target_hash:bytes32, - target_epoch:int128, - source_epoch:int128) -> bool: +def _votable(validator_index: uint256, + target_hash: bytes32, + target_epoch: uint256, + source_epoch: uint256) -> bool: # Check that this vote has not yet been made - if bitwise_and(self.checkpoints[target_epoch].vote_bitmap[floor(validator_index / 256)], - shift(convert(1, 'uint256'), validator_index % 256)): + already_voted: uint256 = bitwise_and( + self.checkpoints[target_epoch].vote_bitmap[validator_index / 256], + shift(convert(1, 'uint256'), convert(validator_index % 256, "int128")) + ) + if already_voted: return False # Check that the vote's target epoch and hash are correct if target_hash != self.recommended_target_hash(): @@ -488,11 +519,11 @@ def _votable( @constant def votable(vote_msg: bytes[1024]) -> bool: # Extract parameters - values = RLPList(vote_msg, [int128, bytes32, int128, int128, bytes]) - validator_index: int128 = values[0] + values = RLPList(vote_msg, [uint256, bytes32, uint256, uint256, bytes]) + validator_index: uint256 = values[0] target_hash: bytes32 = values[1] - target_epoch: int128 = values[2] - source_epoch: int128 = values[3] + target_epoch: uint256 = values[2] + source_epoch: uint256 = values[3] return self._votable(validator_index, target_hash, target_epoch, source_epoch) @@ -501,9 +532,9 @@ def votable(vote_msg: bytes[1024]) -> bool: # Called at the start of any epoch @public -def initialize_epoch(epoch: int128): +def initialize_epoch(epoch: uint256): # Check that the epoch actually has started - computed_current_epoch: int128 = floor(block.number / self.EPOCH_LENGTH) + computed_current_epoch: uint256 = block.number / self.EPOCH_LENGTH assert epoch <= computed_current_epoch and epoch == self.current_epoch + 1 # must track the deposits related to the checkpoint _before_ updating current_epoch @@ -512,15 +543,15 @@ def initialize_epoch(epoch: int128): self.current_epoch = epoch - self.last_voter_rescale = 1 + self.collective_reward() - self.last_nonvoter_rescale = self.last_voter_rescale / (1 + self.reward_factor) + self.last_voter_rescale = 1.0 + self.collective_reward() + self.last_nonvoter_rescale = self.last_voter_rescale / (1.0 + self.reward_factor) self.deposit_scale_factor[epoch] = self.deposit_scale_factor[epoch - 1] * self.last_nonvoter_rescale self.total_slashed[epoch] = self.total_slashed[epoch - 1] if self.deposit_exists(): # Set the reward factor for the next epoch. adj_interest_base: decimal = self.BASE_INTEREST_FACTOR / self.sqrt_of_total_deposits() - self.reward_factor = adj_interest_base + self.BASE_PENALTY_FACTOR * (self.esf() - 2) + self.reward_factor = adj_interest_base + self.BASE_PENALTY_FACTOR * convert(convert((self.esf() - 2), "int128"), "decimal") # ESF is only thing that is changing and reward_factor is being used above. assert self.reward_factor > 0.0 else: @@ -544,9 +575,9 @@ def deposit(validation_addr: address, withdrawal_addr: address): assert extract32(raw_call(self.PURITY_CHECKER, concat('\xa1\x90\x3e\xab', convert(validation_addr, 'bytes32')), gas=500000, outsize=32), 0) != convert(0, 'bytes32') assert not self.validator_indexes[withdrawal_addr] assert msg.value >= self.MIN_DEPOSIT_SIZE - validator_index: int128 = self.next_validator_index - start_dynasty: int128 = self.dynasty + 2 - scaled_deposit: decimal(wei/m) = msg.value / self.deposit_scale_factor[self.current_epoch] + validator_index: uint256 = self.next_validator_index + start_dynasty: uint256 = self.dynasty + 2 + scaled_deposit: decimal(wei/sf) = self.ONE_WEI_DECIMAL * convert(convert(msg.value, "int128"), "decimal") / self.deposit_scale_factor[self.current_epoch] self.validators[validator_index] = { deposit: scaled_deposit, start_dynasty: start_dynasty, @@ -571,7 +602,7 @@ def deposit(validation_addr: address, withdrawal_addr: address): @public def logout(logout_msg: bytes[1024]): - assert self.current_epoch == floor(block.number / self.EPOCH_LENGTH) + assert self.current_epoch == block.number / self.EPOCH_LENGTH # Get hash for signature, and implicitly assert that it is an RLP list # consisting solely of RLP elements @@ -579,9 +610,9 @@ def logout(logout_msg: bytes[1024]): raw_call(self.MSG_HASHER, logout_msg, gas=self.MSG_HASHER_GAS_LIMIT, outsize=32), 0 ) - values = RLPList(logout_msg, [int128, int128, bytes]) - validator_index: int128 = values[0] - epoch: int128 = values[1] + values = RLPList(logout_msg, [uint256, uint256, bytes]) + validator_index: uint256 = values[0] + epoch: uint256 = values[1] sig: bytes[1024] = values[2] assert self.current_epoch >= epoch @@ -589,7 +620,7 @@ def logout(logout_msg: bytes[1024]): assert from_withdrawal or self.validate_signature(msg_hash, sig, validator_index) # Check that we haven't already withdrawn - end_dynasty: int128 = self.dynasty + self.DYNASTY_LOGOUT_DELAY + end_dynasty: uint256 = self.dynasty + self.DYNASTY_LOGOUT_DELAY assert self.validators[validator_index].end_dynasty > end_dynasty self.validators[validator_index].end_dynasty = end_dynasty @@ -605,28 +636,49 @@ def logout(logout_msg: bytes[1024]): # Withdraw deposited ether @public -def withdraw(validator_index: int128): +def withdraw(validator_index: uint256): # Check that we can withdraw - end_dynasty: int128 = self.validators[validator_index].end_dynasty + end_dynasty: uint256 = self.validators[validator_index].end_dynasty assert self.dynasty > end_dynasty - end_epoch: int128 = self.dynasty_start_epoch[end_dynasty + 1] - withdrawal_epoch: int128 = end_epoch + self.WITHDRAWAL_DELAY + end_epoch: uint256 = self.dynasty_start_epoch[end_dynasty + 1] + withdrawal_epoch: uint256 = end_epoch + self.WITHDRAWAL_DELAY assert self.current_epoch >= withdrawal_epoch # Withdraw - withdraw_amount: int128(wei) + withdraw_amount: wei_value if not self.validators[validator_index].is_slashed: - withdraw_amount = floor(self.validators[validator_index].deposit * self.deposit_scale_factor[end_epoch]) + withdraw_amount = self.ONE_WEI_UINT256 * convert( + floor( + self.validators[validator_index].deposit * self.deposit_scale_factor[end_epoch] + ), + "uint256" + ) else: - recently_slashed: wei_value = self.total_slashed[withdrawal_epoch] - self.total_slashed[withdrawal_epoch - 2 * self.WITHDRAWAL_DELAY] - fraction_to_slash: decimal = recently_slashed * self.SLASH_FRACTION_MULTIPLIER / self.validators[validator_index].total_deposits_at_logout + # prevent a negative lookup in total_slashed + base_epoch: uint256 + if 2 * self.WITHDRAWAL_DELAY > withdrawal_epoch: + base_epoch = 0 + else: + base_epoch = withdrawal_epoch - 2 * self.WITHDRAWAL_DELAY - # can't withdraw a negative amount - fraction_to_withdraw: decimal = max((1 - fraction_to_slash), 0) + recently_slashed: wei_value = self.total_slashed[withdrawal_epoch] - self.total_slashed[base_epoch] + fraction_to_slash: decimal = convert(convert(recently_slashed * self.SLASH_FRACTION_MULTIPLIER, "int128"), "decimal") / \ + convert(convert(self.validators[validator_index].total_deposits_at_logout, "int128"), "decimal") - deposit_size: int128(wei) = floor(self.validators[validator_index].deposit * self.deposit_scale_factor[withdrawal_epoch]) - withdraw_amount = floor(deposit_size * fraction_to_withdraw) + # can't withdraw a negative amount + fraction_to_withdraw: decimal = max((1.0 - fraction_to_slash), 0.0) + + deposit_size: wei_value = self.ONE_WEI_UINT256 * convert( + floor(self.validators[validator_index].deposit * self.deposit_scale_factor[withdrawal_epoch]), + "uint256" + ) + withdraw_amount = self.ONE_WEI_UINT256 * convert( + floor( + convert(convert(deposit_size, "int128"), "decimal") * fraction_to_withdraw + ), + "uint256" + ) send(self.validators[validator_index].withdrawal_addr, withdraw_amount) @@ -646,26 +698,26 @@ def vote(vote_msg: bytes[1024]): assert msg.sender == self.NULL_SENDER # Extract parameters - values = RLPList(vote_msg, [int128, bytes32, int128, int128, bytes]) - validator_index: int128 = values[0] + values = RLPList(vote_msg, [uint256, bytes32, uint256, uint256, bytes]) + validator_index: uint256 = values[0] target_hash: bytes32 = values[1] - target_epoch: int128 = values[2] - source_epoch: int128 = values[3] + target_epoch: uint256 = values[2] + source_epoch: uint256 = values[3] sig: bytes[1024] = values[4] assert self._votable(validator_index, target_hash, target_epoch, source_epoch) assert self.validate_vote_signature(vote_msg) # Record that the validator voted for this target epoch so they can't again - self.checkpoints[target_epoch].vote_bitmap[floor(validator_index / 256)] = \ - bitwise_or(self.checkpoints[target_epoch].vote_bitmap[floor(validator_index / 256)], - shift(convert(1, 'uint256'), validator_index % 256)) + self.checkpoints[target_epoch].vote_bitmap[validator_index / 256] = \ + bitwise_or(self.checkpoints[target_epoch].vote_bitmap[validator_index / 256], + shift(convert(1, 'uint256'), convert(validator_index % 256, "int128"))) # Record that this vote took place in_current_dynasty: bool = self.in_dynasty(validator_index, self.dynasty) in_prev_dynasty: bool = self.in_dynasty(validator_index, self.dynasty - 1) - current_dynasty_votes: decimal(wei/m) = self.checkpoints[target_epoch].cur_dyn_votes[source_epoch] - previous_dynasty_votes: decimal(wei/m) = self.checkpoints[target_epoch].prev_dyn_votes[source_epoch] + current_dynasty_votes: decimal(wei/sf) = self.checkpoints[target_epoch].cur_dyn_votes[source_epoch] + previous_dynasty_votes: decimal(wei/sf) = self.checkpoints[target_epoch].prev_dyn_votes[source_epoch] if in_current_dynasty: current_dynasty_votes += self.validators[validator_index].deposit @@ -677,14 +729,16 @@ def vote(vote_msg: bytes[1024]): # Process rewards. # Pay the reward if the vote was submitted in time and the vote is voting the correct data if self.expected_source_epoch == source_epoch: - reward: int128(wei/m) = floor(self.validators[validator_index].deposit * self.reward_factor) + reward: decimal(wei/sf) = self.validators[validator_index].deposit * self.reward_factor self.proc_reward(validator_index, reward) # If enough votes with the same source_epoch and hash are made, # then the hash value is justified - if (current_dynasty_votes >= self.total_curdyn_deposits * 2 / 3 and - previous_dynasty_votes >= self.total_prevdyn_deposits * 2 / 3) and \ - not self.checkpoints[target_epoch].is_justified: + two_thirds_curdyn: bool = current_dynasty_votes >= self.total_curdyn_deposits * 2.0 / 3.0 + two_thirds_prevdyn: bool = previous_dynasty_votes >= self.total_prevdyn_deposits * 2.0 / 3.0 + enough_votes: bool = two_thirds_curdyn and two_thirds_prevdyn + + if enough_votes and not self.checkpoints[target_epoch].is_justified: self.checkpoints[target_epoch].is_justified = True self.last_justified_epoch = target_epoch self.main_hash_justified = True @@ -718,12 +772,12 @@ def slash(vote_msg_1: bytes[1024], vote_msg_2: bytes[1024]): # Extract validator_index # `slashable` guarantees that validator_index is the same for each vote_msg # so just extract validator_index from vote_msg_1 - values = RLPList(vote_msg_1, [int128, bytes32, int128, int128, bytes]) - validator_index: int128 = values[0] + values = RLPList(vote_msg_1, [uint256, bytes32, uint256, uint256, bytes]) + validator_index: uint256 = values[0] # Slash the offending validator, and give a 4% "finder's fee" - validator_deposit: int128(wei) = self.deposit_size(validator_index) - slashing_bounty: int128(wei) = floor(validator_deposit / 25) + validator_deposit: wei_value = self.deposit_size(validator_index) + slashing_bounty: wei_value = validator_deposit / 25 self.total_slashed[self.current_epoch] += validator_deposit self.validators[validator_index].is_slashed = True @@ -737,9 +791,9 @@ def slash(vote_msg_1: bytes[1024], vote_msg_2: bytes[1024]): # if validator not logged out yet, remove total from next dynasty # and forcibly logout next dynasty - end_dynasty: int128 = self.validators[validator_index].end_dynasty + end_dynasty: uint256 = self.validators[validator_index].end_dynasty if self.dynasty < end_dynasty: - deposit: decimal(wei/m) = self.validators[validator_index].deposit + deposit: decimal(wei/sf) = self.validators[validator_index].deposit self.dynasty_wei_delta[self.dynasty + 1] -= deposit self.validators[validator_index].end_dynasty = self.dynasty + 1 diff --git a/requirements.txt b/requirements.txt index d7bc08a..3181bc3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ rlp==1.0.1 -eth-tester[py-evm]==0.1.0b24 -web3>=4.2.1 +eth-tester==0.1.0b24 +py-evm==0.2.0a16 +web3>=4.3.0 pytest==3.4.0 -git+https://github.com/ethereum/vyper.git@248c723288e84899908048efff4c3e0b12f0b3dc +git+https://github.com/ethereum/vyper.git@8113f79f7bce362e7a0f803b32736cb460c78640 diff --git a/tests/conftest.py b/tests/conftest.py index e87c9b7..b21e2a9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,7 @@ GAS_PRICE = 25 * 10**9 NULL_SENDER = '0xffffffffffffffffffffffffffffffffffffffff' +CASPER_ADDRESS = "0x0000000000000000000000000000000000000042" VYPER_RLP_DECODER_TX_HEX = "0xf9035b808506fc23ac0083045f788080b903486103305660006109ac5260006109cc527f0100000000000000000000000000000000000000000000000000000000000000600035046109ec526000610a0c5260006109005260c06109ec51101515585760f86109ec51101561006e5760bf6109ec510336141558576001610a0c52610098565b60013560f76109ec51036020035260005160f66109ec510301361415585760f66109ec5103610a0c525b61022060016064818352015b36610a0c511015156100b557610291565b7f0100000000000000000000000000000000000000000000000000000000000000610a0c5135046109ec526109cc5160206109ac51026040015260016109ac51016109ac5260806109ec51101561013b5760016109cc5161044001526001610a0c516109cc5161046001376001610a0c5101610a0c5260216109cc51016109cc52610281565b60b86109ec5110156101d15760806109ec51036109cc51610440015260806109ec51036001610a0c51016109cc51610460013760816109ec5114156101ac5760807f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350410151558575b607f6109ec5103610a0c5101610a0c5260606109ec51036109cc51016109cc52610280565b60c06109ec51101561027d576001610a0c51013560b76109ec510360200352600051610a2c526038610a2c5110157f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350402155857610a2c516109cc516104400152610a2c5160b66109ec5103610a0c51016109cc516104600137610a2c5160b66109ec5103610a0c510101610a0c526020610a2c51016109cc51016109cc5261027f565bfe5b5b5b81516001018083528114156100a4575b5050601f6109ac511115155857602060206109ac5102016109005260206109005103610a0c5261022060016064818352015b6000610a0c5112156102d45761030a565b61090051610a0c516040015101610a0c51610900516104400301526020610a0c5103610a0c5281516001018083528114156102c3575b50506109cc516109005101610420526109cc5161090051016109005161044003f35b61000461033003610004600039610004610330036000f31b2d4f" # NOQA VYPER_RLP_DECODER_TX_SENDER = "0x39ba083c30fCe59883775Fc729bBE1f9dE4DEe11" @@ -56,6 +57,10 @@ ] +setattr(eth_tester.backends.pyevm.main, 'GENESIS_GAS_LIMIT', 10**9) +setattr(eth_tester.backends.pyevm.main, 'GENESIS_DIFFICULTY', 1) + + @pytest.fixture def next_contract_address(w3, base_tester, fake_contract_code): def next_contract_address(sender): @@ -237,10 +242,6 @@ def casper_args(casper_config, ] -setattr(eth_tester.backends.pyevm.main, 'GENESIS_GAS_LIMIT', 10**9) -setattr(eth_tester.backends.pyevm.main, 'GENESIS_DIFFICULTY', 1) - - @pytest.fixture def base_tester(): return EthereumTester(PyEVMBackend()) @@ -258,31 +259,37 @@ def w3(base_tester): @pytest.fixture -def tester( - w3, - base_tester, - casper_args, - casper_code, - casper_abi, - casper_address, - deploy_rlp_decoder, - deploy_msg_hasher, - deploy_purity_checker, - base_sender, - initialize_contract=True): +def tester(w3, + base_tester, + casper_args, + casper_code, + casper_abi, + casper_address, + deploy_rlp_decoder, + deploy_msg_hasher, + deploy_purity_checker, + base_sender, + initialize_contract=True): deploy_rlp_decoder() deploy_msg_hasher() deploy_purity_checker() # NOTE: bytecode cannot be compiled before RLP Decoder is deployed to chain # otherwise, vyper compiler cannot properly embed RLP decoder address - casper_bytecode = compiler.compile(casper_code) + casper_bytecode = compiler.compile(casper_code, bytecode_runtime=True) + + chain = base_tester.backend.chain + vm = chain.get_vm() + + vm.state.account_db.set_code(Web3.toBytes(hexstr=casper_address), casper_bytecode) + vm.state.account_db.persist() + new_state_root = vm.state.account_db.state_root - Casper = w3.eth.contract(abi=casper_abi, bytecode=casper_bytecode) - tx_hash = Casper.constructor().transact({'from': base_sender}) - tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + new_header = chain.header.copy(state_root=new_state_root) + chain.header = new_header - assert tx_receipt.contractAddress == casper_address + # mine block to ensure we don't have mismatched state + base_tester.mine_block() # Casper contract needs money for its activity w3.eth.sendTransaction({ @@ -353,8 +360,8 @@ def casper_abi(casper_code): @pytest.fixture -def casper_address(next_contract_address, base_sender): - return next_contract_address(base_sender) +def casper_address(): + return CASPER_ADDRESS @pytest.fixture diff --git a/tests/test_init.py b/tests/test_init.py index a64028f..92b7d8b 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,6 +1,12 @@ import pytest from decimal import Decimal +import web3 +import eth_tester + + +TRANSACTION_FAILED = eth_tester.exceptions.TransactionFailed +VALIDATION_ERROR = web3.exceptions.ValidationError def test_no_double_init( @@ -50,27 +56,28 @@ def test_start_epoch( @pytest.mark.parametrize( - 'epoch_length, success', + 'epoch_length, error', [ - (-1, False), - (0, False), - (10, True), - (250, True), - (256, False), - (500, False), + (-1, VALIDATION_ERROR), + (0, TRANSACTION_FAILED), + (10, None), + (250, None), + (256, TRANSACTION_FAILED), + (500, TRANSACTION_FAILED), ] ) def test_init_epoch_length( epoch_length, - success, + error, casper_args, deploy_casper_contract, assert_tx_failed): casper = deploy_casper_contract(casper_args, initialize_contract=False) - if not success: + if error: assert_tx_failed( - lambda: casper.functions.init(*casper_args).transact() + lambda: casper.functions.init(*casper_args).transact(), + error ) return @@ -79,56 +86,55 @@ def test_init_epoch_length( @pytest.mark.parametrize( - 'warm_up_period, success', + 'warm_up_period, error', [ - (-1, False), - (0, True), - (10, True), - (256, True), - (50000, True), + (-1, VALIDATION_ERROR), + (0, None), + (10, None), + (256, None), + (50000, None), ] ) -def test_init_warm_up_period( - warm_up_period, - success, - casper_args, - deploy_casper_contract, - assert_tx_failed): +def test_init_warm_up_period(warm_up_period, + error, + casper_args, + deploy_casper_contract, + assert_tx_failed): casper = deploy_casper_contract(casper_args, initialize_contract=False) - if not success: + if error: assert_tx_failed( - lambda: casper.functions.init(*casper_args).transact() + lambda: casper.functions.init(*casper_args).transact(), + error ) return casper.functions.init(*casper_args).transact() assert casper.functions.WARM_UP_PERIOD().call() == warm_up_period - @pytest.mark.parametrize( - 'withdrawal_delay, success', + 'withdrawal_delay, error', [ - (-42, False), - (-1, False), - (0, True), - (1, True), - (10, True), - (10000, True), - (500000000, True), + (-42, VALIDATION_ERROR), + (-1, VALIDATION_ERROR), + (0, None), + (1, None), + (10, None), + (10000, None), + (500000000, None), ] ) -def test_init_withdrawal_delay( - withdrawal_delay, - success, - casper_args, - deploy_casper_contract, - assert_tx_failed): +def test_init_withdrawal_delay(withdrawal_delay, + error, + casper_args, + deploy_casper_contract, + assert_tx_failed): casper = deploy_casper_contract(casper_args, initialize_contract=False) - if not success: + if error: assert_tx_failed( - lambda: casper.functions.init(*casper_args).transact() + lambda: casper.functions.init(*casper_args).transact(), + error ) return @@ -137,29 +143,29 @@ def test_init_withdrawal_delay( @pytest.mark.parametrize( - 'dynasty_logout_delay, success', + 'dynasty_logout_delay, error', [ - (-42, False), - (-1, False), - (0, False), - (1, False), - (2, True), - (3, True), - (100, True), - (3000000, True), + (-42, VALIDATION_ERROR), + (-1, VALIDATION_ERROR), + (0, TRANSACTION_FAILED), + (1, TRANSACTION_FAILED), + (2, None), + (3, None), + (100, None), + (3000000, None), ] ) -def test_init_dynasty_logout_delay( - dynasty_logout_delay, - success, - casper_args, - deploy_casper_contract, - assert_tx_failed): +def test_init_dynasty_logout_delay(dynasty_logout_delay, + error, + casper_args, + deploy_casper_contract, + assert_tx_failed): casper = deploy_casper_contract(casper_args, initialize_contract=False) - if not success: + if error: assert_tx_failed( - lambda: casper.functions.init(*casper_args).transact() + lambda: casper.functions.init(*casper_args).transact(), + error ) return @@ -168,27 +174,27 @@ def test_init_dynasty_logout_delay( @pytest.mark.parametrize( - 'base_interest_factor, success', + 'base_interest_factor, error', [ - (-10, False), - (Decimal('-0.001'), False), - (0, True), - (Decimal('7e-3'), True), - (Decimal('0.1'), True), - (Decimal('1.5'), True), + (-10, TRANSACTION_FAILED), + (Decimal('-0.001'), TRANSACTION_FAILED), + (0, None), + (Decimal('7e-3'), None), + (Decimal('0.1'), None), + (Decimal('1.5'), None), ] ) -def test_init_base_interest_factor( - base_interest_factor, - success, - casper_args, - deploy_casper_contract, - assert_tx_failed): +def test_init_base_interest_factor(base_interest_factor, + error, + casper_args, + deploy_casper_contract, + assert_tx_failed): casper = deploy_casper_contract(casper_args, initialize_contract=False) - if not success: + if error: assert_tx_failed( - lambda: casper.functions.init(*casper_args).transact() + lambda: casper.functions.init(*casper_args).transact(), + error ) return @@ -197,27 +203,27 @@ def test_init_base_interest_factor( @pytest.mark.parametrize( - 'base_penalty_factor, success', + 'base_penalty_factor, error', [ - (-10, False), - (Decimal('-0.001'), False), - (0, True), - (Decimal('7e-3'), True), - (Decimal('0.1'), True), - (Decimal('1.5'), True), + (-10, TRANSACTION_FAILED), + (Decimal('-0.001'), TRANSACTION_FAILED), + (0, None), + (Decimal('7e-3'), None), + (Decimal('0.1'), None), + (Decimal('1.5'), None), ] ) -def test_init_base_penalty_factor( - base_penalty_factor, - success, - casper_args, - deploy_casper_contract, - assert_tx_failed): +def test_init_base_penalty_factor(base_penalty_factor, + error, + casper_args, + deploy_casper_contract, + assert_tx_failed): casper = deploy_casper_contract(casper_args, initialize_contract=False) - if not success: + if error: assert_tx_failed( - lambda: casper.functions.init(*casper_args).transact() + lambda: casper.functions.init(*casper_args).transact(), + error ) return @@ -226,29 +232,29 @@ def test_init_base_penalty_factor( @pytest.mark.parametrize( - 'min_deposit_size, success', + 'min_deposit_size, error', [ - (int(-1e10), False), - (-1, False), - (0, False), - (1, True), - (42, True), - (int(1e4), True), - (int(2.5e20), True), - (int(5e30), True), + (int(-1e10), VALIDATION_ERROR), + (-1, VALIDATION_ERROR), + (0, TRANSACTION_FAILED), + (1, None), + (42, None), + (int(1e4), None), + (int(2.5e20), None), + (int(5e30), None), ] ) -def test_init_min_deposit_size( - min_deposit_size, - success, - casper_args, - deploy_casper_contract, - assert_tx_failed): +def test_init_min_deposit_size(min_deposit_size, + error, + casper_args, + deploy_casper_contract, + assert_tx_failed): casper = deploy_casper_contract(casper_args, initialize_contract=False) - if not success: + if error: assert_tx_failed( - lambda: casper.functions.init(*casper_args).transact() + lambda: casper.functions.init(*casper_args).transact(), + error ) return @@ -256,10 +262,9 @@ def test_init_min_deposit_size( assert casper.functions.MIN_DEPOSIT_SIZE().call() == min_deposit_size -def test_init_null_sender( - null_sender, - casper_args, - deploy_casper_contract): +def test_init_null_sender(null_sender, + casper_args, + deploy_casper_contract): casper = deploy_casper_contract(casper_args, initialize_contract=False) casper.functions.init(*casper_args).transact() diff --git a/tests/test_slashing.py b/tests/test_slashing.py index 43797c6..3b79dd4 100644 --- a/tests/test_slashing.py +++ b/tests/test_slashing.py @@ -391,6 +391,7 @@ def test_withdraw_after_slash(w3, new_epoch() end_dynasty = concise_casper.validators__end_dynasty(slashed_index) + assert concise_casper.dynasty() > end_dynasty end_epoch = concise_casper.dynasty_start_epoch(end_dynasty + 1) withdrawal_epoch = end_epoch + concise_casper.WITHDRAWAL_DELAY() assert concise_casper.current_epoch() == withdrawal_epoch