├── .gitignore ├── .travis.yml ├── IMPLEMENTATION.md ├── LICENSE ├── README.md ├── VALIDATOR_GUIDE.md ├── casper ├── __init__.py └── contracts │ ├── msg_hash.se.py │ ├── purity_checker.py │ ├── simple_casper.v.py │ └── sqrt.se.py ├── misc ├── rando.py ├── rando.v.py └── validation_codes │ ├── fixed_address_creator.py │ ├── hash_ladder_signer.py │ ├── hash_ladder_tester.py │ └── verify_hash_ladder_sig.se ├── old-README.md └── tests ├── conftest.py ├── test_chain_initialization.py ├── test_deposit.py ├── test_fork_choice_helpers.py ├── test_init.py ├── test_initialize_epoch.py ├── test_logout.py ├── test_logs.py ├── test_multiple_validators.py ├── test_purity_checker.py ├── test_slashing.py ├── test_valcode_purity.py ├── test_voting.py └── utils ├── common_assertions.py ├── utils.py └── valcodes.py /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .env 3 | .idea 4 | .pytest_cache 5 | .python-version 6 | .DS_Store 7 | 8 | *.pyc 9 | *.egg-info 10 | data* 11 | notes.txt 12 | 13 | build/ 14 | dist/ 15 | venv/ 16 | prof/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: "3.6" 3 | 4 | script: 5 | - pytest tests 6 | -------------------------------------------------------------------------------- /IMPLEMENTATION.md: -------------------------------------------------------------------------------- 1 | ## Deprecated 2 | This guide has been deprecated in favor of [EIP 1011](http://eips.ethereum.org/EIPS/eip-1011) for client implementation and the [Validator Guide](https://github.com/ethereum/casper/blob/master/VALIDATOR_GUIDE.md) for validator implementation. 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Deprecated] 2 | 3 | Please refer to [Ethereum 2.0 Specifications](https://github.com/ethereum/eth2.0-specs) for the latest developments 4 | -------------------------------------------------------------------------------- /VALIDATOR_GUIDE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Outline 3 | 4 | This document outlines the components of implementing an FFG Validator. These include the: 5 | - Validator workflow overview 6 | - Validator States 7 | - Validator Code 8 | - Vote format and generation 9 | - Logout format 10 | - `simple_casper` contract overview 11 | - Casper transaction gas refunds 12 | - Simple validator voting logic. 13 | 14 | ## Validator Workflow Overview 15 | 1. Create valcode: 16 | - Deploy a new contract which is used to validate a validator's signature. 17 | 2. Submit Deposit: 18 | - Call `casper.deposit(validation_addr, withdrawal_addr)` passing in the validation code contract address from step (1) and your withdrawal address. 19 | 3. **[Once each Epoch]** Submit new vote message: 20 | - Wait to vote until the checkpoint is at least `EPOCH_LENGTH/4` blocks deep in the main chain. This ensures all validators vote on the same block. 21 | - Generate unsigned vote message based on your chain's current head. 22 | - Broadcast the unsigned vote transaction to the network. 23 | 4. Logout: 24 | - Submit logout message. 25 | - Call `casper.logout(logout_msg)` passing in your newly generated logout message. 26 | 5. Withdraw: 27 | - Call `casper.withdraw(validator_index)` and your funds will be sent to your validator's withdrawal address specified in step (2). 28 | 29 | ## Validator States 30 | Validators are highly stateful. They must handle valcode creation, depositing, voting, and logging out. Each stage also requires waiting for transaction confirmations. Because of this complexity, a mapping of state to handler is used. 31 | 32 | The validator state mapping [implemented in pyethapp](https://github.com/karlfloersch/pyethapp/blob/47df0f592533dded868f052dd51d37ebe57e612f/pyethapp/validator_service.py#L58-L67) is as follows: 33 | ``` 34 | uninitiated: self.check_status, 35 | waiting_for_valcode: self.check_valcode, 36 | waiting_for_login: self.check_status, 37 | voting: self.vote, 38 | waiting_for_log_out: self.vote_then_logout, 39 | waiting_for_withdrawable: self.check_withdrawable, 40 | waiting_for_withdrawn: self.check_withdrawn, 41 | logged_out: self.check_status 42 | ``` 43 | 44 | ### Validator State Transition Diagram 45 | ![ffg-validator](https://user-images.githubusercontent.com/706123/34855668-d2f55412-f6f5-11e7-8d83-370ffe65a9b8.png) 46 | Arrows are the logic followed upon receiving a new block while in a given state. For example, if the validator is in state `voting` and receives a new block whose height is `in_first_quarter_of_epoch`, then the validator follows the arrow to remain in the state `voting`. 47 | 48 | ## Validation Code 49 | Validators must deploy their own signature validation contract. This will be used to check the signatures attached to their votes. This validation code **must** be a pure function. This means no storage reads/writes, environment variable reads OR external calls (except to other contracts that have already been purity-verified, or to precompiles) allowed. 50 | 51 | For basic signature verification, [ecdsa signatures are currently being used](https://github.com/karlfloersch/pyethereum/blob/a66ab671e0bb19327bb8cd11d69664146451c250/ethereum/hybrid_casper/casper_utils.py#L73). The validation code for these ecdsa signatures can be found [here](https://github.com/karlfloersch/pyethereum/blob/a66ab671e0bb19327bb8cd11d69664146451c250/ethereum/hybrid_casper/casper_utils.py#L52). Note, this LLL code uses the elliptic curve public key recovery precompile. 52 | 53 | The validation code contract is currently being deployed as a part of the `induct_validator()` function [found here](https://github.com/karlfloersch/pyethereum/blob/a66ab671e0bb19327bb8cd11d69664146451c250/ethereum/hybrid_casper/casper_utils.py#L83-L87): 54 | ``` 55 | def induct_validator(chain, casper, key, value): 56 | sender = utils.privtoaddr(key) 57 | valcode_addr = chain.tx(key, "", 0, mk_validation_code(sender)) # Create a new validation code contract based on the validator's Ethereum address 58 | assert utils.big_endian_to_int(chain.tx(key, purity_checker_address, 0, purity_translator.encode('submit', [valcode_addr]))) == 1 59 | casper.deposit(valcode_addr, sender, value=value) # Submit deposit specifying the validation code contract address 60 | ``` 61 | 62 | ## Casper Vote Format 63 | A Casper vote is an RLP-encoded list with the elements: 64 | ``` 65 | [ 66 | validator_index: number, # Index of the validator sending this vote 67 | target_hash: bytes32, # Hash of the target checkpoint block for this vote 68 | target_epoch: number, # Epoch number of the target checkpoint 69 | source_epoch: number, # Epoch number of the source checkpoint 70 | signature # A signed hash of the first four elements in this list, RLP encoded. (ie. RLP([validator_index, target_hash, target_epoch, source_epoch]) 71 | ] 72 | ``` 73 | Casper vote messages are simply included in normal transactions sent to the Casper contract's `casper.vote(vote_msg)` function. 74 | 75 | ## Casper Vote Generation 76 | To generate a Casper vote which votes on your chain's current head, first get the vote message contents. To do this, using the Casper contract call: 77 | - `casper.validator_indexes(WITHDRAWAL_ADDRESS)` for the `validator_index` 78 | - `casper.recommended_target_hash()` for the `target_hash` 79 | - `casper.current_epoch()` for the `target_epoch` 80 | - `casper.recommended_source_epoch()` for the `source_epoch` 81 | 82 | Next, RLP encode all these elements. To compute your signature, compute the `sha3` hash of your vote's RLP encoded list, and sign the hash. Your signature must be valid when checked against your validator's `validation_code` contract. Finally, append your signature to the end of the vote message contents. 83 | 84 | This is [implemented in Pyethereum](https://github.com/karlfloersch/pyethereum/blob/a66ab671e0bb19327bb8cd11d69664146451c250/ethereum/hybrid_casper/casper_utils.py#L71-L75) as follows: 85 | ``` 86 | def mk_vote(validator_index, target_hash, target_epoch, source_epoch, key): 87 | msg_hash = utils.sha3(rlp.encode([validator_index, target_hash, target_epoch, source_epoch])) 88 | v, r, s = utils.ecdsa_raw_sign(msg_hash, key) 89 | sig = utils.encode_int32(v) + utils.encode_int32(r) + utils.encode_int32(s) 90 | return rlp.encode([validator_index, target_hash, target_epoch, source_epoch, sig]) 91 | ``` 92 | 93 | ## Logout Message Generation 94 | Like the Casper vote messages, a logout message is an RLP encoded list where the last element is the validator's signature. The elements of the unsigned list are the `validator_index` and `epoch` where epoch is the current epoch. A signature is generated in the same way it is done with votes above. 95 | 96 | This is [implemented in Pyethereum](https://github.com/karlfloersch/pyethereum/blob/a66ab671e0bb19327bb8cd11d69664146451c250/ethereum/hybrid_casper/casper_utils.py#L77-L81) as follows: 97 | ``` 98 | def mk_logout(validator_index, epoch, key): 99 | msg_hash = utils.sha3(rlp.encode([validator_index, epoch])) 100 | v, r, s = utils.ecdsa_raw_sign(msg_hash, key) 101 | sig = utils.encode_int32(v) + utils.encode_int32(r) + utils.encode_int32(s) 102 | return rlp.encode([validator_index, epoch, sig]) 103 | ``` 104 | 105 | ## Simple Casper Contract High Level Overview 106 | The Casper smart contract contains Casper's core logic. It is written in Vyper & can be deployed to the blockchain like any other contract to `CASPER_ADDR`. Casper messages are then sent to the contract by calling `vote(vote_msg)` where `vote_msg` is a Casper vote messaged as outlined above. 107 | 108 | ### [[Contract Source]](https://github.com/ethereum/casper/blob/master/casper/contracts/simple_casper.v.py) 109 | 110 | #### `def __init__(epoch_length, withdrawal_delay, ...)` 111 | The Casper contract constructor takes in key settings. Most notably: 112 | - `epoch_length` 113 | - `withdrawal_delay` 114 | - `dynasty_logout_delay` 115 | - `min_deposit_size` 116 | - `base_interest_factor` 117 | - `base_penalty_factor` 118 | 119 | These settings cannot be changed after deployment. 120 | 121 | #### `def initialize_epoch(epoch)` 122 | Calculates the interest rate & penalty factor for this epoch based on the time since finality. 123 | 124 | Once a new epoch begins, this function is immediately called as the first transaction applied to the state. See Epoch initialization for more details. 125 | 126 | #### `def deposit(validation_addr, withdrawal_addr)` 127 | Accepts deposits from prospective validators & adds them to the next validator set. 128 | 129 | #### `def logout(logout_msg)` 130 | Initiates validator logout. The validator must continue to validate for `dynasty_logout_delay` dynasties before entering the `withdrawal_delay` waiting period. 131 | 132 | #### `def withdraw(validator_index)` 133 | If the validator has waited for a period greater than `withdrawal_delay` epochs past their `end_dynasty`, then send them ETH equivalent to their deposit. 134 | 135 | #### `def vote(vote_msg)` 136 | Called once by each validator each epoch. The vote message contains the fields presented in Casper Vote Format. 137 | 138 | #### `def slash(vote_msg_1, vote_msg_2)` 139 | Can be called by anyone who detects a slashing condition violation. Sends 4% of slashed validator's funds to the caller as a finder's fee and burns the remaining 96%. 140 | 141 | ## Casper Vote Gas Refunds 142 | Casper votes do not pay gas if successful, and will be considered invalid transactions and not be included in the block if failed. This avoids a large gas burden on validators. 143 | 144 | ## Simple Validator Voting Logic 145 | Once a validator is logged in, they can use the following logic to determine when to send a vote: 146 | 1. When a new block is received & replaces our chain's current head, call `validate(block)` 147 | 2. Inside `validate(block)` check: 148 | 1) The block is at least `EPOCH_LENGTH/4` blocks deep to ensure that the checkpoint hash is safe to vote on. 149 | 2) [NO_DBL_VOTE] The block's epoch has not been voted on before. 150 | 3) [NO_SURROUND] The block's `target_epoch` >= `self.latest_target_epoch` and `source_epoch` >= `self.latest_source_epoch`. NOTE: This check is very simple, but it excludes a couple cases where it would be safe to vote. 151 | 3. If all of the checks pass, generate & send a new vote! 152 | 153 | NOTE: To check if a validator is logged in, one can use: 154 | ``` python 155 | return casper.validators__start_dynasty(validator_index) <= casper.dynasty() 156 | ``` 157 | 158 | #### [[See the current validator implementation for more information.]](https://github.com/karlfloersch/pyethapp/blob/dev_env/pyethapp/validator_service.py) 159 | -------------------------------------------------------------------------------- /casper/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ############# version ################## 3 | import os.path 4 | import re 5 | import subprocess 6 | 7 | from pkg_resources import get_distribution, DistributionNotFound 8 | 9 | GIT_DESCRIBE_RE = re.compile('^(?Pv\d+\.\d+\.\d+)-(?P\d+-g[a-fA-F0-9]+(?:-dirty)?)$') 10 | 11 | 12 | __version__ = None 13 | try: 14 | _dist = get_distribution('casper') 15 | # Normalize case for Windows systems 16 | dist_loc = os.path.normcase(_dist.location) 17 | here = os.path.normcase(__file__) 18 | if not here.startswith(os.path.join(dist_loc, 'casper')): 19 | # not installed, but there is another version that *is* 20 | raise DistributionNotFound 21 | __version__ = _dist.version 22 | except DistributionNotFound: 23 | pass 24 | 25 | if not __version__: 26 | try: 27 | rev = subprocess.check_output(['git', 'describe', '--tags', '--dirty'], 28 | stderr=subprocess.STDOUT) 29 | match = GIT_DESCRIBE_RE.match(rev) 30 | if match: 31 | __version__ = "{}+git-{}".format(match.group("version"), match.group("git")) 32 | except: 33 | pass 34 | 35 | if not __version__: 36 | __version__ = 'undefined' 37 | 38 | # ########### endversion ################## 39 | -------------------------------------------------------------------------------- /casper/contracts/msg_hash.se.py: -------------------------------------------------------------------------------- 1 | # Fetches the char from calldata at position $x 2 | macro calldatachar($x): 3 | div(calldataload($x), 2**248) 4 | 5 | # Fetches the next $b bytes from calldata starting at position $x 6 | # Assumes that there is nothing important in memory at bytes 0..63 7 | macro calldatabytes_as_int($x, $b): 8 | ~mstore(32-$b, calldataload($x)) 9 | ~mload(0) 10 | 11 | # Position in calldata 12 | with pos = 0: 13 | # First char in calldata 14 | with c0 = calldatachar(0): 15 | # The start of the array must be in 192...255 because it represents 16 | # a list length 17 | # Length ++ body case 18 | if c0 < 248: 19 | pos = 1 20 | # Length of length ++ length ++ body case 21 | else: 22 | pos = (c0 - 246) 23 | # Start position of the list (save it) 24 | with startpos = pos: 25 | # Start position of the previous element 26 | with lastpos = 0: 27 | # Keep looping until we hit the end of the input 28 | while pos < ~calldatasize(): 29 | # Next char in calldata 30 | with c = calldatachar(pos): 31 | lastpos = pos 32 | # Single byte 0x00...0x7f body case 33 | if c < 128: 34 | pos += 1 35 | # Length ++ body case 36 | elif c < 184: 37 | pos += c - 127 38 | # Length of length ++ length ++ body case 39 | elif c < 192: 40 | pos += calldatabytes_as_int(pos + 1, c - 183) + (c - 182) 41 | 42 | # Length of new RLP list 43 | with newlen = lastpos - startpos: 44 | # Length ++ body case 45 | if newlen < 56: 46 | # Store length in the first byte 47 | ~mstore8(0, 192 + newlen) 48 | # Copy calldata right after length 49 | ~calldatacopy(1, startpos, newlen) 50 | # Return the hash 51 | return(~sha3(0, 1 + newlen)) 52 | else: 53 | # The log256 of the length (ie. length of length) 54 | # Can't go higher than 16777216 bytes due to gas limits 55 | with _log = if(newlen < 256, 1, if(newlen < 65536, 2, 3)): 56 | # Store the length 57 | ~mstore(0, newlen) 58 | # Store the length of the length right before the length 59 | with 31minuslog = 31 - _log: 60 | ~mstore8(31minuslog, 247 + _log) 61 | # Store the rest of the data 62 | ~calldatacopy(32, startpos, newlen) 63 | # Return the hash 64 | return(~sha3(31minuslog, 1 + _log + newlen)) 65 | -------------------------------------------------------------------------------- /casper/contracts/purity_checker.py: -------------------------------------------------------------------------------- 1 | ## This Python source code hosts the Vyper LLL used for the purity checker. 2 | ## The purity checker scans a contract's bytecode to see if it uses any operations that rely on (external) mutable state. 3 | ## This code was ported from an original written in the deprecated Serpent: https://github.com/ethereum/research/blob/master/impurity/check_for_impurity.se 4 | 5 | ## The following are memory maps for each function: 6 | # MEMORY MAP for `submit` method 7 | # [320, 351]: addr, the input address, 32 bytes 8 | # [352, 352+_EXTCODESIZE-1]: bytecode at addr, _EXTCODESIZE bytes 9 | # [352+_EXTCODESIZE, 352+33*_EXTCODESIZE-32]: ops, array to hold processed opcodes, 32*_EXTCODESIZE bytes 10 | # [352+33*_EXTCODESIZE, 352+65*_EXTCODESIZE-32]: pushargs, array to hold processed push arguments, 32*_EXTCODESIZE bytes 11 | # [352+65*_EXTCODESIZE, 383+65*_EXTCODESIZE]: i, loop counter, 32 bytes 12 | 13 | # MEMORY MAP for `check` method 14 | # [320, 351]: addr, the input address, 32 bytes 15 | 16 | from vyper import compile_lll, optimizer 17 | from vyper.parser.parser import LLLnode 18 | 19 | from vyper.opcodes import opcodes 20 | 21 | def find_opcode_hex(opcode): 22 | if opcode in opcodes: 23 | return opcodes[opcode][0] 24 | return opcode 25 | 26 | banned_opcodes = map(find_opcode_hex,[ 27 | 'BALANCE', 28 | 'ORIGIN', 29 | 'CALLER', 30 | 'GASPRICE', 31 | 'EXTCODESIZE', 32 | 'EXTCODECOPY', 33 | 'BLOCKHASH', 34 | 'COINBASE', 35 | 'TIMESTAMP', 36 | 'NUMBER', 37 | 'DIFFICULTY', 38 | 'GASLIMIT', 39 | 0x46, # rest of the 0x40 opcode space 40 | 0x47, 41 | 0x48, 42 | 0x49, 43 | 0x4a, 44 | 0x4b, 45 | 0x4c, 46 | 0x4d, 47 | 0x4e, 48 | 0x4f, 49 | 'SLOAD', 50 | 'SSTORE', 51 | 'GAS', 52 | 'CREATE', 53 | 'SELFDESTRUCT' 54 | ]) 55 | 56 | banned_opcodes_bitmask = sum([2**x for x in banned_opcodes]) 57 | 58 | invalid_if_banned = ["if", 59 | ["and", banned_opcodes_bitmask, ["exp", 2, "_c"]], 60 | "invalid"] 61 | 62 | is_push = ["and", ["le", 0x60, "_c"], ["le", "_c", 0x7f]] 63 | 64 | def index_pushargs(index): 65 | return ["add", ["add", 352, ["mul", 33, "_EXTCODESIZE"]], ["mul", 32, index]] 66 | 67 | handle_push = ["seq", 68 | ["mstore", index_pushargs("_op"), ["div", ["mload", ["add", ["add", 352, ["mload", "_i"]], 1]], ["exp", 256, ["sub", 0x7f, "_c"]]]], 69 | ["mstore", "_i", ["add", ["sub", "_c", 0x5f], ["mload", "_i"]]]] # there is an extra -1 in here to account for the increment of the repeat loop; -0x5e ~> -0x5f from the serpent code 70 | 71 | is_some_call = ["or", ["eq", "_c", 0xf1], 72 | ["or", ["eq", "_c", 0xf2], ["eq", "_c", 0xf4]]] 73 | 74 | def index_ops(index): 75 | return ["add", ["add", 352, "_EXTCODESIZE"], ["mul", 32, index]] 76 | 77 | find_address = ["if", ["and", ["ge", "_op", 2], 78 | ["and", ["ge", ["mload", index_ops(["sub", "_op", 1])], 0x60], 79 | ["le", ["mload", index_ops(["sub", "_op", 1])], 0x7f]]], 80 | ["set", "_address_entry", ["sub", "_op", 2]], 81 | ["if", 82 | ["and", ["ge", "_op", 4], 83 | ["and", ["eq", ["mload", index_ops(["sub", "_op", 1])], 0x03], 84 | ["and", ["eq", 85 | ["mload", index_ops(["sub", "_op", 2])], 0x5a], 86 | ["and", ["ge", 87 | ["mload", index_ops(["sub", "_op", 3])], 0x60], 88 | ["le", 89 | ["mload", index_ops(["sub", "_op", 3])], 0x7f]]]]], 90 | ["set", "_address_entry", ["sub", "_op", 4]], 91 | ["if", ["and", ["ge", "_op", 2], 92 | ["eq", 93 | ["mload", index_ops(["sub", "_op", 1])], 0x5a]], 94 | ["set", "_address_entry", ["sub", "_op", 2]], 95 | ["if", ["and", ["ge", "_op", 2], 96 | ["eq", 97 | ["mload", index_ops(["sub", "_op", 1])], 0x90]], 98 | ["set", "_address_entry", ["sub", "_op", 2]], 99 | ["if", ["and", ["ge", "_op", 2], 100 | ["and", ["ge", 101 | ["mload", index_ops(["sub", "_op", 1])], 0x80], 102 | ["lt", 103 | ["mload", index_ops(["sub", "_op", 1])], 0x90]]], 104 | ["set", "_address_entry", ["sub", "_op", 2]], 105 | "invalid"]]]]] 106 | 107 | filter_address_usage = ["if", ["sload", ["add", ["sha3_32", 0], # self.approved_addrs 108 | ["mload", index_pushargs("_address_entry")]]], 109 | ["seq"], 110 | ["if", ["eq", 111 | ["mload", index_ops("_address_entry")], 0x30], 112 | ["seq"], 113 | ["if", ["eq", 114 | ["mload", index_ops("_address_entry")], 0x60], 115 | ["seq"], 116 | "invalid"]]] 117 | 118 | handle_some_call = ["with", "_address_entry", 0, 119 | ["seq", 120 | find_address, 121 | filter_address_usage]] 122 | 123 | dispatch_compound_sequences = ["if", is_push, 124 | handle_push, 125 | ["if", is_some_call, 126 | handle_some_call]] 127 | 128 | process_byte = ["seq", 129 | invalid_if_banned, 130 | dispatch_compound_sequences, 131 | ["mstore", ["add", ["add", 352, "_EXTCODESIZE"], ["mul", 32, "_op"]], "_c"], 132 | ["set", "_op", ["add", "_op", 1]]] 133 | 134 | loop_body = ["if", 135 | ["ge", ["mload", "_i"], "_EXTCODESIZE"], 136 | "break", 137 | ["with", "_c", ["mod", ["mload", ["add", 352, ["sub", ["mload", "_i"], 31]]], 256], 138 | process_byte]] 139 | 140 | purity_checker_lll = LLLnode.from_list( 141 | ["seq", 142 | ["return", 143 | 0, 144 | ["lll", 145 | ["seq", 146 | ["mstore", 28, ["calldataload", 0]], 147 | ["mstore", 32, 1461501637330902918203684832716283019655932542976], 148 | ["mstore", 64, 170141183460469231731687303715884105727], 149 | ["mstore", 96, -170141183460469231731687303715884105728], 150 | ["mstore", 128, 1701411834604692317316873037158841057270000000000], 151 | ["mstore", 160, -1701411834604692317316873037158841057280000000000], 152 | ["if", 153 | ["eq", ["mload", 0], 2710585003], # submit 154 | ["seq", 155 | ["calldatacopy", 320, 4, 32], 156 | ["assert", ["iszero", "callvalue"]], 157 | ["uclamplt", ["calldataload", 4], ["mload", 32]], # checking address input 158 | # scan bytecode at address input 159 | ["with", "_EXTCODESIZE", ["extcodesize", ["mload", 320]], # addr 160 | ["if", ["eq", "_EXTCODESIZE", 0], 161 | "invalid", # ban accounts with no code 162 | ["seq", 163 | ["extcodecopy", ["mload", 320], 352, 0, "_EXTCODESIZE"], 164 | ["with", "_i", ["add", 352, ["mul", 65, "_EXTCODESIZE"]], 165 | ["with", "_op", 0, 166 | ["repeat", "_i", 0, 167 | 115792089237316195423570985008687907853269984665640564039457584007913129639935, 168 | loop_body]]]]]], 169 | # approve the address `addr` 170 | ["sstore", ["add", ["sha3_32", 0], ["mload", 320]], 1], 171 | ["mstore", 0, 1], 172 | ["return", 0, 32], 173 | "stop"]], 174 | ["if", 175 | ["eq", ["mload", 0], 3258357672], # check 176 | ["seq", 177 | ["calldatacopy", 320, 4, 32], 178 | ["assert", ["iszero", "callvalue"]], 179 | ["uclamplt", ["calldataload", 4], ["mload", 32]], # checking address input 180 | ["mstore", 0, ["sload", ["add", ["sha3_32", 0], ["mload", 320]]]], 181 | ["return", 0, 32], 182 | "stop"]]], 183 | 0]]]) 184 | 185 | def lll_to_evm(lll): 186 | return compile_lll.assembly_to_evm(compile_lll.compile_to_assembly(optimizer.optimize(lll))) 187 | 188 | def purity_checker_data(): 189 | return lll_to_evm(purity_checker_lll) 190 | 191 | def purity_checker_data_hex(): 192 | return '0x' + purity_checker_data().hex() 193 | -------------------------------------------------------------------------------- /casper/contracts/simple_casper.v.py: -------------------------------------------------------------------------------- 1 | # 2 | # List of events the contract logs 3 | # Withdrawal address used always in _from and _to as it's unique 4 | # and validator index is removed after some events 5 | # 6 | Deposit: event({_from: indexed(address), _validator_index: indexed(uint256), _validation_address: address, _start_dyn: uint256, _amount: wei_value}) 7 | Vote: event({_from: indexed(address), _validator_index: indexed(uint256), _target_hash: indexed(bytes32), _target_epoch: uint256, _source_epoch: uint256}) 8 | Logout: event({_from: indexed(address), _validator_index: indexed(uint256), _end_dyn: uint256}) 9 | Withdraw: event({_to: indexed(address), _validator_index: indexed(uint256), _amount: wei_value}) 10 | Slash: event({_from: indexed(address), _offender: indexed(address), _offender_index: indexed(uint256), _bounty: wei_value}) 11 | Epoch: event({_number: indexed(uint256), _checkpoint_hash: indexed(bytes32), _is_justified: bool, _is_finalized: bool}) 12 | 13 | units: { 14 | sf: "scale_factor" 15 | } 16 | 17 | validators: public({ 18 | # Used to determine the amount of wei the validator holds. To get the actual 19 | # amount of wei, multiply this by the deposit_scale_factor. 20 | deposit: decimal(wei/sf), 21 | start_dynasty: uint256, 22 | end_dynasty: uint256, 23 | is_slashed: bool, 24 | total_deposits_at_logout: wei_value, 25 | # The address which the validator's signatures must verify against 26 | addr: address, 27 | withdrawal_addr: address 28 | }[uint256]) 29 | 30 | # Map of epoch number to checkpoint hash 31 | checkpoint_hashes: public(bytes32[uint256]) 32 | 33 | # Next available validator index 34 | next_validator_index: public(uint256) 35 | 36 | # Mapping of validator's withdrawal address to their index number 37 | validator_indexes: public(uint256[address]) 38 | 39 | # Current dynasty, it measures the number of finalized checkpoints 40 | # in the chain from root to the parent of current block 41 | dynasty: public(uint256) 42 | 43 | # Map of the change to total deposits for specific dynasty 44 | dynasty_wei_delta: public(decimal(wei / sf)[uint256]) 45 | 46 | # Total scaled deposits in the current dynasty 47 | total_curdyn_deposits: decimal(wei / sf) 48 | 49 | # Total scaled deposits in the previous dynasty 50 | total_prevdyn_deposits: decimal(wei / sf) 51 | 52 | # Mapping of dynasty to start epoch of that dynasty 53 | dynasty_start_epoch: public(uint256[uint256]) 54 | 55 | # Mapping of epoch to what dynasty it is 56 | dynasty_in_epoch: public(uint256[uint256]) 57 | 58 | checkpoints: public({ 59 | # track size of scaled deposits for use in client fork choice 60 | cur_dyn_deposits: wei_value, 61 | prev_dyn_deposits: wei_value, 62 | # track total votes for each dynasty 63 | cur_dyn_votes: decimal(wei / sf)[uint256], 64 | prev_dyn_votes: decimal(wei / sf)[uint256], 65 | # Bitmap of which validator IDs have already voted 66 | vote_bitmap: uint256[uint256], 67 | # Is a vote referencing the given epoch justified? 68 | is_justified: bool, 69 | # Is a vote referencing the given epoch finalized? 70 | is_finalized: bool 71 | }[uint256]) # index: target epoch 72 | 73 | # Is the current expected hash justified 74 | main_hash_justified: public(bool) 75 | 76 | # Value used to calculate the per-epoch fee that validators should be charged 77 | deposit_scale_factor: public(decimal(sf)[uint256]) 78 | 79 | last_nonvoter_rescale: public(decimal) 80 | last_voter_rescale: public(decimal) 81 | 82 | current_epoch: public(uint256) 83 | last_finalized_epoch: public(uint256) 84 | last_justified_epoch: public(uint256) 85 | 86 | # Reward for voting as fraction of deposit size 87 | reward_factor: public(decimal) 88 | 89 | # Expected source epoch for a vote 90 | expected_source_epoch: public(uint256) 91 | 92 | # Running total of deposits slashed 93 | total_slashed: public(wei_value[uint256]) 94 | 95 | # Flag that only allows contract initialization to happen once 96 | initialized: bool 97 | 98 | # ***** Parameters ***** 99 | 100 | # Length of an epoch in blocks 101 | EPOCH_LENGTH: public(uint256) 102 | 103 | # Length of warm up period in blocks 104 | WARM_UP_PERIOD: public(uint256) 105 | 106 | # Withdrawal delay in blocks 107 | WITHDRAWAL_DELAY: public(uint256) 108 | 109 | # Logout delay in dynasties 110 | DYNASTY_LOGOUT_DELAY: public(uint256) 111 | 112 | # MSG_HASHER calculator library address 113 | # Hashes message contents but not the signature 114 | MSG_HASHER: address 115 | 116 | # Purity checker library address 117 | PURITY_CHECKER: address 118 | 119 | NULL_SENDER: public(address) 120 | BASE_INTEREST_FACTOR: public(decimal) 121 | BASE_PENALTY_FACTOR: public(decimal) 122 | MIN_DEPOSIT_SIZE: public(wei_value) 123 | START_EPOCH: public(uint256) 124 | 125 | # ****** Pre-defined Constants ****** 126 | 127 | DEFAULT_END_DYNASTY: uint256 128 | MSG_HASHER_GAS_LIMIT: uint256 129 | VALIDATION_GAS_LIMIT: uint256 130 | SLASH_FRACTION_MULTIPLIER: uint256 131 | 132 | ONE_WEI_UINT256: wei_value 133 | ONE_WEI_DECIMAL: decimal(wei) 134 | 135 | 136 | 137 | @public 138 | def init(epoch_length: uint256, 139 | warm_up_period: uint256, 140 | withdrawal_delay: uint256, 141 | dynasty_logout_delay: uint256, 142 | msg_hasher: address, 143 | purity_checker: address, 144 | null_sender: address, 145 | base_interest_factor: decimal, 146 | base_penalty_factor: decimal, 147 | min_deposit_size: wei_value): 148 | 149 | assert not self.initialized 150 | assert epoch_length > 0 and epoch_length < 256 151 | assert dynasty_logout_delay >= 2 152 | assert base_interest_factor >= 0.0 153 | assert base_penalty_factor >= 0.0 154 | assert min_deposit_size > 0 155 | 156 | self.initialized = True 157 | 158 | self.EPOCH_LENGTH = epoch_length 159 | self.WARM_UP_PERIOD = warm_up_period 160 | self.WITHDRAWAL_DELAY = withdrawal_delay 161 | self.DYNASTY_LOGOUT_DELAY = dynasty_logout_delay 162 | self.BASE_INTEREST_FACTOR = base_interest_factor 163 | self.BASE_PENALTY_FACTOR = base_penalty_factor 164 | self.MIN_DEPOSIT_SIZE = min_deposit_size 165 | 166 | self.START_EPOCH = (block.number + warm_up_period) / self.EPOCH_LENGTH 167 | 168 | # helper contracts 169 | self.MSG_HASHER = msg_hasher 170 | self.PURITY_CHECKER = purity_checker 171 | 172 | self.NULL_SENDER = null_sender 173 | 174 | # Start validator index counter at 1 because validator_indexes[] requires non-zero values 175 | self.next_validator_index = 1 176 | 177 | self.dynasty = 0 178 | self.current_epoch = self.START_EPOCH 179 | # TODO: test deposit_scale_factor when deploying when current_epoch > 0 180 | self.deposit_scale_factor[self.current_epoch] = 10000000000.0 181 | self.total_curdyn_deposits = 0 182 | self.total_prevdyn_deposits = 0 183 | self.DEFAULT_END_DYNASTY = 1000000000000000000000000000000 184 | self.MSG_HASHER_GAS_LIMIT = 200000 185 | self.VALIDATION_GAS_LIMIT = 200000 186 | self.SLASH_FRACTION_MULTIPLIER = 3 187 | self.ONE_WEI_UINT256 = 1 188 | self.ONE_WEI_DECIMAL = 1.0 189 | 190 | 191 | # ****** Private Constants ***** 192 | 193 | # Returns number of epochs since finalization. 194 | @private 195 | @constant 196 | def esf() -> uint256: 197 | return self.current_epoch - self.last_finalized_epoch 198 | 199 | 200 | # Compute square root factor 201 | @private 202 | @constant 203 | def sqrt_of_total_deposits() -> decimal: 204 | epoch: uint256 = self.current_epoch 205 | one_ether_as_wei: decimal = convert(convert(as_wei_value(1, "ether"), "int128"), "decimal") 206 | ether_deposited_as_number: decimal = convert( 207 | 1 + as_unitless_number( 208 | floor( 209 | max(self.total_prevdyn_deposits, self.total_curdyn_deposits) * 210 | self.deposit_scale_factor[epoch - 1] / 211 | one_ether_as_wei 212 | ) 213 | ), 214 | "decimal" 215 | ) 216 | sqrt: decimal = ether_deposited_as_number / 2.0 217 | for i in range(20): 218 | sqrt = (sqrt + (ether_deposited_as_number / sqrt)) / 2.0 219 | return sqrt 220 | 221 | 222 | @private 223 | @constant 224 | def deposit_exists() -> bool: 225 | return self.total_curdyn_deposits > 0.0 and self.total_prevdyn_deposits > 0.0 226 | 227 | 228 | @private 229 | @constant 230 | def in_dynasty(validator_index:uint256, _dynasty:uint256) -> bool: 231 | start_dynasty: uint256 = self.validators[validator_index].start_dynasty 232 | end_dynasty: uint256 = self.validators[validator_index].end_dynasty 233 | return (start_dynasty <= _dynasty) and (_dynasty < end_dynasty) 234 | 235 | 236 | # ***** Private ***** 237 | 238 | # Increment dynasty when checkpoint is finalized. 239 | # TODO: Might want to split out the cases separately. 240 | @private 241 | def increment_dynasty(): 242 | epoch: uint256 = self.current_epoch 243 | # Increment the dynasty if finalized 244 | if self.checkpoints[epoch - 2].is_finalized: 245 | self.dynasty += 1 246 | self.total_prevdyn_deposits = self.total_curdyn_deposits 247 | self.total_curdyn_deposits += self.dynasty_wei_delta[self.dynasty] 248 | self.dynasty_start_epoch[self.dynasty] = epoch 249 | self.dynasty_in_epoch[epoch] = self.dynasty 250 | if self.main_hash_justified: 251 | self.expected_source_epoch = epoch - 1 252 | self.main_hash_justified = False 253 | 254 | 255 | @private 256 | def insta_finalize(): 257 | epoch: uint256 = self.current_epoch 258 | self.main_hash_justified = True 259 | self.checkpoints[epoch - 1].is_justified = True 260 | self.checkpoints[epoch - 1].is_finalized = True 261 | self.last_justified_epoch = epoch - 1 262 | self.last_finalized_epoch = epoch - 1 263 | # Log previous Epoch status update 264 | log.Epoch(epoch - 1, self.checkpoint_hashes[epoch - 1], True, True) 265 | 266 | 267 | # Returns the current collective reward factor, which rewards the dynasty for high-voting levels. 268 | @private 269 | def collective_reward() -> decimal: 270 | epoch: uint256 = self.current_epoch 271 | live: bool = self.esf() <= 2 272 | if not self.deposit_exists() or not live: 273 | return 0.0 274 | # Fraction that voted 275 | cur_vote_frac: decimal = self.checkpoints[epoch - 1].cur_dyn_votes[self.expected_source_epoch] / self.total_curdyn_deposits 276 | prev_vote_frac: decimal = self.checkpoints[epoch - 1].prev_dyn_votes[self.expected_source_epoch] / self.total_prevdyn_deposits 277 | vote_frac: decimal = min(cur_vote_frac, prev_vote_frac) 278 | return vote_frac * self.reward_factor / 2.0 279 | 280 | 281 | # Reward the given validator & miner, and reflect this in total deposit figured 282 | @private 283 | def proc_reward(validator_index: uint256, reward: decimal(wei/sf)): 284 | # Reward validator 285 | self.validators[validator_index].deposit += reward 286 | start_dynasty: uint256 = self.validators[validator_index].start_dynasty 287 | end_dynasty: uint256 = self.validators[validator_index].end_dynasty 288 | current_dynasty: uint256 = self.dynasty 289 | past_dynasty: uint256 = current_dynasty - 1 290 | if ((start_dynasty <= current_dynasty) and (current_dynasty < end_dynasty)): 291 | self.total_curdyn_deposits += reward 292 | if ((start_dynasty <= past_dynasty) and (past_dynasty < end_dynasty)): 293 | self.total_prevdyn_deposits += reward 294 | if end_dynasty < self.DEFAULT_END_DYNASTY: # validator has submit `logout` 295 | self.dynasty_wei_delta[end_dynasty] -= reward 296 | # Reward miner 297 | miner_reward: wei_value = convert( 298 | floor(reward * self.deposit_scale_factor[self.current_epoch] / 8.0), 299 | "uint256" 300 | ) * self.ONE_WEI_UINT256 301 | send(block.coinbase, miner_reward) 302 | 303 | 304 | # Removes a validator from the validator pool 305 | @private 306 | def delete_validator(validator_index: uint256): 307 | self.validator_indexes[self.validators[validator_index].withdrawal_addr] = 0 308 | self.validators[validator_index] = { 309 | deposit: 0, 310 | start_dynasty: 0, 311 | end_dynasty: 0, 312 | is_slashed: False, 313 | total_deposits_at_logout: 0, 314 | addr: None, 315 | withdrawal_addr: None 316 | } 317 | 318 | 319 | # cannot be labeled @constant because of external call 320 | # even though the call is to a pure contract call 321 | @private 322 | def validate_signature(msg_hash: bytes32, sig: bytes[1024], validator_index: uint256) -> bool: 323 | return extract32(raw_call(self.validators[validator_index].addr, concat(msg_hash, sig), gas=self.VALIDATION_GAS_LIMIT, outsize=32), 0) == convert(1, 'bytes32') 324 | 325 | 326 | # ***** Public Constants ***** 327 | 328 | @public 329 | @constant 330 | def main_hash_voted_frac() -> decimal: 331 | return min(self.checkpoints[self.current_epoch].cur_dyn_votes[self.expected_source_epoch] / self.total_curdyn_deposits, 332 | self.checkpoints[self.current_epoch].prev_dyn_votes[self.expected_source_epoch] / self.total_prevdyn_deposits) 333 | 334 | 335 | @public 336 | @constant 337 | def deposit_size(validator_index: uint256) -> wei_value: 338 | return self.ONE_WEI_UINT256 * convert( 339 | floor(self.validators[validator_index].deposit * self.deposit_scale_factor[self.current_epoch]), 340 | "uint256" 341 | ) 342 | 343 | 344 | @public 345 | @constant 346 | def total_curdyn_deposits_in_wei() -> wei_value: 347 | return self.ONE_WEI_UINT256 * convert( 348 | floor(self.total_curdyn_deposits * self.deposit_scale_factor[self.current_epoch]), 349 | "uint256" 350 | ) 351 | 352 | 353 | @public 354 | @constant 355 | def total_prevdyn_deposits_in_wei() -> wei_value: 356 | return self.ONE_WEI_UINT256 * convert( 357 | floor(self.total_prevdyn_deposits * self.deposit_scale_factor[self.current_epoch]), 358 | "uint256" 359 | ) 360 | 361 | 362 | @public 363 | # cannot be labeled @constant because of external call 364 | def validate_vote_signature(vote_msg: bytes[1024]) -> bool: 365 | msg_hash: bytes32 = extract32( 366 | raw_call(self.MSG_HASHER, vote_msg, gas=self.MSG_HASHER_GAS_LIMIT, outsize=32), 367 | 0 368 | ) 369 | # Extract parameters 370 | values = RLPList(vote_msg, [uint256, bytes32, uint256, uint256, bytes]) 371 | validator_index: uint256 = values[0] 372 | sig: bytes[1024] = values[4] 373 | 374 | return self.validate_signature(msg_hash, sig, validator_index) 375 | 376 | 377 | @public 378 | # cannot be labeled @constant because of external call 379 | # even though the call is to a pure contract call 380 | def slashable(vote_msg_1: bytes[1024], vote_msg_2: bytes[1024]) -> bool: 381 | # Message 1: Extract parameters 382 | msg_hash_1: bytes32 = extract32( 383 | raw_call(self.MSG_HASHER, vote_msg_1, gas=self.MSG_HASHER_GAS_LIMIT, outsize=32), 384 | 0 385 | ) 386 | values_1 = RLPList(vote_msg_1, [uint256, bytes32, uint256, uint256, bytes]) 387 | validator_index_1: uint256 = values_1[0] 388 | target_epoch_1: uint256 = values_1[2] 389 | source_epoch_1: uint256 = values_1[3] 390 | sig_1: bytes[1024] = values_1[4] 391 | 392 | # Message 2: Extract parameters 393 | msg_hash_2: bytes32 = extract32( 394 | raw_call(self.MSG_HASHER, vote_msg_2, gas=self.MSG_HASHER_GAS_LIMIT, outsize=32), 395 | 0 396 | ) 397 | values_2 = RLPList(vote_msg_2, [uint256, bytes32, uint256, uint256, bytes]) 398 | validator_index_2: uint256 = values_2[0] 399 | target_epoch_2: uint256 = values_2[2] 400 | source_epoch_2: uint256 = values_2[3] 401 | sig_2: bytes[1024] = values_2[4] 402 | 403 | if not self.validate_signature(msg_hash_1, sig_1, validator_index_1): 404 | return False 405 | if not self.validate_signature(msg_hash_2, sig_2, validator_index_2): 406 | return False 407 | if validator_index_1 != validator_index_2: 408 | return False 409 | if self.validators[validator_index_1].start_dynasty > self.dynasty: 410 | return False 411 | if msg_hash_1 == msg_hash_2: 412 | return False 413 | if self.validators[validator_index_1].is_slashed: 414 | return False 415 | 416 | double_vote: bool = target_epoch_1 == target_epoch_2 417 | surround_vote: bool = ( 418 | target_epoch_1 > target_epoch_2 and source_epoch_1 < source_epoch_2 or 419 | target_epoch_2 > target_epoch_1 and source_epoch_2 < source_epoch_1 420 | ) 421 | 422 | return double_vote or surround_vote 423 | 424 | 425 | # 426 | # Helper functions that clients can call to know what to vote 427 | # 428 | 429 | @public 430 | @constant 431 | def recommended_source_epoch() -> uint256: 432 | return self.expected_source_epoch 433 | 434 | 435 | @public 436 | @constant 437 | def recommended_target_hash() -> bytes32: 438 | return blockhash(self.current_epoch*self.EPOCH_LENGTH - 1) 439 | 440 | 441 | # 442 | # Helper methods for client fork choice 443 | # NOTE: both methods use a non-conventional loop structure 444 | # with an incredibly high range and a return/break to exit. 445 | # This is to bypass vyper's prevention of unbounded loops. 446 | # This has been assessed as a reasonable tradeoff because these 447 | # methods are 'constant' and are only to be called locally rather 448 | # than as a part of an actual block tx. 449 | # 450 | 451 | @public 452 | @constant 453 | def highest_justified_epoch(min_total_deposits: wei_value) -> uint256: 454 | epoch: uint256 455 | for i in range(1000000000000000000000000000000): 456 | epoch = self.current_epoch - convert(i, "uint256") 457 | is_justified: bool = self.checkpoints[epoch].is_justified 458 | enough_cur_dyn_deposits: bool = self.checkpoints[epoch].cur_dyn_deposits >= min_total_deposits 459 | enough_prev_dyn_deposits: bool = self.checkpoints[epoch].prev_dyn_deposits >= min_total_deposits 460 | 461 | if is_justified and (enough_cur_dyn_deposits and enough_prev_dyn_deposits): 462 | return epoch 463 | 464 | if epoch == self.START_EPOCH: 465 | break 466 | 467 | # no justified epochs found, use 0 as default 468 | # to 0 out the affect of casper on fork choice 469 | return 0 470 | 471 | 472 | @public 473 | @constant 474 | def highest_finalized_epoch(min_total_deposits: wei_value) -> int128: 475 | epoch: uint256 476 | for i in range(1000000000000000000000000000000): 477 | epoch = self.current_epoch - convert(i, "uint256") 478 | is_finalized: bool = self.checkpoints[epoch].is_finalized 479 | enough_cur_dyn_deposits: bool = self.checkpoints[epoch].cur_dyn_deposits >= min_total_deposits 480 | enough_prev_dyn_deposits: bool = self.checkpoints[epoch].prev_dyn_deposits >= min_total_deposits 481 | 482 | if is_finalized and (enough_cur_dyn_deposits and enough_prev_dyn_deposits): 483 | return convert(epoch, "int128") 484 | 485 | if epoch == self.START_EPOCH: 486 | break 487 | 488 | # no finalized epochs found, use -1 as default 489 | # to signal not to locally finalize anything 490 | return -1 491 | 492 | 493 | @private 494 | @constant 495 | def _votable(validator_index: uint256, 496 | target_hash: bytes32, 497 | target_epoch: uint256, 498 | source_epoch: uint256) -> bool: 499 | # Check that this vote has not yet been made 500 | already_voted: uint256 = bitwise_and( 501 | self.checkpoints[target_epoch].vote_bitmap[validator_index / 256], 502 | shift(convert(1, 'uint256'), convert(validator_index % 256, "int128")) 503 | ) 504 | if already_voted: 505 | return False 506 | # Check that the vote's target epoch and hash are correct 507 | if target_hash != self.recommended_target_hash(): 508 | return False 509 | if target_epoch != self.current_epoch: 510 | return False 511 | # Check that the vote source points to a justified epoch 512 | if not self.checkpoints[source_epoch].is_justified: 513 | return False 514 | 515 | # ensure validator can vote for the target_epoch 516 | in_current_dynasty: bool = self.in_dynasty(validator_index, self.dynasty) 517 | in_prev_dynasty: bool = self.in_dynasty(validator_index, self.dynasty - 1) 518 | return in_current_dynasty or in_prev_dynasty 519 | 520 | 521 | @public 522 | @constant 523 | def votable(vote_msg: bytes[1024]) -> bool: 524 | # Extract parameters 525 | values = RLPList(vote_msg, [uint256, bytes32, uint256, uint256, bytes]) 526 | validator_index: uint256 = values[0] 527 | target_hash: bytes32 = values[1] 528 | target_epoch: uint256 = values[2] 529 | source_epoch: uint256 = values[3] 530 | 531 | return self._votable(validator_index, target_hash, target_epoch, source_epoch) 532 | 533 | 534 | # ***** Public ***** 535 | 536 | # Called at the start of any epoch 537 | @public 538 | def initialize_epoch(epoch: uint256): 539 | # Check that the epoch actually has started 540 | computed_current_epoch: uint256 = block.number / self.EPOCH_LENGTH 541 | assert epoch <= computed_current_epoch and epoch == self.current_epoch + 1 542 | 543 | # must track the deposits related to the checkpoint _before_ updating current_epoch 544 | self.checkpoints[epoch].cur_dyn_deposits = self.total_curdyn_deposits_in_wei() 545 | self.checkpoints[epoch].prev_dyn_deposits = self.total_prevdyn_deposits_in_wei() 546 | 547 | self.current_epoch = epoch 548 | 549 | self.last_voter_rescale = 1.0 + self.collective_reward() 550 | self.last_nonvoter_rescale = self.last_voter_rescale / (1.0 + self.reward_factor) 551 | self.deposit_scale_factor[epoch] = self.deposit_scale_factor[epoch - 1] * self.last_nonvoter_rescale 552 | self.total_slashed[epoch] = self.total_slashed[epoch - 1] 553 | 554 | if self.deposit_exists(): 555 | # Set the reward factor for the next epoch. 556 | adj_interest_base: decimal = self.BASE_INTEREST_FACTOR / self.sqrt_of_total_deposits() 557 | self.reward_factor = adj_interest_base + self.BASE_PENALTY_FACTOR * convert(convert((self.esf() - 2), "int128"), "decimal") 558 | # ESF is only thing that is changing and reward_factor is being used above. 559 | assert self.reward_factor > 0.0 560 | else: 561 | # Before the first validator deposits, new epochs are finalized instantly. 562 | self.insta_finalize() 563 | self.reward_factor = 0 564 | 565 | # Store checkpoint hash for easy access 566 | self.checkpoint_hashes[epoch] = self.recommended_target_hash() 567 | 568 | # Increment the dynasty if finalized 569 | self.increment_dynasty() 570 | 571 | # Log new epoch creation 572 | log.Epoch(epoch, self.checkpoint_hashes[epoch], False, False) 573 | 574 | 575 | @public 576 | @payable 577 | def deposit(validation_addr: address, withdrawal_addr: address): 578 | assert extract32(raw_call(self.PURITY_CHECKER, concat('\xa1\x90\x3e\xab', convert(validation_addr, 'bytes32')), gas=500000, outsize=32), 0) != convert(0, 'bytes32') 579 | assert not self.validator_indexes[withdrawal_addr] 580 | assert msg.value >= self.MIN_DEPOSIT_SIZE 581 | validator_index: uint256 = self.next_validator_index 582 | start_dynasty: uint256 = self.dynasty + 2 583 | scaled_deposit: decimal(wei/sf) = self.ONE_WEI_DECIMAL * convert(convert(msg.value, "int128"), "decimal") / self.deposit_scale_factor[self.current_epoch] 584 | self.validators[validator_index] = { 585 | deposit: scaled_deposit, 586 | start_dynasty: start_dynasty, 587 | end_dynasty: self.DEFAULT_END_DYNASTY, 588 | is_slashed: False, 589 | total_deposits_at_logout: 0, 590 | addr: validation_addr, 591 | withdrawal_addr: withdrawal_addr 592 | } 593 | self.validator_indexes[withdrawal_addr] = validator_index 594 | self.next_validator_index += 1 595 | self.dynasty_wei_delta[start_dynasty] += scaled_deposit 596 | # Log deposit event 597 | log.Deposit( 598 | withdrawal_addr, 599 | validator_index, 600 | validation_addr, 601 | start_dynasty, 602 | msg.value 603 | ) 604 | 605 | 606 | @public 607 | def logout(logout_msg: bytes[1024]): 608 | assert self.current_epoch == block.number / self.EPOCH_LENGTH 609 | 610 | # Get hash for signature, and implicitly assert that it is an RLP list 611 | # consisting solely of RLP elements 612 | msg_hash: bytes32 = extract32( 613 | raw_call(self.MSG_HASHER, logout_msg, gas=self.MSG_HASHER_GAS_LIMIT, outsize=32), 614 | 0 615 | ) 616 | values = RLPList(logout_msg, [uint256, uint256, bytes]) 617 | validator_index: uint256 = values[0] 618 | epoch: uint256 = values[1] 619 | sig: bytes[1024] = values[2] 620 | 621 | assert self.current_epoch >= epoch 622 | from_withdrawal: bool = msg.sender == self.validators[validator_index].withdrawal_addr 623 | assert from_withdrawal or self.validate_signature(msg_hash, sig, validator_index) 624 | 625 | # Check that we haven't already withdrawn 626 | end_dynasty: uint256 = self.dynasty + self.DYNASTY_LOGOUT_DELAY 627 | assert self.validators[validator_index].end_dynasty > end_dynasty 628 | 629 | self.validators[validator_index].end_dynasty = end_dynasty 630 | self.validators[validator_index].total_deposits_at_logout = self.total_curdyn_deposits_in_wei() 631 | self.dynasty_wei_delta[end_dynasty] -= self.validators[validator_index].deposit 632 | 633 | log.Logout( 634 | self.validators[validator_index].withdrawal_addr, 635 | validator_index, 636 | self.validators[validator_index].end_dynasty 637 | ) 638 | 639 | 640 | # Withdraw deposited ether 641 | @public 642 | def withdraw(validator_index: uint256): 643 | # Check that we can withdraw 644 | end_dynasty: uint256 = self.validators[validator_index].end_dynasty 645 | assert self.dynasty > end_dynasty 646 | 647 | end_epoch: uint256 = self.dynasty_start_epoch[end_dynasty + 1] 648 | withdrawal_epoch: uint256 = end_epoch + self.WITHDRAWAL_DELAY 649 | assert self.current_epoch >= withdrawal_epoch 650 | 651 | # Withdraw 652 | withdraw_amount: wei_value 653 | if not self.validators[validator_index].is_slashed: 654 | withdraw_amount = self.ONE_WEI_UINT256 * convert( 655 | floor( 656 | self.validators[validator_index].deposit * self.deposit_scale_factor[end_epoch] 657 | ), 658 | "uint256" 659 | ) 660 | else: 661 | # prevent a negative lookup in total_slashed 662 | base_epoch: uint256 663 | if 2 * self.WITHDRAWAL_DELAY > withdrawal_epoch: 664 | base_epoch = 0 665 | else: 666 | base_epoch = withdrawal_epoch - 2 * self.WITHDRAWAL_DELAY 667 | 668 | recently_slashed: wei_value = self.total_slashed[withdrawal_epoch] - self.total_slashed[base_epoch] 669 | fraction_to_slash: decimal = convert(convert(recently_slashed * self.SLASH_FRACTION_MULTIPLIER, "int128"), "decimal") / \ 670 | convert(convert(self.validators[validator_index].total_deposits_at_logout, "int128"), "decimal") 671 | 672 | # can't withdraw a negative amount 673 | fraction_to_withdraw: decimal = max((1.0 - fraction_to_slash), 0.0) 674 | 675 | deposit_size: wei_value = self.ONE_WEI_UINT256 * convert( 676 | floor(self.validators[validator_index].deposit * self.deposit_scale_factor[withdrawal_epoch]), 677 | "uint256" 678 | ) 679 | withdraw_amount = self.ONE_WEI_UINT256 * convert( 680 | floor( 681 | convert(convert(deposit_size, "int128"), "decimal") * fraction_to_withdraw 682 | ), 683 | "uint256" 684 | ) 685 | 686 | send(self.validators[validator_index].withdrawal_addr, withdraw_amount) 687 | 688 | # Log withdraw event 689 | log.Withdraw( 690 | self.validators[validator_index].withdrawal_addr, 691 | validator_index, 692 | withdraw_amount 693 | ) 694 | 695 | self.delete_validator(validator_index) 696 | 697 | 698 | # Process a vote message 699 | @public 700 | def vote(vote_msg: bytes[1024]): 701 | assert msg.sender == self.NULL_SENDER 702 | 703 | # Extract parameters 704 | values = RLPList(vote_msg, [uint256, bytes32, uint256, uint256, bytes]) 705 | validator_index: uint256 = values[0] 706 | target_hash: bytes32 = values[1] 707 | target_epoch: uint256 = values[2] 708 | source_epoch: uint256 = values[3] 709 | sig: bytes[1024] = values[4] 710 | 711 | assert self._votable(validator_index, target_hash, target_epoch, source_epoch) 712 | assert self.validate_vote_signature(vote_msg) 713 | 714 | # Record that the validator voted for this target epoch so they can't again 715 | self.checkpoints[target_epoch].vote_bitmap[validator_index / 256] = \ 716 | bitwise_or(self.checkpoints[target_epoch].vote_bitmap[validator_index / 256], 717 | shift(convert(1, 'uint256'), convert(validator_index % 256, "int128"))) 718 | 719 | # Record that this vote took place 720 | in_current_dynasty: bool = self.in_dynasty(validator_index, self.dynasty) 721 | in_prev_dynasty: bool = self.in_dynasty(validator_index, self.dynasty - 1) 722 | current_dynasty_votes: decimal(wei/sf) = self.checkpoints[target_epoch].cur_dyn_votes[source_epoch] 723 | previous_dynasty_votes: decimal(wei/sf) = self.checkpoints[target_epoch].prev_dyn_votes[source_epoch] 724 | 725 | if in_current_dynasty: 726 | current_dynasty_votes += self.validators[validator_index].deposit 727 | self.checkpoints[target_epoch].cur_dyn_votes[source_epoch] = current_dynasty_votes 728 | if in_prev_dynasty: 729 | previous_dynasty_votes += self.validators[validator_index].deposit 730 | self.checkpoints[target_epoch].prev_dyn_votes[source_epoch] = previous_dynasty_votes 731 | 732 | # Process rewards. 733 | # Pay the reward if the vote was submitted in time and the vote is voting the correct data 734 | if self.expected_source_epoch == source_epoch: 735 | reward: decimal(wei/sf) = self.validators[validator_index].deposit * self.reward_factor 736 | self.proc_reward(validator_index, reward) 737 | 738 | # If enough votes with the same source_epoch and hash are made, 739 | # then the hash value is justified 740 | two_thirds_curdyn: bool = current_dynasty_votes >= self.total_curdyn_deposits * 2.0 / 3.0 741 | two_thirds_prevdyn: bool = previous_dynasty_votes >= self.total_prevdyn_deposits * 2.0 / 3.0 742 | enough_votes: bool = two_thirds_curdyn and two_thirds_prevdyn 743 | 744 | if enough_votes and not self.checkpoints[target_epoch].is_justified: 745 | self.checkpoints[target_epoch].is_justified = True 746 | self.last_justified_epoch = target_epoch 747 | self.main_hash_justified = True 748 | 749 | # Log target epoch status update 750 | log.Epoch(target_epoch, self.checkpoint_hashes[target_epoch], True, False) 751 | 752 | # If two epochs are justified consecutively, 753 | # then the source_epoch finalized 754 | if target_epoch == source_epoch + 1: 755 | self.checkpoints[source_epoch].is_finalized = True 756 | self.last_finalized_epoch = source_epoch 757 | # Log source epoch status update 758 | log.Epoch(source_epoch, self.checkpoint_hashes[source_epoch], True, True) 759 | 760 | # Log vote event 761 | log.Vote( 762 | self.validators[validator_index].withdrawal_addr, 763 | validator_index, 764 | target_hash, 765 | target_epoch, 766 | source_epoch 767 | ) 768 | 769 | 770 | # Cannot sign two votes for same target_epoch; no surround vote. 771 | @public 772 | def slash(vote_msg_1: bytes[1024], vote_msg_2: bytes[1024]): 773 | assert self.slashable(vote_msg_1, vote_msg_2) 774 | 775 | # Extract validator_index 776 | # `slashable` guarantees that validator_index is the same for each vote_msg 777 | # so just extract validator_index from vote_msg_1 778 | values = RLPList(vote_msg_1, [uint256, bytes32, uint256, uint256, bytes]) 779 | validator_index: uint256 = values[0] 780 | 781 | # Slash the offending validator, and give a 4% "finder's fee" 782 | validator_deposit: wei_value = self.deposit_size(validator_index) 783 | slashing_bounty: wei_value = validator_deposit / 25 784 | self.total_slashed[self.current_epoch] += validator_deposit 785 | self.validators[validator_index].is_slashed = True 786 | 787 | # Log slashing 788 | log.Slash( 789 | msg.sender, 790 | self.validators[validator_index].withdrawal_addr, 791 | validator_index, 792 | slashing_bounty, 793 | ) 794 | 795 | # if validator not logged out yet, remove total from next dynasty 796 | # and forcibly logout next dynasty 797 | end_dynasty: uint256 = self.validators[validator_index].end_dynasty 798 | if self.dynasty < end_dynasty: 799 | deposit: decimal(wei/sf) = self.validators[validator_index].deposit 800 | self.dynasty_wei_delta[self.dynasty + 1] -= deposit 801 | self.validators[validator_index].end_dynasty = self.dynasty + 1 802 | 803 | # if validator was already staged for logout at end_dynasty, 804 | # ensure that we don't doubly remove from total 805 | if end_dynasty < self.DEFAULT_END_DYNASTY: 806 | self.dynasty_wei_delta[end_dynasty] += deposit 807 | # if no previously logged out, remember the total deposits at logout 808 | else: 809 | self.validators[validator_index].total_deposits_at_logout = self.total_curdyn_deposits_in_wei() 810 | 811 | send(msg.sender, slashing_bounty) 812 | -------------------------------------------------------------------------------- /casper/contracts/sqrt.se.py: -------------------------------------------------------------------------------- 1 | with inp = ~calldataload(0): 2 | foo = inp 3 | exp = 0 4 | while foo >= 256: 5 | foo = ~div(foo, 256) 6 | exp += 1 7 | with x = ~div(inp, 16 ** exp): 8 | while 1: 9 | y = ~div(x + ~div(inp, x) + 1, 2) 10 | if x == y: 11 | return x 12 | x = y 13 | -------------------------------------------------------------------------------- /misc/rando.py: -------------------------------------------------------------------------------- 1 | from ethereum.tools import tester 2 | from ethereum.utils import sha3, normalize_address 3 | 4 | c = tester.Chain() 5 | x = c.contract(open('rando.v.py').read(), language='vyper') 6 | for i in range(10): 7 | x.deposit(sender=tester.keys[i], value=(i+1)*10**15) 8 | c.mine(1) 9 | 10 | o = [0] * 10 11 | for i in range(550): 12 | addr = normalize_address(x.random_select(sha3(str(i)))) 13 | o[tester.accounts.index(addr)] += 1 14 | 15 | for i, v in enumerate(o): 16 | ev = 10*(i+1) 17 | if not ev - 4*ev**0.5 < v < ev + 4*ev**0.5: 18 | raise Exception("More than four standard deviations away; something is wrong: %.2f %d %.2f" % 19 | (ev - 4*ev**0.5, v, ev + 4*ev**0.5)) 20 | print(o) 21 | -------------------------------------------------------------------------------- /misc/rando.v.py: -------------------------------------------------------------------------------- 1 | # The purpose of this contract is to showcase how it actually 2 | # is not all that complex to implement a scheme where anyone 3 | # can deposit an arbitrary amount and validators can still be 4 | # fairly pseudorandomly selected. It works by storing a binary 5 | # tree where at each leaf of the tree it stores the total 6 | # amount of ETH held by validators that are under that leaf. 7 | # Random selection can then be done by climbing down the tree 8 | # jumping left or right with probability weighted the value 9 | # stored at each branch. Complexity of all operations (insert, 10 | # delete, update, edit balance, read) is O(log(n)) though only 11 | # insert is implemented so far. Implementing delete, update and 12 | # a queue so that exited validators' slots can be reused should 13 | # be fairly simple. 14 | 15 | validator_table: public({bal: wei_value, addr: address}[65536]) 16 | next_validator_index: num 17 | total_deposits: public(wei_value) 18 | 19 | @payable 20 | @public 21 | def deposit(): 22 | assert self.next_validator_index < 32768 23 | ind:num = 32768 + self.next_validator_index 24 | self.validator_table[ind].addr = msg.sender 25 | for i in range(15): 26 | self.validator_table[ind].bal += msg.value 27 | ind = ind / 2 28 | self.total_deposits += msg.value 29 | self.next_validator_index += 1 30 | 31 | @public 32 | @constant 33 | def random_select(seed: bytes32) -> address: 34 | select_val:wei_value = as_wei_value(as_num128(num256_mod(as_num256(seed), as_num256(self.total_deposits))), wei) 35 | ind:num = 1 36 | for i in range(15): 37 | if select_val <= self.validator_table[ind * 2].bal: 38 | ind = ind * 2 39 | else: 40 | select_val -= self.validator_table[ind * 2].bal 41 | ind = ind * 2 + 1 42 | return self.validator_table[ind].addr 43 | -------------------------------------------------------------------------------- /misc/validation_codes/fixed_address_creator.py: -------------------------------------------------------------------------------- 1 | 2 | import serpent 3 | import rlp 4 | from ethereum import utils 5 | from ethereum import tester 6 | from ethereum import transactions 7 | 8 | msg_hash = serpent.compile('msg_hash.se.py') 9 | 10 | tests = [ 11 | [b"\x01"], 12 | [b"\x80", "a"], 13 | [b"\x81", "b"], 14 | [b""], 15 | [b"", b"\x01", b""], 16 | [b"", b"\x81", b""], 17 | [b"dog", b"c" * 54, b"\x01"], 18 | [b"\x01", b"c" * 55, b"pig"], 19 | [b"moose", b"c" * 56, b"\x00"], 20 | [b'\x01', b'55555555555555555555555555555555', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x88\xa7\x85r\x1b3\x17\xcaP\x96\xca\xd3S\xfcgM\xec\xe0\xf5!\xc8\xb4m\xd9\xb7E\xf3\x81d\x87\x93VD\xe0Ej\xcd\xec\x80\x11\x86(qZ\x9b\x80\xbf\xce\xe5*\r\x9d.o\xcd\x11s\xc5\xbc\x8c\xcb\xb9\xa9 '] 21 | ] 22 | 23 | s = tester.state() 24 | c = s.evm(msg_hash, sender=tester.k0, endowment=0) 25 | 26 | for test in tests: 27 | z = s.send(tester.k0, c, 0, rlp.encode(test)) 28 | assert z == utils.sha3(rlp.encode(test[:-1])) 29 | print("Passed test, gas consumed: ", s.state.receipts[-1].gas_used - s.state.receipts[-2].gas_used - s.last_tx.intrinsic_gas_used) 30 | 31 | # Create transaction 32 | t = transactions.Transaction(0, 30 * 10**9, 2999999, '', 0, msg_hash) 33 | t.startgas = t.intrinsic_gas_used + 50000 + 200 * len(msg_hash) 34 | t.v = 27 35 | t.r = 45 36 | t.s = 79 37 | print("Message Hash") 38 | print('Send %d wei to %s' % (t.startgas * t.gasprice, 39 | '0x'+utils.encode_hex(t.sender))) 40 | 41 | print('Contract address: 0x'+utils.encode_hex(utils.mk_contract_address(t.sender, 0))) 42 | print('Code: 0x'+utils.encode_hex(rlp.encode(t))) 43 | -------------------------------------------------------------------------------- /misc/validation_codes/hash_ladder_signer.py: -------------------------------------------------------------------------------- 1 | # Implements a version of https://gist.github.com/alexwebr/da8dd928002a236c4709 2 | 3 | try: 4 | from Crypto.Hash import keccak 5 | sha3 = lambda x: keccak.new(digest_bits=256, data=x).digest() 6 | except ImportError: 7 | import sha3 as _sha3 8 | sha3 = lambda x: _sha3.sha3_256(x).digest() 9 | 10 | NUM_SUBKEYS = 32 11 | DEPTH = 32 12 | 13 | def iterate_hash(msg, n): 14 | for i in range(n): 15 | msg = sha3(msg) 16 | return msg 17 | 18 | class LamportSigner(): 19 | def __init__(self, key, depth): 20 | self.indexcount = 2**depth 21 | self.priv = key 22 | self.keys = [] 23 | self.pubs = [] 24 | for i in range(self.indexcount): 25 | subkeys = [sha3(key + bytes([i // 256, i % 256, j])) for j in range(NUM_SUBKEYS + 1)] 26 | self.keys.append(subkeys) 27 | pubs = [iterate_hash(k, DEPTH) for k in subkeys[:-1]] + [iterate_hash(subkeys[-1], DEPTH * NUM_SUBKEYS)] 28 | self.pubs.append(b''.join(pubs)) 29 | if i % 256 == 255: 30 | print("Finished %d out of %d privkeys" % ((i + 1), self.indexcount)) 31 | self.merkle_nodes = [0] * self.indexcount + [sha3(x) for x in self.pubs] 32 | for j in range(self.indexcount - 1, 0, -1): 33 | self.merkle_nodes[j] = sha3(self.merkle_nodes[j * 2] + self.merkle_nodes[j * 2 + 1]) 34 | if j % 256 == 0: 35 | print("Building Merkle tree, %d values remaining" % j) 36 | self.pub = self.merkle_nodes[1] 37 | 38 | def merkle_prove_pubkey(self, index): 39 | adjusted_index = self.indexcount + index 40 | o = [] 41 | while adjusted_index > 1: 42 | o.append(self.merkle_nodes[adjusted_index ^ 1]) 43 | adjusted_index >>= 1 44 | return o 45 | 46 | def sign(self, msghash, index): 47 | assert isinstance(msghash, bytes) 48 | subkeys, balance_key = self.keys[index][:-1], self.keys[index][-1] 49 | depths = [msghash[i] % DEPTH for i in range(NUM_SUBKEYS)] 50 | values = [iterate_hash(subkey, depth) for subkey, depth in zip(subkeys, depths)] + \ 51 | [iterate_hash(balance_key, DEPTH * NUM_SUBKEYS - sum(depths))] 52 | return b''.join(values) + b''.join(self.merkle_prove_pubkey(index)) + bytes([index // 256, index % 256]) 53 | -------------------------------------------------------------------------------- /misc/validation_codes/hash_ladder_tester.py: -------------------------------------------------------------------------------- 1 | import serpent 2 | from ethereum import tester as t 3 | import hash_ladder_signer as h 4 | import binascii 5 | 6 | s = t.state() 7 | 8 | PROOFLEN = 7 9 | 10 | signer = h.LamportSigner(b'\x54' * 32, PROOFLEN) 11 | 12 | verifier_code = open('verify_hash_ladder_sig.se').read() \ 13 | .replace('41fd19e4450fd5fa8499231552a2e967e95a6e5a8e6bb5de5523b9cbdfc559e7', signer.pub.hex()) 14 | 15 | verifier = s.contract(verifier_code) 16 | 17 | msg = h.sha3(b'doge') 18 | print(s.send(t.k0, verifier, 0, msg + signer.sign(msg, 9))) 19 | print('Verification successful') 20 | print('Gas used: %d' % (s.state.receipts[-1].gas_used - s.state.receipts[-2].gas_used - s.last_tx.intrinsic_gas_used)) 21 | -------------------------------------------------------------------------------- /misc/validation_codes/verify_hash_ladder_sig.se: -------------------------------------------------------------------------------- 1 | macro PUB: 0x41fd19e4450fd5fa8499231552a2e967e95a6e5a8e6bb5de5523b9cbdfc559e7 2 | macro NUM_SUBKEYS: 32 3 | macro DEPTH: 32 4 | macro PROOFLEN: 7 5 | 6 | # Copy signature into memory 7 | ~calldatacopy(0, 32, 32 * NUM_SUBKEYS + 32) 8 | 9 | # Climb down the hash ladder 10 | with totdepth = 0: 11 | with i = 0: 12 | while i < NUM_SUBKEYS * 32: 13 | ~mstore(32 * NUM_SUBKEYS + 96, ~calldataload(0)) 14 | with j = ~mod(~mload(32 * NUM_SUBKEYS + 65 + ~div(i, 32)), DEPTH) : 15 | totdepth += j 16 | while j < DEPTH: 17 | ~mstore(i, ~sha3(i, 32)) 18 | j += 1 19 | i += 32 20 | while totdepth > 0: 21 | ~mstore(NUM_SUBKEYS * 32, ~sha3(NUM_SUBKEYS * 32, 32)) 22 | totdepth -= 1 23 | # ~return(0, 32 * NUM_SUBKEYS + 32) 24 | 25 | # ~return(0, 32 * NUM_SUBKEYS + 32) 26 | # Pubkey hash 27 | with h = ~sha3(0, 32 * NUM_SUBKEYS + 32): 28 | # Verify the Merkle proof 29 | with index = ~mod(~calldataload(~calldatasize() - 32), 65536): 30 | with i = 0: 31 | while i < PROOFLEN: 32 | if index % 2: 33 | ~mstore(0, ~calldataload(64 + 32 * NUM_SUBKEYS + 32 * i)) 34 | ~mstore(32, h) 35 | else: 36 | ~mstore(32, ~calldataload(64 + 32 * NUM_SUBKEYS + 32 * i)) 37 | ~mstore(0, h) 38 | h = ~sha3(0, 64) 39 | i += 1 40 | index = ~div(index, 2) 41 | assert h == PUB 42 | return(1) 43 | -------------------------------------------------------------------------------- /old-README.md: -------------------------------------------------------------------------------- 1 | # Casper 2 | 3 | [![](https://img.shields.io/badge/made%20by-Ethereum%20Foundation-blue.svg?style=flat-square)](http://ethereum.org) 4 | [![Build Status](https://travis-ci.org/ethereum/casper.svg?branch=master)](https://travis-ci.org/ethereum/casper) 5 | [![Casper](https://img.shields.io/badge/gitter-Casper-4AB495.svg)](https://gitter.im/ethereum/casper) 6 | [![Casper scaling and protocol economics](https://img.shields.io/badge/gitter-Casper%20scaling%20and%20protocol%20economics-4AB495.svg)](https://gitter.im/ethereum/casper-scaling-and-protocol-economics) 7 | [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 8 | 9 | > Implements Casper FFG (the Friendly Finality Gadget), a Proof-of-Stake finality protocol that can be layered on any block proposal mechanism. 10 | 11 | ## Background 12 | 13 | - Implements a [Casper FFG](https://arxiv.org/abs/1710.09437) smart contract, written in [Vyper](https://github.com/ethereum/vyper). 14 | - See this [Casper the Friendly Finality Gadget](https://arxiv.org/abs/1710.09437) paper by Vitalik Buterin and Virgil Griffith introducing Casper FFG. 15 | - [EIP-1011](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1011.md): 16 | specification of the Casper the Friendly Finality Gadget (FFG) PoW/PoS consensus model. 17 | 18 | ## Installation 19 | 20 | For macOS, with [brew](https://brew.sh/) installed: 21 | 22 | ```bash 23 | brew install pandoc # required for a python dependency 24 | brew install leveldb 25 | ``` 26 | 27 | For all systems: 28 | 29 | ```bash 30 | pip3 install -r requirements.txt 31 | ``` 32 | 33 | NOTE: pip3 is a version of pip using python version 3. 34 | NOTE: we suggest using virtualenv to sandbox your setup. 35 | 36 | ## Usage 37 | 38 | - [VALIDATOR_GUIDE.md](https://github.com/ethereum/casper/blob/master/VALIDATOR_GUIDE.md): 39 | information about implementing a Casper FFG validator. 40 | 41 | ## Contribute 42 | 43 | Feel free to ask questions in our [Gitter room](https://gitter.im/ethereum/casper) or open an [issue](https://github.com/ethereum/casper/issues) for feature requests or bug reports. Feel free to make a PR! 44 | 45 | ## License 46 | 47 | [UNLICENSE](LICENSE) 48 | 49 | ## Tests 50 | 51 | ```bash 52 | pytest tests 53 | ``` 54 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import rlp 4 | 5 | from decimal import ( 6 | Decimal, 7 | ) 8 | 9 | import eth_tester 10 | from eth_tester import ( 11 | EthereumTester, 12 | PyEVMBackend 13 | ) 14 | from web3.providers.eth_tester import ( 15 | EthereumTesterProvider, 16 | ) 17 | from web3 import ( 18 | Web3, 19 | ) 20 | from web3.contract import ( 21 | ConciseContract, 22 | ) 23 | from vyper import ( 24 | compiler, 25 | utils as vyper_utils, 26 | ) 27 | 28 | from utils.utils import encode_int32 29 | from utils.valcodes import compile_valcode_to_evm_bytecode 30 | 31 | OWN_DIR = os.path.dirname(os.path.realpath(__file__)) 32 | 33 | GAS_PRICE = 25 * 10**9 34 | 35 | NULL_SENDER = '0xffffffffffffffffffffffffffffffffffffffff' 36 | CASPER_ADDRESS = "0x0000000000000000000000000000000000000042" 37 | 38 | VYPER_RLP_DECODER_TX_HEX = "0xf9035b808506fc23ac0083045f788080b903486103305660006109ac5260006109cc527f0100000000000000000000000000000000000000000000000000000000000000600035046109ec526000610a0c5260006109005260c06109ec51101515585760f86109ec51101561006e5760bf6109ec510336141558576001610a0c52610098565b60013560f76109ec51036020035260005160f66109ec510301361415585760f66109ec5103610a0c525b61022060016064818352015b36610a0c511015156100b557610291565b7f0100000000000000000000000000000000000000000000000000000000000000610a0c5135046109ec526109cc5160206109ac51026040015260016109ac51016109ac5260806109ec51101561013b5760016109cc5161044001526001610a0c516109cc5161046001376001610a0c5101610a0c5260216109cc51016109cc52610281565b60b86109ec5110156101d15760806109ec51036109cc51610440015260806109ec51036001610a0c51016109cc51610460013760816109ec5114156101ac5760807f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350410151558575b607f6109ec5103610a0c5101610a0c5260606109ec51036109cc51016109cc52610280565b60c06109ec51101561027d576001610a0c51013560b76109ec510360200352600051610a2c526038610a2c5110157f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350402155857610a2c516109cc516104400152610a2c5160b66109ec5103610a0c51016109cc516104600137610a2c5160b66109ec5103610a0c510101610a0c526020610a2c51016109cc51016109cc5261027f565bfe5b5b5b81516001018083528114156100a4575b5050601f6109ac511115155857602060206109ac5102016109005260206109005103610a0c5261022060016064818352015b6000610a0c5112156102d45761030a565b61090051610a0c516040015101610a0c51610900516104400301526020610a0c5103610a0c5281516001018083528114156102c3575b50506109cc516109005101610420526109cc5161090051016109005161044003f35b61000461033003610004600039610004610330036000f31b2d4f" # NOQA 39 | VYPER_RLP_DECODER_TX_SENDER = "0x39ba083c30fCe59883775Fc729bBE1f9dE4DEe11" 40 | MSG_HASHER_TX_HEX = "0xf9016d808506fc23ac0083026a508080b9015a6101488061000e6000396101565660007f01000000000000000000000000000000000000000000000000000000000000006000350460f8811215610038576001915061003f565b60f6810391505b508060005b368312156100c8577f01000000000000000000000000000000000000000000000000000000000000008335048391506080811215610087576001840193506100c2565b60b881121561009d57607f8103840193506100c1565b60c08112156100c05760b68103600185013560b783036020035260005101840193505b5b5b50610044565b81810360388112156100f4578060c00160005380836001378060010160002060e052602060e0f3610143565b61010081121561010557600161011b565b6201000081121561011757600261011a565b60035b5b8160005280601f038160f701815382856020378282600101018120610140526020610140f350505b505050505b6000f31b2d4f" # NOQA 41 | MSG_HASHER_TX_SENDER = "0xD7a3BD6C9eA32efF147d067f907AE6b22d436F91" 42 | PURITY_CHECKER_TX_HEX = "0xf90403808506fc23ac0083061a808080b903f06103d856600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263a1903eab600051141561038657602060046101403734156100b457600080fd5b60043560205181106100c557600080fd5b50610140513b60008114156100da57fe610367565b806000610160610140513c806041026101600160008160007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff818352015b84845110151561012b5761036256610351565b610100601f8551036101600151068060020a7f80010000000000000000000000000000000000000430ffff1c0e000000000000161561016657fe5b607f811115816060111516156101a55780607f036101000a600186516101600101510484602002876021026101600101528451605f820301855261033d565b60f4811460f282141760f18214171561033c576000607f6001860360200288610160010151111560606001870360200289610160010151101516600286101516156101f5576002850390506102e2565b607f6003860360200288610160010151111560606003870360200289610160010151101516605a6002870360200289610160010151141660036001870360200289610160010151141660048610151615610254576004850390506102e1565b605a6001860360200288610160010151146002861015161561027b576002850390506102e0565b6090600186036020028861016001015114600286101516156102a2576002850390506102df565b609060018603602002886101600101511060806001870360200289610160010151101516600286101516156102dc576002850390506102de565bfe5b5b5b5b5b8060200287602102610160010151600060c052602060c0200154156103065761033a565b60308160200288610160010151141561031e57610339565b60608160200288610160010151141561033657610338565bfe5b5b5b505b5b808460200287610160010152600184019350505b5b8151600101808352811415610118575b505050505b50600161014051600060c052602060c0200155600160005260206000f3005b63c23697a860005114156103d357602060046101403734156103a757600080fd5b60043560205181106103b857600080fd5b5061014051600060c052602060c020015460005260206000f3005b5b6100046103d8036100046000396100046103d8036000f31b2d4f" # NOQA 43 | PURITY_CHECKER_TX_SENDER = "0xA0DceC204FB3e25E16d86800c39658C5C8DD2764" 44 | PURITY_CHECKER_ABI = [{'name': 'check', 'type': 'function', 'constant': True, 'inputs': [{'name': 'addr', 'type': 'address'}], 'outputs': [{'name': 'out', 'type': 'bool'}]}, {'name': 'submit', 'type': 'function', 'constant': False, 'inputs': [{'name': 'addr', 'type': 'address'}], 'outputs': [{'name': 'out', 'type': 'bool'}]}] # NOQA 45 | 46 | EPOCH_LENGTH = 10 47 | WARM_UP_PERIOD = 20 48 | DYNASTY_LOGOUT_DELAY = 5 49 | WITHDRAWAL_DELAY = 8 50 | BASE_INTEREST_FACTOR = Decimal('0.02') 51 | BASE_PENALTY_FACTOR = Decimal('0.002') 52 | MIN_DEPOSIT_SIZE = 1000 * 10**18 # 1000 ether 53 | 54 | DEPOSIT_AMOUNTS = [ 55 | 2000 * 10**18, 56 | # 1000 * 10**18, 57 | ] 58 | 59 | 60 | setattr(eth_tester.backends.pyevm.main, 'GENESIS_GAS_LIMIT', 10**9) 61 | setattr(eth_tester.backends.pyevm.main, 'GENESIS_DIFFICULTY', 1) 62 | 63 | 64 | @pytest.fixture 65 | def next_contract_address(w3, base_tester, fake_contract_code): 66 | def next_contract_address(sender): 67 | snapshot_id = base_tester.take_snapshot() 68 | bytecode = compiler.compile(fake_contract_code) 69 | hex_bytecode = Web3.toHex(bytecode) 70 | tx_hash = w3.eth.sendTransaction({ 71 | 'from': sender, 72 | 'to': '', 73 | 'gas': 7000000, 74 | 'data': hex_bytecode 75 | }) 76 | contract_address = w3.eth.getTransactionReceipt(tx_hash).contractAddress 77 | 78 | base_tester.revert_to_snapshot(snapshot_id) 79 | return contract_address 80 | return next_contract_address 81 | 82 | 83 | @pytest.fixture 84 | def fake_hash(): 85 | return b'\xbc' * 32 86 | 87 | 88 | @pytest.fixture 89 | def fake_contract_code(): 90 | return ''' 91 | @public 92 | def five() -> int128: 93 | return 5 94 | ''' 95 | 96 | 97 | @pytest.fixture 98 | def base_sender(base_tester): 99 | return base_tester.get_accounts()[-1] 100 | 101 | 102 | @pytest.fixture 103 | def funded_accounts(base_tester): 104 | return base_tester.get_accounts()[0:5] 105 | 106 | 107 | @pytest.fixture 108 | def funded_account(funded_accounts): 109 | return funded_accounts[0] 110 | 111 | 112 | @pytest.fixture 113 | def validation_keys(w3, funded_accounts): 114 | # use address as the keymash to gen new private keys 115 | # insecure but fine for our purposes 116 | return [w3.eth.account.create(str(address)).privateKey for address in funded_accounts] 117 | 118 | 119 | @pytest.fixture 120 | def validation_key(validation_keys): 121 | return validation_keys[0] 122 | 123 | 124 | @pytest.fixture 125 | def validation_addrs(w3, validation_keys): 126 | return [w3.eth.account.privateKeyToAccount(key).address for key in validation_keys] 127 | 128 | 129 | @pytest.fixture(params=DEPOSIT_AMOUNTS) 130 | def deposit_amount(request): 131 | return request.param 132 | 133 | 134 | @pytest.fixture 135 | def vyper_rlp_decoder_address(): 136 | tmp_tester = EthereumTester(PyEVMBackend()) 137 | tmp_w3 = w3(tmp_tester) 138 | address = deploy_rlp_decoder(tmp_w3)() 139 | return address 140 | 141 | 142 | @pytest.fixture 143 | def msg_hasher_address(): 144 | tmp_tester = EthereumTester(PyEVMBackend()) 145 | tmp_w3 = w3(tmp_tester) 146 | address = deploy_msg_hasher(tmp_w3)() 147 | return address 148 | 149 | 150 | @pytest.fixture 151 | def purity_checker_address(): 152 | tmp_tester = EthereumTester(PyEVMBackend()) 153 | tmp_w3 = w3(tmp_tester) 154 | address = deploy_purity_checker(tmp_w3)() 155 | return address 156 | 157 | 158 | @pytest.fixture 159 | def purity_checker(w3, purity_checker_address): 160 | purity_checker = w3.eth.contract( 161 | address=purity_checker_address, 162 | abi=PURITY_CHECKER_ABI 163 | ) 164 | return purity_checker 165 | 166 | 167 | @pytest.fixture 168 | def null_sender(base_tester): 169 | return base_tester.get_accounts()[-2] 170 | 171 | 172 | @pytest.fixture 173 | def epoch_length(): 174 | return EPOCH_LENGTH 175 | 176 | 177 | @pytest.fixture 178 | def warm_up_period(): 179 | return WARM_UP_PERIOD 180 | 181 | 182 | @pytest.fixture 183 | def withdrawal_delay(): 184 | return WITHDRAWAL_DELAY 185 | 186 | 187 | @pytest.fixture 188 | def dynasty_logout_delay(): 189 | return DYNASTY_LOGOUT_DELAY 190 | 191 | 192 | @pytest.fixture 193 | def base_interest_factor(): 194 | return BASE_INTEREST_FACTOR 195 | 196 | 197 | @pytest.fixture 198 | def base_penalty_factor(): 199 | return BASE_PENALTY_FACTOR 200 | 201 | 202 | @pytest.fixture 203 | def min_deposit_size(): 204 | return MIN_DEPOSIT_SIZE 205 | 206 | 207 | @pytest.fixture 208 | def casper_config(epoch_length, 209 | warm_up_period, 210 | withdrawal_delay, 211 | dynasty_logout_delay, 212 | base_interest_factor, 213 | base_penalty_factor, 214 | min_deposit_size): 215 | return { 216 | "epoch_length": epoch_length, # in blocks 217 | "warm_up_period": warm_up_period, # in blocks 218 | "withdrawal_delay": withdrawal_delay, # in epochs 219 | "dynasty_logout_delay": dynasty_logout_delay, # in dynasties 220 | "base_interest_factor": base_interest_factor, 221 | "base_penalty_factor": base_penalty_factor, 222 | "min_deposit_size": min_deposit_size 223 | } 224 | 225 | 226 | @pytest.fixture 227 | def casper_args(casper_config, 228 | msg_hasher_address, 229 | purity_checker_address, 230 | null_sender): 231 | return [ 232 | casper_config["epoch_length"], 233 | casper_config["warm_up_period"], 234 | casper_config["withdrawal_delay"], 235 | casper_config["dynasty_logout_delay"], 236 | msg_hasher_address, 237 | purity_checker_address, 238 | null_sender, 239 | casper_config["base_interest_factor"], 240 | casper_config["base_penalty_factor"], 241 | casper_config["min_deposit_size"] 242 | ] 243 | 244 | 245 | @pytest.fixture 246 | def base_tester(): 247 | return EthereumTester(PyEVMBackend()) 248 | 249 | 250 | def zero_gas_price_strategy(web3, transaction_params=None): 251 | return 0 # zero gas price makes testing simpler. 252 | 253 | 254 | @pytest.fixture 255 | def w3(base_tester): 256 | web3 = Web3(EthereumTesterProvider(base_tester)) 257 | web3.eth.setGasPriceStrategy(zero_gas_price_strategy) 258 | return web3 259 | 260 | 261 | @pytest.fixture 262 | def tester(w3, 263 | base_tester, 264 | casper_args, 265 | casper_code, 266 | casper_abi, 267 | casper_address, 268 | deploy_rlp_decoder, 269 | deploy_msg_hasher, 270 | deploy_purity_checker, 271 | base_sender, 272 | initialize_contract=True): 273 | deploy_rlp_decoder() 274 | deploy_msg_hasher() 275 | deploy_purity_checker() 276 | 277 | # NOTE: bytecode cannot be compiled before RLP Decoder is deployed to chain 278 | # otherwise, vyper compiler cannot properly embed RLP decoder address 279 | casper_bytecode = compiler.compile(casper_code, bytecode_runtime=True) 280 | 281 | chain = base_tester.backend.chain 282 | vm = chain.get_vm() 283 | 284 | vm.state.account_db.set_code(Web3.toBytes(hexstr=casper_address), casper_bytecode) 285 | vm.state.account_db.persist() 286 | new_state_root = vm.state.account_db.state_root 287 | 288 | new_header = chain.header.copy(state_root=new_state_root) 289 | chain.header = new_header 290 | 291 | # mine block to ensure we don't have mismatched state 292 | base_tester.mine_block() 293 | 294 | # Casper contract needs money for its activity 295 | w3.eth.sendTransaction({ 296 | 'to': casper_address, 297 | 'value': 10**21 298 | }) 299 | 300 | if initialize_contract: 301 | casper_contract = casper(w3, base_tester, casper_abi, casper_address) 302 | casper_contract.functions.init(*casper_args).transact() 303 | 304 | return base_tester 305 | 306 | 307 | @pytest.fixture 308 | def deploy_rlp_decoder(w3): 309 | def deploy_rlp_decoder(): 310 | w3.eth.sendTransaction({ 311 | 'to': VYPER_RLP_DECODER_TX_SENDER, 312 | 'value': 10**17 313 | }) 314 | tx_hash = w3.eth.sendRawTransaction(VYPER_RLP_DECODER_TX_HEX) 315 | 316 | receipt = w3.eth.getTransactionReceipt(tx_hash) 317 | contract_address = receipt.contractAddress 318 | assert vyper_utils.RLP_DECODER_ADDRESS == w3.toInt(hexstr=contract_address) 319 | return contract_address 320 | return deploy_rlp_decoder 321 | 322 | 323 | @pytest.fixture 324 | def deploy_msg_hasher(w3): 325 | def deploy_msg_hasher(): 326 | w3.eth.sendTransaction({ 327 | 'to': MSG_HASHER_TX_SENDER, 328 | 'value': 10**17 329 | }) 330 | tx_hash = w3.eth.sendRawTransaction(MSG_HASHER_TX_HEX) 331 | 332 | receipt = w3.eth.getTransactionReceipt(tx_hash) 333 | return receipt.contractAddress 334 | return deploy_msg_hasher 335 | 336 | 337 | @pytest.fixture 338 | def deploy_purity_checker(w3): 339 | def deploy_purity_checker(): 340 | w3.eth.sendTransaction({ 341 | 'to': PURITY_CHECKER_TX_SENDER, 342 | 'value': 10**17 343 | }) 344 | tx_hash = w3.eth.sendRawTransaction(PURITY_CHECKER_TX_HEX) 345 | 346 | receipt = w3.eth.getTransactionReceipt(tx_hash) 347 | return receipt.contractAddress 348 | return deploy_purity_checker 349 | 350 | 351 | @pytest.fixture 352 | def casper_code(): 353 | with open(get_dirs('simple_casper.v.py')[0]) as f: 354 | return f.read() 355 | 356 | 357 | @pytest.fixture 358 | def casper_abi(casper_code): 359 | return compiler.mk_full_signature(casper_code) 360 | 361 | 362 | @pytest.fixture 363 | def casper_address(): 364 | return CASPER_ADDRESS 365 | 366 | 367 | @pytest.fixture 368 | def casper_deposit_filter(casper): 369 | return casper.events.Deposit.createFilter(fromBlock='latest') 370 | 371 | 372 | @pytest.fixture 373 | def casper_vote_filter(casper): 374 | return casper.events.Vote.createFilter(fromBlock='latest') 375 | 376 | 377 | @pytest.fixture 378 | def casper_logout_filter(casper): 379 | return casper.events.Logout.createFilter(fromBlock='latest') 380 | 381 | 382 | @pytest.fixture 383 | def casper_withdraw_filter(casper): 384 | return casper.events.Withdraw.createFilter(fromBlock='latest') 385 | 386 | 387 | @pytest.fixture 388 | def casper_slash_filter(casper): 389 | return casper.events.Slash.createFilter(fromBlock='latest') 390 | 391 | 392 | @pytest.fixture 393 | def casper_epoch_filter(casper): 394 | return casper.events.Epoch.createFilter(fromBlock='latest') 395 | 396 | 397 | @pytest.fixture 398 | def casper(w3, tester, casper_abi, casper_address): 399 | casper = w3.eth.contract( 400 | address=casper_address, 401 | abi=casper_abi 402 | ) 403 | return casper 404 | 405 | 406 | @pytest.fixture 407 | def concise_casper(casper): 408 | return ConciseContract(casper) 409 | 410 | 411 | @pytest.fixture 412 | def deploy_casper_contract( 413 | w3, 414 | base_tester, 415 | casper_code, 416 | casper_abi, 417 | casper_address, 418 | deploy_rlp_decoder, 419 | deploy_msg_hasher, 420 | deploy_purity_checker, 421 | base_sender): 422 | def deploy_casper_contract(contract_args, initialize_contract=True): 423 | t = tester( 424 | w3, base_tester, contract_args, casper_code, casper_abi, casper_address, 425 | deploy_rlp_decoder, deploy_msg_hasher, deploy_purity_checker, 426 | base_sender, initialize_contract 427 | ) 428 | return casper(w3, t, casper_abi, casper_address) 429 | return deploy_casper_contract 430 | 431 | 432 | def get_dirs(path): 433 | abs_contract_path = os.path.realpath(os.path.join(OWN_DIR, '..', 'casper', 'contracts')) 434 | sub_dirs = [x[0] for x in os.walk(abs_contract_path)] 435 | extra_args = ' '.join(['{}={}'.format(d.split('/')[-1], d) for d in sub_dirs]) 436 | path = '{}/{}'.format(abs_contract_path, path) 437 | return path, extra_args 438 | 439 | 440 | # Note: If called during "warm_up-period", new_epoch mines all the way through 441 | # the warm up period until `initialize_epoch` can first be called 442 | @pytest.fixture 443 | def new_epoch(tester, casper, epoch_length): 444 | def new_epoch(): 445 | block_number = tester.get_block_by_number('latest')['number'] 446 | current_epoch = casper.functions.current_epoch().call() 447 | next_epoch = current_epoch + 1 448 | 449 | tester.mine_blocks(epoch_length * next_epoch - block_number) 450 | 451 | casper.functions.initialize_epoch(next_epoch).transact() 452 | 453 | return new_epoch 454 | 455 | 456 | @pytest.fixture 457 | def mk_validation_code(): 458 | def mk_validation_code(address, valcode_type): 459 | return compile_valcode_to_evm_bytecode(valcode_type, address) 460 | return mk_validation_code 461 | 462 | 463 | @pytest.fixture 464 | def send_vote(casper, null_sender): 465 | def send_vote(vote_msg, sender=None): 466 | if sender is None: 467 | sender = null_sender 468 | 469 | casper.functions.vote( 470 | vote_msg 471 | ).transact({ 472 | 'from': sender 473 | }) 474 | return send_vote 475 | 476 | 477 | @pytest.fixture 478 | def mk_vote(w3): 479 | def mk_vote(validator_index, target_hash, target_epoch, source_epoch, validation_key): 480 | msg_hash = w3.sha3( 481 | rlp.encode([validator_index, target_hash, target_epoch, source_epoch]) 482 | ) 483 | signed = w3.eth.account.signHash(msg_hash, validation_key) 484 | sig = encode_int32(signed.v) + encode_int32(signed.r) + encode_int32(signed.s) 485 | return rlp.encode([validator_index, target_hash, target_epoch, source_epoch, sig]) 486 | return mk_vote 487 | 488 | 489 | @pytest.fixture 490 | def mk_suggested_vote(concise_casper, mk_vote): 491 | def mk_suggested_vote(validator_index, validation_key): 492 | target_hash = concise_casper.recommended_target_hash() 493 | target_epoch = concise_casper.current_epoch() 494 | source_epoch = concise_casper.recommended_source_epoch() 495 | return mk_vote(validator_index, target_hash, target_epoch, source_epoch, validation_key) 496 | return mk_suggested_vote 497 | 498 | 499 | @pytest.fixture 500 | def mk_slash_votes(concise_casper, mk_vote, fake_hash): 501 | def mk_slash_votes(validator_index, validation_key): 502 | vote_1 = mk_vote( 503 | validator_index, 504 | concise_casper.recommended_target_hash(), 505 | concise_casper.current_epoch(), 506 | concise_casper.recommended_source_epoch(), 507 | validation_key 508 | ) 509 | vote_2 = mk_vote( 510 | validator_index, 511 | fake_hash, 512 | concise_casper.current_epoch(), 513 | concise_casper.recommended_source_epoch(), 514 | validation_key 515 | ) 516 | return vote_1, vote_2 517 | return mk_slash_votes 518 | 519 | 520 | @pytest.fixture 521 | def mk_logout_msg_signed(w3): 522 | def mk_logout_msg_signed(validator_index, epoch, validation_key): 523 | msg_hash = Web3.sha3(rlp.encode([validator_index, epoch])) 524 | signed = w3.eth.account.signHash(msg_hash, validation_key) 525 | sig = encode_int32(signed.v) + encode_int32(signed.r) + encode_int32(signed.s) 526 | return rlp.encode([validator_index, epoch, sig]) 527 | return mk_logout_msg_signed 528 | 529 | 530 | @pytest.fixture 531 | def mk_logout_msg_unsigned(): 532 | def mk_logout_msg_unsigned(validator_index, epoch): 533 | v, r, s = (0, 0, 0) 534 | sig = encode_int32(v) + encode_int32(r) + encode_int32(s) 535 | return rlp.encode([validator_index, epoch, sig]) 536 | return mk_logout_msg_unsigned 537 | 538 | 539 | @pytest.fixture 540 | def logout_validator_via_signed_msg(casper, concise_casper, mk_logout_msg_signed, base_sender): 541 | def logout_validator_via_signed_msg(validator_index, 542 | msg_signing_key, 543 | tx_sender_addr=base_sender): 544 | logout_msg = mk_logout_msg_signed( 545 | validator_index, 546 | concise_casper.current_epoch(), 547 | msg_signing_key 548 | ) 549 | casper.functions.logout(logout_msg).transact({'from': tx_sender_addr}) 550 | return logout_validator_via_signed_msg 551 | 552 | 553 | @pytest.fixture 554 | def logout_validator_via_unsigned_msg(casper, concise_casper, mk_logout_msg_unsigned): 555 | def logout_validator_via_unsigned_msg(validator_index, tx_sender_addr): 556 | logout_tx = mk_logout_msg_unsigned(validator_index, concise_casper.current_epoch()) 557 | casper.functions.logout(logout_tx).transact({'from': tx_sender_addr}) 558 | return logout_validator_via_unsigned_msg 559 | 560 | 561 | @pytest.fixture 562 | def deploy_validation_contract(w3, casper, mk_validation_code): 563 | def deploy_validation_contract(addr, valcode_type): 564 | tx_hash = w3.eth.sendTransaction({ 565 | 'to': '', 566 | 'data': mk_validation_code(addr, valcode_type) 567 | }) 568 | contract_address = w3.eth.getTransactionReceipt(tx_hash).contractAddress 569 | return contract_address 570 | return deploy_validation_contract 571 | 572 | 573 | @pytest.fixture 574 | def deposit_validator(w3, tester, casper, deploy_validation_contract): 575 | def deposit_validator( 576 | withdrawal_addr, 577 | validation_key, 578 | value, 579 | valcode_type="pure_ecrecover"): 580 | 581 | validation_addr = w3.eth.account.privateKeyToAccount(validation_key).address 582 | validation_contract_addr = deploy_validation_contract(validation_addr, valcode_type) 583 | 584 | casper.functions.deposit( 585 | validation_contract_addr, 586 | withdrawal_addr 587 | ).transact({ 588 | 'value': value 589 | }) 590 | 591 | return casper.functions.validator_indexes(withdrawal_addr).call() 592 | return deposit_validator 593 | 594 | 595 | # deposits privkey, value and steps forward two epochs 596 | # to step dynasties forward to induct validator 597 | # NOTE: This method only works when no deposits exist and chain insta-finalizes 598 | # If inducting a validator when desposits exists, use `deposit_validator` and 599 | # manually finalize 600 | @pytest.fixture 601 | def induct_validator(w3, tester, casper, deposit_validator, new_epoch): 602 | def induct_validator( 603 | withdrawal_addr, 604 | validation_key, 605 | value, 606 | valcode_type="pure_ecrecover"): 607 | 608 | validator_index = deposit_validator( 609 | withdrawal_addr, 610 | validation_key, 611 | value, 612 | valcode_type 613 | ) 614 | new_epoch() # justify 615 | new_epoch() # finalize and increment dynasty 616 | new_epoch() # finalize and increment dynasty 617 | return validator_index 618 | return induct_validator 619 | 620 | 621 | # deposits list of (privkey, value) and steps forward two epochs 622 | # to step dynasties forward to induct validators 623 | # NOTE: This method only works when no deposits exist and chain insta-finalizes 624 | # If inducting validators when desposits exists, use `deposit_validator` and 625 | # manually finalize 626 | @pytest.fixture 627 | def induct_validators(tester, casper, deposit_validator, new_epoch): 628 | def induct_validators(accounts, validation_keys, values): 629 | start_index = casper.functions.next_validator_index().call() 630 | for account, key, value in zip(accounts, validation_keys, values): 631 | deposit_validator(account, key, value) 632 | new_epoch() # justify 633 | new_epoch() # finalize and increment dynasty 634 | new_epoch() # finalize and increment dynasty 635 | return list(range(start_index, start_index + len(accounts))) 636 | return induct_validators 637 | 638 | 639 | @pytest.fixture 640 | def assert_failed(): 641 | def assert_failed(function_to_test, exception): 642 | with pytest.raises(exception): 643 | function_to_test() 644 | return assert_failed 645 | 646 | 647 | @pytest.fixture 648 | def assert_tx_failed(base_tester): 649 | def assert_tx_failed(function_to_test, exception=eth_tester.exceptions.TransactionFailed): 650 | snapshot_id = base_tester.take_snapshot() 651 | with pytest.raises(exception): 652 | function_to_test() 653 | base_tester.revert_to_snapshot(snapshot_id) 654 | return assert_tx_failed 655 | -------------------------------------------------------------------------------- /tests/test_chain_initialization.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def test_rlp_decoding_is_pure( 4 | tester, 5 | purity_checker, 6 | vyper_rlp_decoder_address 7 | ): 8 | purity_return_val = purity_checker.functions.submit(vyper_rlp_decoder_address).call() 9 | assert purity_return_val == 1 10 | 11 | 12 | def test_msg_hasher_is_pure( 13 | tester, 14 | purity_checker, 15 | msg_hasher_address, 16 | ): 17 | purity_return_val = purity_checker.functions.submit(msg_hasher_address).call() 18 | assert purity_return_val == 1 19 | 20 | 21 | # sanity check on casper contract basic functionality 22 | def test_init_first_epoch(tester, concise_casper, new_epoch, warm_up_period, epoch_length): 23 | block_number = tester.get_block_by_number('latest')['number'] 24 | start_epoch = (block_number + warm_up_period) // epoch_length 25 | 26 | assert concise_casper.current_epoch() == start_epoch 27 | assert concise_casper.next_validator_index() == 1 28 | 29 | new_epoch() 30 | 31 | assert concise_casper.dynasty() == 0 32 | assert concise_casper.next_validator_index() == 1 33 | assert concise_casper.current_epoch() == start_epoch + 1 34 | assert concise_casper.total_slashed(concise_casper.current_epoch()) == 0 35 | -------------------------------------------------------------------------------- /tests/test_deposit.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_deposit_sets_withdrawal_addr(concise_casper, 5 | funded_account, 6 | validation_key, 7 | deposit_amount, 8 | deposit_validator): 9 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 10 | 11 | assert concise_casper.validators__withdrawal_addr(validator_index) == funded_account 12 | 13 | 14 | def test_deposit_sets_validator_deposit(concise_casper, 15 | funded_account, 16 | validation_key, 17 | deposit_amount, 18 | deposit_validator): 19 | current_epoch = concise_casper.current_epoch() 20 | scale_factor = concise_casper.deposit_scale_factor(current_epoch) 21 | expected_scaled_deposit = deposit_amount / scale_factor 22 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 23 | 24 | assert concise_casper.validators__deposit(validator_index) == expected_scaled_deposit 25 | 26 | 27 | def test_deposit_updates_next_val_index(concise_casper, 28 | funded_account, 29 | validation_key, 30 | deposit_amount, 31 | deposit_validator): 32 | next_validator_index = concise_casper.next_validator_index() 33 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 34 | assert validator_index == next_validator_index 35 | assert concise_casper.next_validator_index() == next_validator_index + 1 36 | 37 | 38 | def test_deposit_sets_start_dynasty(concise_casper, 39 | funded_account, 40 | validation_key, 41 | deposit_amount, 42 | deposit_validator): 43 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 44 | expected_start_dynasty = concise_casper.dynasty() + 2 45 | assert concise_casper.validators__start_dynasty(validator_index) == expected_start_dynasty 46 | 47 | 48 | def test_deposit_sets_end_dynasty(concise_casper, 49 | funded_account, 50 | validation_key, 51 | deposit_amount, 52 | deposit_validator): 53 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 54 | 55 | expected_end_dynasty = 1000000000000000000000000000000 56 | assert concise_casper.validators__end_dynasty(validator_index) == expected_end_dynasty 57 | 58 | 59 | def test_deposit_is_not_slashed(concise_casper, 60 | funded_account, 61 | validation_key, 62 | deposit_amount, 63 | deposit_validator): 64 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 65 | assert not concise_casper.validators__is_slashed(validator_index) 66 | 67 | 68 | def test_deposit_total_deposits_at_logout(concise_casper, 69 | funded_account, 70 | validation_key, 71 | deposit_amount, 72 | deposit_validator): 73 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 74 | 75 | assert concise_casper.validators__total_deposits_at_logout(validator_index) == 0 76 | 77 | 78 | def test_deposit_updates_dynasty_wei_delta(concise_casper, 79 | funded_account, 80 | validation_key, 81 | deposit_amount, 82 | deposit_validator): 83 | start_dynasty = concise_casper.dynasty() + 2 84 | assert concise_casper.dynasty_wei_delta(start_dynasty) == 0 85 | 86 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 87 | scaled_deposit_size = concise_casper.validators__deposit(validator_index) 88 | 89 | assert concise_casper.dynasty_wei_delta(start_dynasty) == scaled_deposit_size 90 | 91 | 92 | def test_deposit_updates_total_deposits(casper, 93 | concise_casper, 94 | funded_account, 95 | validation_key, 96 | deposit_amount, 97 | induct_validator, 98 | mk_suggested_vote, 99 | send_vote, 100 | new_epoch): 101 | assert concise_casper.total_curdyn_deposits_in_wei() == 0 102 | assert concise_casper.total_prevdyn_deposits_in_wei() == 0 103 | 104 | # note, full induction 105 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 106 | 107 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 108 | assert concise_casper.total_prevdyn_deposits_in_wei() == 0 109 | 110 | send_vote(mk_suggested_vote(validator_index, validation_key)) 111 | new_epoch() 112 | 113 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 114 | assert concise_casper.total_prevdyn_deposits_in_wei() == deposit_amount 115 | 116 | 117 | @pytest.mark.parametrize( 118 | 'warm_up_period,epoch_length', 119 | [ 120 | (10, 5), 121 | (25, 10), 122 | (100, 50), 123 | ] 124 | ) 125 | def test_deposit_during_warm_up_period(concise_casper, 126 | funded_account, 127 | validation_key, 128 | deposit_amount, 129 | deposit_validator, 130 | new_epoch, 131 | warm_up_period, 132 | epoch_length): 133 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 134 | 135 | expected_start_dynasty = concise_casper.dynasty() + 2 136 | assert concise_casper.validators__start_dynasty(validator_index) == expected_start_dynasty 137 | 138 | new_epoch() # new_epoch mines through warm_up_period on first call 139 | concise_casper.dynasty() == 0 140 | new_epoch() 141 | concise_casper.dynasty() == 1 142 | new_epoch() 143 | concise_casper.dynasty() == 2 144 | 145 | concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 146 | concise_casper.total_prevdyn_deposits_in_wei() == 0 147 | 148 | new_epoch() 149 | 150 | concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 151 | concise_casper.total_prevdyn_deposits_in_wei() == deposit_amount 152 | -------------------------------------------------------------------------------- /tests/test_fork_choice_helpers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize( 5 | 'start_epoch,min_deposits', 6 | [ 7 | (2, 0), 8 | (3, 1), 9 | (0, int(1e4)), 10 | (7, int(4e10)), 11 | (6, int(2e30)) 12 | ] 13 | ) 14 | def test_default_highest_justified_epoch( 15 | base_tester, 16 | start_epoch, 17 | min_deposits, 18 | epoch_length, 19 | casper_args, 20 | deploy_casper_contract): 21 | block_number = base_tester.get_block_by_number('latest')['number'] 22 | base_tester.mine_blocks( 23 | epoch_length * start_epoch - block_number 24 | ) 25 | casper = deploy_casper_contract(casper_args) 26 | 27 | assert casper.functions.highest_justified_epoch(min_deposits).call() == 0 28 | 29 | 30 | @pytest.mark.parametrize( 31 | 'min_deposits', 32 | [ 33 | (0), 34 | (1), 35 | (int(1e4)), 36 | (int(4e10)), 37 | (int(2e30)) 38 | ] 39 | ) 40 | def test_highest_justified_epoch_no_validators( 41 | concise_casper, 42 | new_epoch, 43 | min_deposits): 44 | for i in range(5): 45 | highest_justified_epoch = concise_casper.highest_justified_epoch(min_deposits) 46 | if min_deposits == 0: 47 | assert highest_justified_epoch == concise_casper.last_justified_epoch() 48 | else: 49 | assert highest_justified_epoch == 0 50 | 51 | new_epoch() 52 | 53 | 54 | @pytest.mark.parametrize( 55 | 'start_epoch,min_deposits,warm_up_period', 56 | [ 57 | (2, 0, 0), 58 | (3, 1, 0), 59 | (0, int(1e4), 0), 60 | (7, int(4e10), 0), 61 | (6, int(2e30), 0), 62 | ] 63 | ) 64 | def test_default_highest_finalized_epoch( 65 | base_tester, 66 | start_epoch, 67 | min_deposits, 68 | warm_up_period, 69 | epoch_length, 70 | casper_args, 71 | deploy_casper_contract): 72 | 73 | block_number = base_tester.get_block_by_number('latest')['number'] 74 | base_tester.mine_blocks( 75 | max(epoch_length * start_epoch - block_number - 2, 0) 76 | ) 77 | casper = deploy_casper_contract(casper_args) 78 | 79 | assert casper.functions.START_EPOCH().call() == start_epoch 80 | assert casper.functions.highest_finalized_epoch(min_deposits).call() == -1 81 | 82 | 83 | @pytest.mark.parametrize( 84 | 'min_deposits', 85 | [ 86 | (0), 87 | (1), 88 | (int(1e4)), 89 | (int(4e10)), 90 | (int(2e30)) 91 | ] 92 | ) 93 | def test_highest_finalized_epoch_no_validators(concise_casper, new_epoch, min_deposits): 94 | for i in range(5): 95 | highest_finalized_epoch = concise_casper.highest_finalized_epoch(min_deposits) 96 | if min_deposits > 0: 97 | expected_epoch = -1 98 | else: 99 | if concise_casper.current_epoch() == concise_casper.START_EPOCH(): 100 | expected_epoch = -1 101 | else: 102 | expected_epoch = concise_casper.last_finalized_epoch() 103 | 104 | assert highest_finalized_epoch == expected_epoch 105 | 106 | new_epoch() 107 | 108 | 109 | def test_highest_justified_and_epoch( 110 | casper, 111 | concise_casper, 112 | funded_account, 113 | validation_key, 114 | deposit_amount, 115 | new_epoch, 116 | induct_validator, 117 | send_vote, 118 | mk_suggested_vote): 119 | start_epoch = concise_casper.START_EPOCH() 120 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 121 | higher_deposit = int(deposit_amount * 1.1) 122 | 123 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 124 | assert concise_casper.current_epoch() == start_epoch + 3 # 3 to induct first dynasty 125 | 126 | assert concise_casper.highest_justified_epoch(deposit_amount) == 0 127 | assert concise_casper.highest_finalized_epoch(deposit_amount) == -1 128 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 2 129 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 2 130 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 131 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 132 | 133 | # justify current_epoch in contract 134 | send_vote(mk_suggested_vote(validator_index, validation_key)) 135 | 136 | assert concise_casper.checkpoints__cur_dyn_deposits(start_epoch + 3) == 0 137 | assert concise_casper.checkpoints__prev_dyn_deposits(start_epoch + 3) == 0 138 | assert concise_casper.last_justified_epoch() == start_epoch + 3 139 | assert concise_casper.last_finalized_epoch() == start_epoch + 2 140 | 141 | assert concise_casper.highest_justified_epoch(deposit_amount) == 0 142 | assert concise_casper.highest_finalized_epoch(deposit_amount) == -1 143 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 3 144 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 2 145 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 146 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 147 | 148 | new_epoch() 149 | send_vote(mk_suggested_vote(validator_index, validation_key)) 150 | 151 | assert concise_casper.checkpoints__cur_dyn_deposits(start_epoch + 4) == deposit_amount 152 | assert concise_casper.checkpoints__prev_dyn_deposits(start_epoch + 4) == 0 153 | assert concise_casper.last_justified_epoch() == start_epoch + 4 154 | assert concise_casper.last_finalized_epoch() == start_epoch + 3 155 | 156 | assert concise_casper.highest_justified_epoch(deposit_amount) == 0 157 | assert concise_casper.highest_finalized_epoch(deposit_amount) == -1 158 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 4 159 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 3 160 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 161 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 162 | 163 | new_epoch() 164 | send_vote(mk_suggested_vote(validator_index, validation_key)) 165 | 166 | assert concise_casper.checkpoints__cur_dyn_deposits(start_epoch + 5) == deposit_amount 167 | assert concise_casper.checkpoints__prev_dyn_deposits(start_epoch + 5) == deposit_amount 168 | assert concise_casper.last_justified_epoch() == start_epoch + 5 169 | assert concise_casper.last_finalized_epoch() == start_epoch + 4 170 | 171 | # enough prev and cur deposits in checkpoint 5 for the justified block 172 | assert concise_casper.highest_justified_epoch(deposit_amount) == start_epoch + 5 173 | # not enough prev and cur deposits in checkpoint 4 for the finalized block 174 | assert concise_casper.highest_finalized_epoch(deposit_amount) == -1 175 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 5 176 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 4 177 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 178 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 179 | 180 | new_epoch() 181 | send_vote(mk_suggested_vote(validator_index, validation_key)) 182 | 183 | assert concise_casper.checkpoints__cur_dyn_deposits(start_epoch + 6) > deposit_amount 184 | assert concise_casper.checkpoints__prev_dyn_deposits(start_epoch + 6) > deposit_amount 185 | assert concise_casper.last_justified_epoch() == start_epoch + 6 186 | assert concise_casper.last_finalized_epoch() == start_epoch + 5 187 | 188 | # enough deposits in checkpoint 6 for justified and checkpoint 5 for finalized! 189 | assert concise_casper.highest_justified_epoch(deposit_amount) == start_epoch + 6 190 | assert concise_casper.highest_finalized_epoch(deposit_amount) == start_epoch + 5 191 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 192 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 193 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 6 194 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 5 195 | 196 | new_epoch() 197 | # no vote 198 | 199 | assert concise_casper.checkpoints__cur_dyn_deposits(start_epoch + 7) > deposit_amount 200 | assert concise_casper.checkpoints__prev_dyn_deposits(start_epoch + 7) > deposit_amount 201 | assert concise_casper.last_justified_epoch() == start_epoch + 6 202 | assert concise_casper.last_finalized_epoch() == start_epoch + 5 203 | 204 | assert concise_casper.highest_justified_epoch(deposit_amount) == start_epoch + 6 205 | assert concise_casper.highest_finalized_epoch(deposit_amount) == start_epoch + 5 206 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 6 207 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 5 208 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 209 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 210 | 211 | new_epoch() 212 | send_vote(mk_suggested_vote(validator_index, validation_key)) 213 | 214 | assert concise_casper.checkpoints__cur_dyn_deposits(start_epoch + 8) > deposit_amount 215 | assert concise_casper.checkpoints__prev_dyn_deposits(start_epoch + 8) > deposit_amount 216 | # new justified 217 | assert concise_casper.last_justified_epoch() == start_epoch + 8 218 | # no new finalized because not sequential justified blocks 219 | assert concise_casper.last_finalized_epoch() == start_epoch + 5 220 | 221 | assert concise_casper.highest_justified_epoch(deposit_amount) == start_epoch + 8 222 | assert concise_casper.highest_finalized_epoch(deposit_amount) == start_epoch + 5 223 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 8 224 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 5 225 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 226 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 227 | 228 | new_epoch() 229 | send_vote(mk_suggested_vote(validator_index, validation_key)) 230 | 231 | assert concise_casper.checkpoints__cur_dyn_deposits(9) > deposit_amount 232 | assert concise_casper.checkpoints__prev_dyn_deposits(9) > deposit_amount 233 | # new justified and finalized because sequential justified blocks 234 | assert concise_casper.last_justified_epoch() == start_epoch + 9 235 | assert concise_casper.last_finalized_epoch() == start_epoch + 8 236 | 237 | assert concise_casper.highest_justified_epoch(deposit_amount) == start_epoch + 9 238 | assert concise_casper.highest_finalized_epoch(deposit_amount) == start_epoch + 8 239 | assert concise_casper.highest_justified_epoch(0) == start_epoch + 9 240 | assert concise_casper.highest_finalized_epoch(0) == start_epoch + 8 241 | assert concise_casper.highest_justified_epoch(higher_deposit) == 0 242 | assert concise_casper.highest_finalized_epoch(higher_deposit) == -1 243 | -------------------------------------------------------------------------------- /tests/test_init.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from decimal import Decimal 4 | import web3 5 | import eth_tester 6 | 7 | 8 | TRANSACTION_FAILED = eth_tester.exceptions.TransactionFailed 9 | VALIDATION_ERROR = web3.exceptions.ValidationError 10 | 11 | 12 | def test_no_double_init( 13 | casper_args, 14 | deploy_casper_contract, 15 | assert_tx_failed): 16 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 17 | 18 | casper.functions.init(*casper_args).transact() 19 | assert_tx_failed( 20 | lambda: casper.functions.init(*casper_args).transact() 21 | ) 22 | 23 | 24 | @pytest.mark.parametrize( 25 | 'deploy_epoch,warm_up_period,epoch_length,expected_start_epoch', 26 | [ 27 | (0, 0, 50, 0), 28 | (0, 10, 50, 0), 29 | (0, 51, 50, 1), 30 | (0, 100, 50, 2), 31 | (1, 0, 20, 1), 32 | (1, 21, 20, 2), 33 | (4, 230, 50, 8), 34 | (10, 500, 25, 30) 35 | ] 36 | ) 37 | def test_start_epoch( 38 | base_tester, 39 | deploy_epoch, 40 | warm_up_period, 41 | expected_start_epoch, 42 | epoch_length, 43 | casper_args, 44 | casper_config, 45 | deploy_casper_contract): 46 | block_number = base_tester.get_block_by_number('latest')['number'] 47 | base_tester.mine_blocks( 48 | max(epoch_length * deploy_epoch - block_number - 1, 0) 49 | ) 50 | 51 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 52 | casper.functions.init(*casper_args).transact() 53 | 54 | assert casper.functions.START_EPOCH().call() == expected_start_epoch 55 | assert casper.functions.current_epoch().call() == expected_start_epoch 56 | 57 | 58 | @pytest.mark.parametrize( 59 | 'epoch_length, error', 60 | [ 61 | (-1, VALIDATION_ERROR), 62 | (0, TRANSACTION_FAILED), 63 | (10, None), 64 | (250, None), 65 | (256, TRANSACTION_FAILED), 66 | (500, TRANSACTION_FAILED), 67 | ] 68 | ) 69 | def test_init_epoch_length( 70 | epoch_length, 71 | error, 72 | casper_args, 73 | deploy_casper_contract, 74 | assert_tx_failed): 75 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 76 | 77 | if error: 78 | assert_tx_failed( 79 | lambda: casper.functions.init(*casper_args).transact(), 80 | error 81 | ) 82 | return 83 | 84 | casper.functions.init(*casper_args).transact() 85 | assert casper.functions.EPOCH_LENGTH().call() == epoch_length 86 | 87 | 88 | @pytest.mark.parametrize( 89 | 'warm_up_period, error', 90 | [ 91 | (-1, VALIDATION_ERROR), 92 | (0, None), 93 | (10, None), 94 | (256, None), 95 | (50000, None), 96 | ] 97 | ) 98 | def test_init_warm_up_period(warm_up_period, 99 | error, 100 | casper_args, 101 | deploy_casper_contract, 102 | assert_tx_failed): 103 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 104 | 105 | if error: 106 | assert_tx_failed( 107 | lambda: casper.functions.init(*casper_args).transact(), 108 | error 109 | ) 110 | return 111 | 112 | casper.functions.init(*casper_args).transact() 113 | assert casper.functions.WARM_UP_PERIOD().call() == warm_up_period 114 | 115 | @pytest.mark.parametrize( 116 | 'withdrawal_delay, error', 117 | [ 118 | (-42, VALIDATION_ERROR), 119 | (-1, VALIDATION_ERROR), 120 | (0, None), 121 | (1, None), 122 | (10, None), 123 | (10000, None), 124 | (500000000, None), 125 | ] 126 | ) 127 | def test_init_withdrawal_delay(withdrawal_delay, 128 | error, 129 | casper_args, 130 | deploy_casper_contract, 131 | assert_tx_failed): 132 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 133 | 134 | if error: 135 | assert_tx_failed( 136 | lambda: casper.functions.init(*casper_args).transact(), 137 | error 138 | ) 139 | return 140 | 141 | casper.functions.init(*casper_args).transact() 142 | assert casper.functions.WITHDRAWAL_DELAY().call() == withdrawal_delay 143 | 144 | 145 | @pytest.mark.parametrize( 146 | 'dynasty_logout_delay, error', 147 | [ 148 | (-42, VALIDATION_ERROR), 149 | (-1, VALIDATION_ERROR), 150 | (0, TRANSACTION_FAILED), 151 | (1, TRANSACTION_FAILED), 152 | (2, None), 153 | (3, None), 154 | (100, None), 155 | (3000000, None), 156 | ] 157 | ) 158 | def test_init_dynasty_logout_delay(dynasty_logout_delay, 159 | error, 160 | casper_args, 161 | deploy_casper_contract, 162 | assert_tx_failed): 163 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 164 | 165 | if error: 166 | assert_tx_failed( 167 | lambda: casper.functions.init(*casper_args).transact(), 168 | error 169 | ) 170 | return 171 | 172 | casper.functions.init(*casper_args).transact() 173 | assert casper.functions.DYNASTY_LOGOUT_DELAY().call() == dynasty_logout_delay 174 | 175 | 176 | @pytest.mark.parametrize( 177 | 'base_interest_factor, error', 178 | [ 179 | (-10, TRANSACTION_FAILED), 180 | (Decimal('-0.001'), TRANSACTION_FAILED), 181 | (0, None), 182 | (Decimal('7e-3'), None), 183 | (Decimal('0.1'), None), 184 | (Decimal('1.5'), None), 185 | ] 186 | ) 187 | def test_init_base_interest_factor(base_interest_factor, 188 | error, 189 | casper_args, 190 | deploy_casper_contract, 191 | assert_tx_failed): 192 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 193 | 194 | if error: 195 | assert_tx_failed( 196 | lambda: casper.functions.init(*casper_args).transact(), 197 | error 198 | ) 199 | return 200 | 201 | casper.functions.init(*casper_args).transact() 202 | assert casper.functions.BASE_INTEREST_FACTOR().call() == base_interest_factor 203 | 204 | 205 | @pytest.mark.parametrize( 206 | 'base_penalty_factor, error', 207 | [ 208 | (-10, TRANSACTION_FAILED), 209 | (Decimal('-0.001'), TRANSACTION_FAILED), 210 | (0, None), 211 | (Decimal('7e-3'), None), 212 | (Decimal('0.1'), None), 213 | (Decimal('1.5'), None), 214 | ] 215 | ) 216 | def test_init_base_penalty_factor(base_penalty_factor, 217 | error, 218 | casper_args, 219 | deploy_casper_contract, 220 | assert_tx_failed): 221 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 222 | 223 | if error: 224 | assert_tx_failed( 225 | lambda: casper.functions.init(*casper_args).transact(), 226 | error 227 | ) 228 | return 229 | 230 | casper.functions.init(*casper_args).transact() 231 | assert casper.functions.BASE_PENALTY_FACTOR().call() == base_penalty_factor 232 | 233 | 234 | @pytest.mark.parametrize( 235 | 'min_deposit_size, error', 236 | [ 237 | (int(-1e10), VALIDATION_ERROR), 238 | (-1, VALIDATION_ERROR), 239 | (0, TRANSACTION_FAILED), 240 | (1, None), 241 | (42, None), 242 | (int(1e4), None), 243 | (int(2.5e20), None), 244 | (int(5e30), None), 245 | ] 246 | ) 247 | def test_init_min_deposit_size(min_deposit_size, 248 | error, 249 | casper_args, 250 | deploy_casper_contract, 251 | assert_tx_failed): 252 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 253 | 254 | if error: 255 | assert_tx_failed( 256 | lambda: casper.functions.init(*casper_args).transact(), 257 | error 258 | ) 259 | return 260 | 261 | casper.functions.init(*casper_args).transact() 262 | assert casper.functions.MIN_DEPOSIT_SIZE().call() == min_deposit_size 263 | 264 | 265 | def test_init_null_sender(null_sender, 266 | casper_args, 267 | deploy_casper_contract): 268 | casper = deploy_casper_contract(casper_args, initialize_contract=False) 269 | 270 | casper.functions.init(*casper_args).transact() 271 | assert casper.functions.NULL_SENDER().call() == null_sender 272 | -------------------------------------------------------------------------------- /tests/test_initialize_epoch.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from web3 import ( 4 | Web3, 5 | ) 6 | 7 | 8 | # ensure that our fixture 'new_epoch' functions properly 9 | @pytest.mark.parametrize( 10 | 'warm_up_period, epoch_length', 11 | [ 12 | (0, 5), 13 | (20, 10), 14 | (21, 10), 15 | (220, 20), 16 | ] 17 | ) 18 | def test_new_epoch_fixture(tester, concise_casper, new_epoch, warm_up_period, epoch_length): 19 | for i in range(4): 20 | prev_epoch = concise_casper.current_epoch() 21 | prev_block_number = tester.get_block_by_number('latest')['number'] 22 | if i == 0: 23 | expected_jump = warm_up_period 24 | expected_jump += epoch_length - (prev_block_number + warm_up_period) % epoch_length 25 | else: 26 | expected_jump = epoch_length - (prev_block_number % epoch_length) 27 | 28 | new_epoch() 29 | block_number = tester.get_block_by_number('latest')['number'] 30 | 31 | assert concise_casper.current_epoch() == prev_epoch + 1 32 | 33 | # +1 because intialize_epoch is mined to take effect 34 | assert block_number == prev_block_number + expected_jump + 1 35 | # should be 1 block past the initialize_epoch block 36 | assert (block_number % epoch_length) == 1 37 | 38 | 39 | def test_epoch_length_range(tester, casper, new_epoch, epoch_length, assert_tx_failed): 40 | new_epoch() 41 | 42 | for _ in range(epoch_length * 3): # check the entire range 3 times 43 | block_number = tester.get_block_by_number('latest')['number'] 44 | 45 | next_is_init_block = (block_number + 1) % epoch_length == 0 46 | next_epoch = casper.functions.current_epoch().call() + 1 47 | if next_is_init_block: 48 | casper.functions.initialize_epoch(next_epoch).transact() 49 | assert casper.functions.current_epoch().call() == next_epoch 50 | else: 51 | assert_tx_failed( 52 | lambda: casper.functions.initialize_epoch(next_epoch).transact() 53 | ) 54 | tester.mine_block() 55 | 56 | 57 | @pytest.mark.parametrize( 58 | 'warm_up_period, epoch_length', 59 | [ 60 | (15, 5), 61 | (20, 10), 62 | (100, 50), 63 | (220, 20), 64 | ] 65 | ) 66 | def test_cannot_initialize_during_warm_up( 67 | tester, 68 | casper, 69 | epoch_length, 70 | warm_up_period, 71 | assert_tx_failed): 72 | 73 | block_number = tester.get_block_by_number('latest')['number'] 74 | current_epoch = casper.functions.current_epoch().call() 75 | assert current_epoch == (block_number + warm_up_period) // epoch_length 76 | 77 | next_epoch = current_epoch + 1 78 | # -1 because the block that called 'init' counts toward warm_up_period 79 | for _ in range(warm_up_period - 1): 80 | # check then mine to ensure that the start block counts 81 | assert_tx_failed( 82 | lambda: casper.functions.initialize_epoch(next_epoch).transact() 83 | ) 84 | tester.mine_block() 85 | 86 | # mine right up until the start of the next epoch 87 | next_block_number = tester.get_block_by_number('latest')['number'] + 1 88 | blocks_until_next_start = epoch_length - next_block_number % epoch_length 89 | for _ in range(blocks_until_next_start): 90 | assert_tx_failed( 91 | lambda: casper.functions.initialize_epoch(next_epoch).transact() 92 | ) 93 | tester.mine_block() 94 | 95 | next_block_number = tester.get_block_by_number('latest')['number'] + 1 96 | assert next_block_number % epoch_length == 0 97 | # at start of next_epoch 98 | casper.functions.initialize_epoch(next_epoch).transact() 99 | assert casper.functions.current_epoch().call() == next_epoch 100 | 101 | 102 | def test_double_epoch_initialization(tester, casper, new_epoch, epoch_length, assert_tx_failed): 103 | new_epoch() 104 | initial_epoch = casper.functions.current_epoch().call() 105 | 106 | tester.mine_blocks(epoch_length - 2) 107 | next_block_number = tester.get_block_by_number('latest')['number'] + 1 108 | assert next_block_number % epoch_length == 0 109 | 110 | next_epoch = initial_epoch + 1 111 | casper.functions.initialize_epoch(next_epoch).transact() 112 | assert casper.functions.current_epoch().call() == next_epoch 113 | assert_tx_failed( 114 | lambda: casper.functions.initialize_epoch(next_epoch).transact() 115 | ) 116 | 117 | 118 | def test_epoch_initialization_one_block_late(tester, casper, epoch_length, new_epoch): 119 | new_epoch() 120 | initial_epoch = casper.functions.current_epoch().call() 121 | 122 | tester.mine_blocks(epoch_length - 1) 123 | next_block_number = tester.get_block_by_number('latest')['number'] + 1 124 | assert (next_block_number % epoch_length) == 1 125 | 126 | expected_epoch = initial_epoch + 1 127 | casper.functions.initialize_epoch(expected_epoch).transact() 128 | 129 | assert casper.functions.current_epoch().call() == expected_epoch 130 | 131 | 132 | def test_epoch_initialize_one_epoch_late( 133 | tester, 134 | casper, 135 | new_epoch, 136 | epoch_length, 137 | assert_tx_failed): 138 | new_epoch() 139 | initial_epoch = casper.functions.current_epoch().call() 140 | 141 | tester.mine_blocks(epoch_length * 2 - 2) 142 | next_block_number = tester.get_block_by_number('latest')['number'] + 1 143 | assert (next_block_number % epoch_length) == 0 144 | 145 | assert_tx_failed( 146 | lambda: casper.functions.initialize_epoch(initial_epoch + 2).transact() 147 | ) 148 | assert casper.functions.current_epoch().call() == initial_epoch 149 | casper.functions.initialize_epoch(initial_epoch + 1).transact() 150 | assert casper.functions.current_epoch().call() == initial_epoch + 1 151 | casper.functions.initialize_epoch(initial_epoch + 2).transact() 152 | assert casper.functions.current_epoch().call() == initial_epoch + 2 153 | 154 | 155 | def test_checkpoint_hashes(tester, casper, epoch_length, new_epoch): 156 | for _ in range(4): 157 | next_epoch = casper.functions.current_epoch().call() + 1 158 | 159 | block_number = tester.get_block_by_number('latest')['number'] 160 | tester.mine_blocks(epoch_length * next_epoch - block_number - 1) 161 | 162 | next_block_number = tester.get_block_by_number('latest')['number'] + 1 163 | assert (next_block_number % epoch_length) == 0 164 | 165 | casper.functions.initialize_epoch(next_epoch).transact() 166 | current_epoch = casper.functions.current_epoch().call() 167 | 168 | checkpoint_block_number = next_block_number - 1 169 | expected_hash = tester.get_block_by_number(checkpoint_block_number)['hash'] 170 | actual_hash = Web3.toHex(casper.functions.checkpoint_hashes(current_epoch).call()) 171 | 172 | assert current_epoch == next_epoch 173 | assert actual_hash == expected_hash 174 | 175 | 176 | def test_checkpoint_deposits( 177 | tester, 178 | casper, 179 | concise_casper, 180 | funded_accounts, 181 | validation_keys, 182 | deposit_amount, 183 | deposit_validator, 184 | new_epoch, 185 | send_vote, 186 | mk_suggested_vote): 187 | current_epoch = concise_casper.current_epoch() 188 | assert concise_casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 189 | assert concise_casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 190 | 191 | new_epoch() 192 | current_epoch = concise_casper.current_epoch() 193 | 194 | assert concise_casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 195 | assert concise_casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 196 | 197 | initial_validator = deposit_validator(funded_accounts[0], validation_keys[0], deposit_amount) 198 | 199 | new_epoch() 200 | current_epoch = concise_casper.current_epoch() 201 | 202 | assert concise_casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 203 | assert concise_casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 204 | 205 | new_epoch() 206 | current_epoch = concise_casper.current_epoch() 207 | 208 | # checkpoints are for the last block in the previous epoch 209 | # so checkpoint dynasty totals should lag behind 210 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 211 | assert concise_casper.total_prevdyn_deposits_in_wei() == 0 212 | assert concise_casper.checkpoints__cur_dyn_deposits(current_epoch) == 0 213 | assert concise_casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 214 | 215 | send_vote(mk_suggested_vote(initial_validator, validation_keys[0])) 216 | new_epoch() 217 | current_epoch = concise_casper.current_epoch() 218 | 219 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 220 | assert concise_casper.total_prevdyn_deposits_in_wei() == deposit_amount 221 | assert concise_casper.checkpoints__cur_dyn_deposits(current_epoch) == deposit_amount 222 | assert concise_casper.checkpoints__prev_dyn_deposits(current_epoch) == 0 223 | 224 | second_validator = deposit_validator(funded_accounts[1], validation_keys[1], deposit_amount) 225 | 226 | send_vote(mk_suggested_vote(initial_validator, validation_keys[0])) 227 | new_epoch() 228 | current_epoch = concise_casper.current_epoch() 229 | 230 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 231 | assert concise_casper.total_prevdyn_deposits_in_wei() == deposit_amount 232 | assert concise_casper.checkpoints__cur_dyn_deposits(current_epoch) == deposit_amount 233 | assert concise_casper.checkpoints__prev_dyn_deposits(current_epoch) == deposit_amount 234 | 235 | prev_curdyn_deposits = concise_casper.total_curdyn_deposits_in_wei() 236 | prev_prevdyn_deposits = concise_casper.total_prevdyn_deposits_in_wei() 237 | 238 | send_vote(mk_suggested_vote(initial_validator, validation_keys[0])) 239 | new_epoch() 240 | current_epoch = concise_casper.current_epoch() 241 | 242 | cur_dyn_deposits = concise_casper.checkpoints__cur_dyn_deposits(current_epoch) 243 | prev_dyn_deposits = concise_casper.checkpoints__prev_dyn_deposits(current_epoch) 244 | assert cur_dyn_deposits >= prev_curdyn_deposits \ 245 | and cur_dyn_deposits < prev_curdyn_deposits * 1.01 246 | assert prev_dyn_deposits >= prev_prevdyn_deposits \ 247 | and prev_dyn_deposits < prev_prevdyn_deposits * 1.01 248 | 249 | for _ in range(3): 250 | prev_curdyn_deposits = concise_casper.total_curdyn_deposits_in_wei() 251 | prev_prevdyn_deposits = concise_casper.total_prevdyn_deposits_in_wei() 252 | 253 | send_vote(mk_suggested_vote(initial_validator, validation_keys[0])) 254 | send_vote(mk_suggested_vote(second_validator, validation_keys[1])) 255 | new_epoch() 256 | current_epoch = concise_casper.current_epoch() 257 | 258 | cur_dyn_deposits = concise_casper.checkpoints__cur_dyn_deposits(current_epoch) 259 | prev_dyn_deposits = concise_casper.checkpoints__prev_dyn_deposits(current_epoch) 260 | 261 | assert cur_dyn_deposits >= prev_curdyn_deposits \ 262 | and cur_dyn_deposits < prev_curdyn_deposits * 1.01 263 | assert prev_dyn_deposits >= prev_prevdyn_deposits \ 264 | and prev_dyn_deposits < prev_prevdyn_deposits * 1.01 265 | -------------------------------------------------------------------------------- /tests/test_logout.py: -------------------------------------------------------------------------------- 1 | from utils.common_assertions import ( 2 | assert_validator_empty, 3 | ) 4 | 5 | 6 | def test_logout_sets_end_dynasty(concise_casper, 7 | funded_account, 8 | validation_key, 9 | deposit_amount, 10 | induct_validator, 11 | logout_validator_via_signed_msg): 12 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 13 | 14 | expected_end_dynasty = concise_casper.dynasty() + concise_casper.DYNASTY_LOGOUT_DELAY() 15 | end_dynasty = concise_casper.validators__end_dynasty(validator_index) 16 | assert end_dynasty == 1000000000000000000000000000000 17 | 18 | logout_validator_via_signed_msg(validator_index, validation_key) 19 | 20 | end_dynasty = concise_casper.validators__end_dynasty(validator_index) 21 | assert end_dynasty == expected_end_dynasty 22 | 23 | 24 | def test_logout_sets_total_deposits_at_logout(concise_casper, 25 | funded_account, 26 | validation_key, 27 | deposit_amount, 28 | induct_validator, 29 | logout_validator_via_signed_msg): 30 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 31 | assert concise_casper.validators__total_deposits_at_logout(validator_index) == 0 32 | 33 | logout_validator_via_signed_msg(validator_index, validation_key) 34 | 35 | deposits_at_logout = concise_casper.validators__total_deposits_at_logout(validator_index) 36 | assert deposits_at_logout == deposit_amount 37 | 38 | 39 | def test_logout_updates_dynasty_wei_delta(concise_casper, 40 | funded_account, 41 | validation_key, 42 | deposit_amount, 43 | induct_validator, 44 | logout_validator_via_signed_msg): 45 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 46 | scaled_deposit_size = concise_casper.validators__deposit(validator_index) 47 | 48 | expected_end_dynasty = concise_casper.dynasty() + concise_casper.DYNASTY_LOGOUT_DELAY() 49 | assert concise_casper.dynasty_wei_delta(expected_end_dynasty) == 0 50 | 51 | logout_validator_via_signed_msg(validator_index, validation_key) 52 | 53 | assert concise_casper.dynasty_wei_delta(expected_end_dynasty) == -scaled_deposit_size 54 | 55 | 56 | def test_logout_from_withdrawal_address_without_signature(concise_casper, 57 | funded_account, 58 | validation_key, 59 | deposit_amount, 60 | induct_validator, 61 | logout_validator_via_unsigned_msg): 62 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 63 | expected_end_dynasty = concise_casper.dynasty() + concise_casper.DYNASTY_LOGOUT_DELAY() 64 | 65 | logout_validator_via_unsigned_msg(validator_index, funded_account) 66 | 67 | assert concise_casper.validators__end_dynasty(validator_index) == expected_end_dynasty 68 | 69 | 70 | def test_logout_from_withdrawal_address_with_signature(concise_casper, 71 | funded_account, 72 | validation_key, 73 | deposit_amount, 74 | induct_validator, 75 | logout_validator_via_signed_msg, 76 | assert_tx_failed): 77 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 78 | expected_end_dynasty = concise_casper.dynasty() + concise_casper.DYNASTY_LOGOUT_DELAY() 79 | 80 | logout_validator_via_signed_msg( 81 | validator_index, 82 | validation_key, 83 | funded_account 84 | ) 85 | 86 | assert concise_casper.validators__end_dynasty(validator_index) == expected_end_dynasty 87 | 88 | 89 | def test_logout_from_non_withdrawal_address_without_signature(casper, 90 | funded_accounts, 91 | validation_keys, 92 | deposit_amount, 93 | induct_validator, 94 | logout_validator_via_unsigned_msg, 95 | assert_tx_failed): 96 | validator_key = validation_keys[0] 97 | validator_addr = funded_accounts[0] 98 | non_validator_addr = funded_accounts[1] 99 | assert validator_addr != non_validator_addr 100 | 101 | validator_index = induct_validator(validator_addr, validator_key, deposit_amount) 102 | 103 | assert_tx_failed( 104 | lambda: logout_validator_via_unsigned_msg(validator_index, non_validator_addr) 105 | ) 106 | 107 | 108 | def test_logout_from_non_withdrawal_address_with_signature(concise_casper, 109 | funded_accounts, 110 | validation_keys, 111 | deposit_amount, 112 | induct_validator, 113 | logout_validator_via_signed_msg, 114 | assert_tx_failed): 115 | validator_key = validation_keys[0] 116 | validator_addr = funded_accounts[0] 117 | non_validator_addr = funded_accounts[1] 118 | assert validator_addr != non_validator_addr 119 | 120 | validator_index = induct_validator(validator_addr, validator_key, deposit_amount) 121 | expected_end_dynasty = concise_casper.dynasty() + concise_casper.DYNASTY_LOGOUT_DELAY() 122 | 123 | logout_validator_via_signed_msg( 124 | validator_index, 125 | validator_key, 126 | non_validator_addr 127 | ) 128 | 129 | assert concise_casper.validators__end_dynasty(validator_index) == expected_end_dynasty 130 | 131 | 132 | def test_logout_with_multiple_validators(w3, 133 | casper, 134 | concise_casper, 135 | funded_accounts, 136 | validation_keys, 137 | deposit_amount, 138 | new_epoch, 139 | induct_validators, 140 | send_vote, 141 | mk_suggested_vote, 142 | logout_validator_via_signed_msg): 143 | validator_indexes = induct_validators( 144 | funded_accounts, 145 | validation_keys, 146 | [deposit_amount] * len(funded_accounts) 147 | ) 148 | num_validators = len(validator_indexes) 149 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount * len(funded_accounts) 150 | 151 | # finalize 3 epochs to get to a stable state 152 | for _ in range(3): 153 | for key, validator_index in zip(validation_keys, validator_indexes): 154 | send_vote(mk_suggested_vote(validator_index, key)) 155 | new_epoch() 156 | 157 | # 0th logs out 158 | logged_out_index = validator_indexes[0] 159 | logged_out_key = validation_keys[0] 160 | logged_out_addr = funded_accounts[0] 161 | # the rest remain 162 | logged_in_indexes = validator_indexes[1:] 163 | logged_in_keys = validation_keys[1:] 164 | 165 | logout_validator_via_signed_msg(logged_out_index, logged_out_key) 166 | 167 | # enter validator's end_dynasty (validator in prevdyn) 168 | dynasty_logout_delay = concise_casper.DYNASTY_LOGOUT_DELAY() 169 | for _ in range(dynasty_logout_delay): 170 | for key, validator_index in zip(validation_keys, validator_indexes): 171 | send_vote(mk_suggested_vote(validator_index, key)) 172 | new_epoch() 173 | assert concise_casper.validators__end_dynasty(logged_out_index) == concise_casper.dynasty() 174 | 175 | logged_in_deposit_size = sum(map(concise_casper.deposit_size, logged_in_indexes)) 176 | logging_out_deposit_size = concise_casper.deposit_size(logged_out_index) 177 | total_deposit_size = logged_in_deposit_size + logging_out_deposit_size 178 | 179 | curdyn_deposits = concise_casper.total_curdyn_deposits_in_wei() 180 | prevdyn_deposits = concise_casper.total_prevdyn_deposits_in_wei() 181 | assert abs(logged_in_deposit_size - curdyn_deposits) < num_validators 182 | assert abs(total_deposit_size - prevdyn_deposits) < num_validators 183 | 184 | # validator no longer in prev or cur dyn 185 | for key, validator_index in zip(logged_in_keys, logged_in_indexes): 186 | send_vote(mk_suggested_vote(validator_index, key)) 187 | new_epoch() 188 | 189 | logged_in_deposit_size = sum(map(concise_casper.deposit_size, logged_in_indexes)) 190 | 191 | curdyn_deposits = concise_casper.total_curdyn_deposits_in_wei() 192 | prevdyn_deposits = concise_casper.total_prevdyn_deposits_in_wei() 193 | assert abs(logged_in_deposit_size - curdyn_deposits) < num_validators 194 | assert abs(logged_in_deposit_size - prevdyn_deposits) < num_validators 195 | 196 | # validator can withdraw after delay 197 | for _ in range(concise_casper.WITHDRAWAL_DELAY()): 198 | for key, validator_index in zip(logged_in_keys, logged_in_indexes): 199 | send_vote(mk_suggested_vote(validator_index, key)) 200 | new_epoch() 201 | 202 | current_epoch = concise_casper.current_epoch() 203 | end_dynasty = concise_casper.validators__end_dynasty(logged_out_index) 204 | assert concise_casper.dynasty() > end_dynasty 205 | end_epoch = concise_casper.dynasty_start_epoch(end_dynasty + 1) 206 | 207 | # Allowed to withdraw 208 | assert current_epoch == end_epoch + concise_casper.WITHDRAWAL_DELAY() 209 | 210 | withdrawal_amount = int( 211 | concise_casper.validators__deposit(logged_out_index) * \ 212 | concise_casper.deposit_scale_factor(end_epoch) 213 | ) 214 | assert withdrawal_amount > 0 215 | 216 | # ensure withdrawal went to the addr 217 | prev_balance = w3.eth.getBalance(logged_out_addr) 218 | casper.functions.withdraw(logged_out_index).transact() 219 | balance = w3.eth.getBalance(logged_out_addr) 220 | assert balance > prev_balance 221 | assert balance - prev_balance == withdrawal_amount 222 | 223 | assert_validator_empty(concise_casper, logged_out_index) 224 | -------------------------------------------------------------------------------- /tests/test_logs.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from web3 import Web3 4 | 5 | 6 | def test_epoch_insta_finalize_logs(tester, 7 | concise_casper, 8 | casper_epoch_filter, 9 | new_epoch): 10 | start_epoch = concise_casper.START_EPOCH() 11 | new_epoch() 12 | new_epoch() 13 | logs = casper_epoch_filter.get_new_entries() 14 | assert len(logs) == 4 15 | log_old = logs[-2]['args'] 16 | log_new = logs[-1]['args'] 17 | 18 | log_fields = { 19 | '_number', 20 | '_checkpoint_hash', 21 | '_is_justified', 22 | '_is_finalized' 23 | } 24 | assert log_fields == log_old.keys() 25 | 26 | # New epoch log 27 | assert log_new['_number'] == start_epoch + 2 28 | init_block_number = tester.get_block_by_number('latest')['number'] - 1 29 | # block before epoch init == checkpoint hash 30 | assert Web3.toHex(log_new['_checkpoint_hash']) == \ 31 | tester.get_block_by_number(init_block_number - 1)['hash'] 32 | assert log_new['_is_justified'] is False 33 | assert log_new['_is_finalized'] is False 34 | 35 | # Insta-finalized previous epoch 36 | assert log_old['_number'] == start_epoch + 1 37 | # block before previous epoch init == checkpoint hash 38 | prev_epoch_block_number = init_block_number - concise_casper.EPOCH_LENGTH() 39 | assert Web3.toHex(log_old['_checkpoint_hash']) == \ 40 | tester.get_block_by_number(prev_epoch_block_number - 1)['hash'] 41 | assert log_old['_is_justified'] is True 42 | assert log_old['_is_finalized'] is True 43 | 44 | 45 | def test_epoch_with_validator_logs(tester, 46 | casper, 47 | concise_casper, 48 | casper_epoch_filter, 49 | new_epoch, 50 | induct_validator, 51 | funded_account, 52 | validation_key, 53 | deposit_amount, 54 | send_vote, 55 | mk_suggested_vote): 56 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 57 | 58 | last_block_number = tester.get_block_by_number('latest')['number'] - 1 59 | 60 | send_vote(mk_suggested_vote(validator_index, validation_key)) 61 | 62 | logs = casper_epoch_filter.get_new_entries() 63 | last_epoch_hash = tester.get_block_by_number(last_block_number - 1)['hash'] 64 | last_epoch_log = [ 65 | log for log in logs 66 | if Web3.toHex(log['args']['_checkpoint_hash']) == last_epoch_hash 67 | ][-1]['args'] 68 | assert last_epoch_log['_is_justified'] is True 69 | assert last_epoch_log['_is_finalized'] is False 70 | 71 | new_epoch() 72 | last_block_number = tester.get_block_by_number('latest')['number'] - 1 73 | send_vote(mk_suggested_vote(validator_index, validation_key)) 74 | 75 | logs = casper_epoch_filter.get_new_entries() 76 | last_epoch_hash = tester.get_block_by_number(last_block_number - 1)['hash'] 77 | last_epoch_log = [ 78 | log for log in logs 79 | if Web3.toHex(log['args']['_checkpoint_hash']) == last_epoch_hash 80 | ][-1]['args'] 81 | prev_epoch_hash = tester.get_block_by_number( 82 | last_block_number - concise_casper.EPOCH_LENGTH() - 1 83 | )['hash'] 84 | prev_epoch_log = [ 85 | log for log in logs 86 | if Web3.toHex(log['args']['_checkpoint_hash']) == prev_epoch_hash 87 | ][-1]['args'] 88 | 89 | assert prev_epoch_log['_is_justified'] is True 90 | assert prev_epoch_log['_is_finalized'] is True 91 | 92 | assert last_epoch_log['_is_justified'] is True 93 | assert last_epoch_log['_is_finalized'] is False 94 | 95 | 96 | def test_deposit_log(concise_casper, 97 | casper_deposit_filter, 98 | funded_account, 99 | validation_key, 100 | new_epoch, 101 | deposit_validator, 102 | deposit_amount): 103 | start_epoch = concise_casper.START_EPOCH() 104 | new_epoch() 105 | assert concise_casper.current_epoch() == start_epoch + 1 106 | 107 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 108 | 109 | logs = casper_deposit_filter.get_new_entries() 110 | assert len(logs) == 1 111 | log = logs[-1]['args'] 112 | 113 | # Deposit log 114 | log_fields = { 115 | '_from', 116 | '_validation_address', 117 | '_validator_index', 118 | '_start_dyn', 119 | '_amount' 120 | } 121 | assert log_fields == log.keys() 122 | assert log['_from'] == funded_account 123 | assert log['_validation_address'] == concise_casper.validators__addr(validator_index) 124 | assert log['_validator_index'] == validator_index 125 | assert log['_start_dyn'] == concise_casper.validators__start_dynasty(validator_index) 126 | assert log['_amount'] == deposit_amount 127 | 128 | 129 | def test_vote_log(casper, 130 | concise_casper, 131 | casper_vote_filter, 132 | funded_account, 133 | validation_key, 134 | new_epoch, 135 | induct_validator, 136 | deposit_amount, 137 | send_vote, 138 | mk_suggested_vote): 139 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 140 | 141 | send_vote(mk_suggested_vote(validator_index, validation_key)) 142 | 143 | logs = casper_vote_filter.get_new_entries() 144 | assert len(logs) == 1 145 | log = logs[-1]['args'] 146 | 147 | log_fields = { 148 | '_from', '_validator_index', 149 | '_target_hash', '_target_epoch', '_source_epoch' 150 | } 151 | assert log_fields == log.keys() 152 | assert log['_from'] == funded_account 153 | assert log['_validator_index'] == validator_index 154 | assert log['_target_hash'] == concise_casper.recommended_target_hash() 155 | assert log['_target_epoch'] == concise_casper.recommended_source_epoch() + 1 156 | assert log['_source_epoch'] == concise_casper.recommended_source_epoch() 157 | 158 | 159 | def test_logout_log(casper, 160 | concise_casper, 161 | casper_logout_filter, 162 | funded_account, 163 | validation_key, 164 | new_epoch, 165 | induct_validator, 166 | deposit_amount, 167 | send_vote, 168 | mk_suggested_vote, 169 | logout_validator_via_signed_msg): 170 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 171 | 172 | send_vote(mk_suggested_vote(validator_index, validation_key)) 173 | 174 | logout_validator_via_signed_msg(validator_index, validation_key) 175 | 176 | logs = casper_logout_filter.get_new_entries() 177 | assert len(logs) == 1 178 | log = logs[-1]['args'] 179 | 180 | log_fields = { 181 | '_from', 182 | '_validator_index', 183 | '_end_dyn' 184 | } 185 | assert log_fields == log.keys() 186 | assert log['_from'] == funded_account 187 | assert log['_validator_index'] == validator_index 188 | assert log['_end_dyn'] == concise_casper.dynasty() + concise_casper.DYNASTY_LOGOUT_DELAY() 189 | 190 | 191 | def test_withdraw_log(w3, 192 | casper, 193 | concise_casper, 194 | casper_withdraw_filter, 195 | funded_account, 196 | validation_key, 197 | new_epoch, 198 | induct_validator, 199 | deposit_amount, 200 | send_vote, 201 | mk_suggested_vote, 202 | logout_validator_via_signed_msg): 203 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 204 | 205 | send_vote(mk_suggested_vote(validator_index, validation_key)) 206 | new_epoch() 207 | 208 | logout_validator_via_signed_msg(validator_index, validation_key) 209 | # Logout delay 210 | for _ in range(concise_casper.DYNASTY_LOGOUT_DELAY() + 1): 211 | send_vote(mk_suggested_vote(validator_index, validation_key)) 212 | new_epoch() 213 | 214 | # In the next dynasty after end_dynasty 215 | assert concise_casper.validators__end_dynasty(validator_index) + 1 == concise_casper.dynasty() 216 | 217 | # Withdrawal delay 218 | for _ in range(concise_casper.WITHDRAWAL_DELAY()): 219 | new_epoch() 220 | 221 | current_epoch = concise_casper.current_epoch() 222 | end_dynasty = concise_casper.validators__end_dynasty(validator_index) 223 | end_epoch = concise_casper.dynasty_start_epoch(end_dynasty + 1) 224 | 225 | # Allowed to withdraw 226 | assert current_epoch == end_epoch + concise_casper.WITHDRAWAL_DELAY() 227 | 228 | expected_amount = concise_casper.deposit_size(validator_index) 229 | 230 | prev_balance = w3.eth.getBalance(funded_account) 231 | casper.functions.withdraw(validator_index).transact() 232 | balance = w3.eth.getBalance(funded_account) 233 | assert balance > prev_balance 234 | 235 | # Withdrawal log 236 | logs = casper_withdraw_filter.get_new_entries() 237 | assert len(logs) == 1 238 | log = logs[-1]['args'] 239 | 240 | log_fields = { 241 | '_to', 242 | '_validator_index', 243 | '_amount' 244 | } 245 | assert log_fields == log.keys() 246 | assert log['_to'] == funded_account 247 | assert log['_validator_index'] == validator_index 248 | assert log['_amount'] == expected_amount 249 | 250 | 251 | def test_slash_log(casper, 252 | concise_casper, 253 | casper_slash_filter, 254 | funded_account, 255 | validation_key, 256 | new_epoch, 257 | induct_validator, 258 | deposit_amount, 259 | mk_slash_votes, 260 | base_sender, 261 | logout_validator_via_signed_msg): 262 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 263 | 264 | vote_1, vote_2 = mk_slash_votes(validator_index, validation_key) 265 | 266 | assert concise_casper.dynasty_wei_delta(concise_casper.dynasty() + 1) == 0 267 | 268 | # Save deposit before slashing 269 | validator_deposit = concise_casper.deposit_size(validator_index) 270 | casper.functions.slash(vote_1, vote_2).transact({'from': base_sender}) 271 | 272 | # Slashed! 273 | assert concise_casper.validators__is_slashed(validator_index) 274 | 275 | # Slash log 276 | logs = casper_slash_filter.get_new_entries() 277 | assert len(logs) == 1 278 | log = logs[-1]['args'] 279 | 280 | log_fields = { 281 | '_from', 282 | '_offender', 283 | '_offender_index', 284 | '_bounty', 285 | } 286 | assert log_fields == log.keys() 287 | assert log['_from'] == base_sender 288 | assert log['_offender'] == funded_account 289 | assert log['_offender_index'] == validator_index 290 | assert log['_bounty'] == math.floor(validator_deposit / 25) 291 | -------------------------------------------------------------------------------- /tests/test_multiple_validators.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def test_deposits(concise_casper, 4 | funded_accounts, 5 | validation_keys, 6 | deposit_amount, 7 | new_epoch, 8 | induct_validators): 9 | induct_validators( 10 | funded_accounts, 11 | validation_keys, 12 | [deposit_amount] * len(funded_accounts) 13 | ) 14 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount * len(funded_accounts) 15 | assert concise_casper.total_prevdyn_deposits_in_wei() == 0 16 | 17 | 18 | def test_deposits_on_staggered_dynasties(casper, 19 | concise_casper, 20 | funded_accounts, 21 | validation_keys, 22 | deposit_amount, 23 | new_epoch, 24 | induct_validator, 25 | deposit_validator, 26 | send_vote, 27 | mk_suggested_vote): 28 | initial_validator = induct_validator(funded_accounts[0], validation_keys[0], deposit_amount) 29 | 30 | # finalize some epochs with just the one validator 31 | for i in range(3): 32 | send_vote(mk_suggested_vote(initial_validator, validation_keys[0])) 33 | new_epoch() 34 | 35 | # induct more validators 36 | for account, key in zip(funded_accounts[1:], validation_keys[1:]): 37 | deposit_validator(account, key, deposit_amount) 38 | 39 | assert concise_casper.deposit_size(initial_validator) == \ 40 | concise_casper.total_curdyn_deposits_in_wei() 41 | 42 | send_vote(mk_suggested_vote(initial_validator, validation_keys[0])) 43 | new_epoch() 44 | assert concise_casper.deposit_size(initial_validator) == \ 45 | concise_casper.total_curdyn_deposits_in_wei() 46 | 47 | send_vote(mk_suggested_vote(initial_validator, validation_keys[0])) 48 | new_epoch() 49 | assert concise_casper.deposit_size(initial_validator) == \ 50 | concise_casper.total_prevdyn_deposits_in_wei() 51 | 52 | 53 | def test_justification_and_finalization(casper, 54 | concise_casper, 55 | funded_accounts, 56 | validation_keys, 57 | deposit_amount, 58 | new_epoch, 59 | induct_validators, 60 | send_vote, 61 | mk_suggested_vote): 62 | validator_indexes = induct_validators( 63 | funded_accounts, 64 | validation_keys, 65 | [deposit_amount] * len(funded_accounts) 66 | ) 67 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount * len(funded_accounts) 68 | 69 | prev_dynasty = concise_casper.dynasty() 70 | for _ in range(10): 71 | for key, validator_index in zip(validation_keys, validator_indexes): 72 | send_vote(mk_suggested_vote(validator_index, key)) 73 | assert concise_casper.main_hash_justified() 74 | assert concise_casper.checkpoints__is_finalized(concise_casper.recommended_source_epoch()) 75 | new_epoch() 76 | assert concise_casper.dynasty() == prev_dynasty + 1 77 | prev_dynasty += 1 78 | 79 | 80 | def test_voters_make_more(casper, 81 | concise_casper, 82 | funded_accounts, 83 | validation_keys, 84 | deposit_amount, 85 | new_epoch, 86 | induct_validators, 87 | send_vote, 88 | mk_suggested_vote): 89 | validator_indexes = induct_validators( 90 | funded_accounts, 91 | validation_keys, 92 | [deposit_amount] * len(funded_accounts) 93 | ) 94 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount * len(funded_accounts) 95 | 96 | nonvoting_index = validator_indexes[0] 97 | voting_indexes = validator_indexes[1:] 98 | voting_keys = validation_keys[1:] 99 | 100 | prev_dynasty = concise_casper.dynasty() 101 | for _ in range(10): 102 | for key, validator_index in zip(voting_keys, voting_indexes): 103 | send_vote(mk_suggested_vote(validator_index, key)) 104 | assert concise_casper.main_hash_justified() 105 | assert concise_casper.checkpoints__is_finalized(concise_casper.recommended_source_epoch()) 106 | new_epoch() 107 | assert concise_casper.dynasty() == prev_dynasty + 1 108 | prev_dynasty += 1 109 | 110 | voting_deposits = list(map(concise_casper.deposit_size, voting_indexes)) 111 | nonvoting_deposit = concise_casper.deposit_size(nonvoting_index) 112 | assert len(set(voting_deposits)) == 1 113 | assert voting_deposits[0] > nonvoting_deposit 114 | 115 | 116 | def test_partial_online(casper, 117 | concise_casper, 118 | funded_accounts, 119 | validation_keys, 120 | deposit_amount, 121 | new_epoch, 122 | induct_validators, 123 | send_vote, 124 | mk_suggested_vote): 125 | validator_indexes = induct_validators( 126 | funded_accounts, 127 | validation_keys, 128 | [deposit_amount] * len(funded_accounts) 129 | ) 130 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount * len(funded_accounts) 131 | 132 | half_index = int(len(validator_indexes) / 2) 133 | online_indexes = validator_indexes[0:half_index] 134 | online_keys = validation_keys[0:half_index] 135 | offline_indexes = validator_indexes[half_index:-1] 136 | 137 | total_online_deposits = sum(map(concise_casper.deposit_size, online_indexes)) 138 | prev_ovp = total_online_deposits / concise_casper.total_curdyn_deposits_in_wei() 139 | 140 | for i in range(100): 141 | for key, validator_index in zip(online_keys, online_indexes): 142 | send_vote(mk_suggested_vote(validator_index, key)) 143 | 144 | total_online_deposits = sum(map(concise_casper.deposit_size, online_indexes)) 145 | ovp = total_online_deposits / concise_casper.total_curdyn_deposits_in_wei() 146 | 147 | # after two non-finalized epochs, offline voters should start losing more 148 | if i >= 2: 149 | assert ovp > prev_ovp 150 | 151 | if ovp >= 0.75: 152 | assert concise_casper.main_hash_justified() 153 | assert concise_casper.checkpoints__is_finalized( 154 | concise_casper.recommended_source_epoch() 155 | ) 156 | break 157 | 158 | new_epoch() 159 | prev_ovp = ovp 160 | 161 | online_validator_deposit_size = sum(map(concise_casper.deposit_size, online_indexes)) 162 | offline_validator_deposit_size = sum(map(concise_casper.deposit_size, offline_indexes)) 163 | assert online_validator_deposit_size > offline_validator_deposit_size 164 | -------------------------------------------------------------------------------- /tests/test_purity_checker.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from eth_tester.exceptions import TransactionFailed 4 | 5 | from vyper import compiler, compile_lll, optimizer 6 | from vyper.parser.parser import LLLnode 7 | 8 | def lll_to_evm(lll): 9 | return compile_lll.assembly_to_evm(compile_lll.compile_to_assembly(optimizer.optimize(lll))) 10 | 11 | # when submitted to purity checker, these should return True 12 | success_cases = [ 13 | """ 14 | @public 15 | def foo(x:int128) -> int128: 16 | return x * 2 17 | """, 18 | """ 19 | @public 20 | def phooey(h:bytes32, v:uint256, r:uint256, s:uint256) -> address: 21 | return ecrecover(h, v, r, s) 22 | """ 23 | ] 24 | 25 | # when submitted to purity checker, these should return False 26 | failed_cases = [ 27 | """ 28 | horse: int128 29 | 30 | @public 31 | def foo() -> int128: 32 | return self.horse 33 | """, 34 | """ 35 | @public 36 | def foo() -> uint256: 37 | return block.number 38 | """, 39 | """ 40 | @public 41 | def foo() -> uint256: 42 | return msg.gas 43 | """] 44 | 45 | ecrecover_lll_src = LLLnode.from_list([ 46 | 'seq', 47 | ['return', [0], 48 | ['lll', 49 | ['seq', 50 | ['calldatacopy', 0, 0, 128], 51 | ['call', 3000, 1, 0, 0, 128, 0, 32], 52 | ['mstore', 53 | 0, 54 | ['eq', 55 | ['mload', 0], 56 | 0]], 57 | ['return', 0, 32]], 58 | [0]]]]) 59 | 60 | def preapproved_call_to(addr): 61 | return LLLnode.from_list([ 62 | 'seq', 63 | ['return', [0], 64 | ['lll', 65 | ['seq', 66 | ['calldatacopy', 0, 0, 128], 67 | ['call', 3000, int(addr, 16), 0, 0, 128, 0, 32], 68 | ['return', 0, 32]], 69 | [0]]]]) 70 | 71 | def deploy_contract(w3, acct, data): 72 | txn_hash = w3.eth.sendTransaction({ 73 | 'from': acct, 74 | 'gas': 400000, 75 | 'data': data 76 | }) 77 | txn_receipt = w3.eth.getTransactionReceipt(txn_hash) 78 | return txn_receipt.contractAddress 79 | 80 | def deploy_minimal_contract(w3, acct, should_fail=False): 81 | op = '01' 82 | if should_fail: 83 | op = '42' 84 | # preamble postamble for code load 85 | return deploy_contract(w3, acct, '61000956' + '60026001' + op + '5b61000461000903610004600039610004610009036000f3') 86 | 87 | def run_test_for(purity_checker, addr, results, submit, should_fail): 88 | initial_check = purity_checker.functions.check(addr).call() 89 | 90 | assert results[0] == initial_check 91 | 92 | if submit: 93 | if should_fail: 94 | with pytest.raises(TransactionFailed): 95 | purity_checker.functions.submit(addr).call() 96 | else: 97 | purity_checker.functions.submit(addr).transact() 98 | 99 | next_check = purity_checker.functions.check(addr).call() 100 | 101 | assert results[1] == next_check 102 | 103 | def run_test(purity_checker, case): 104 | addr = case['addr'] 105 | results = case['results'] 106 | submit = case.get('submit', True) 107 | should_fail = case.get('should_fail', False) 108 | 109 | run_test_for(purity_checker, addr, results, submit, should_fail) 110 | 111 | ## NOTE: there is some dependency ordering with pytest fixtures; `casper` is not used here 112 | def test_purity_checker(casper, 113 | w3, 114 | funded_accounts, 115 | purity_checker): 116 | acct = funded_accounts[0] 117 | external_acct = funded_accounts[1] 118 | non_submitted_external_acct = funded_accounts[2] 119 | 120 | # setup to call an already approved address 121 | some_pure_contract = deploy_minimal_contract(w3, acct) 122 | purity_checker.functions.submit(some_pure_contract).transact() 123 | 124 | cases = [ 125 | { 126 | 'addr': external_acct, 127 | 'results': [False, False], 128 | 'should_fail': True, 129 | 'name': 'external_contract' 130 | }, 131 | { 132 | 'addr': non_submitted_external_acct, 133 | 'submit': False, 134 | 'results': [False, False], 135 | 'name': 'non_submitted_external_contract' 136 | }, 137 | { 138 | 'addr': deploy_minimal_contract(w3, acct), 139 | 'results': [False, True], 140 | 'name': 'minimal_contract' 141 | }, 142 | { 143 | 'addr': deploy_minimal_contract(w3, acct, should_fail=True), 144 | 'results': [False, False], 145 | 'should_fail': True, 146 | 'name': 'minimal_contract' 147 | }, 148 | { 149 | 'addr': deploy_contract(w3, acct, lll_to_evm(ecrecover_lll_src).hex()), 150 | 'results': [False, True], 151 | 'name': 'pure_ecrecover_contract' 152 | }, 153 | { 154 | 'addr': deploy_contract(w3, acct, lll_to_evm(preapproved_call_to(some_pure_contract)).hex()), 155 | 'results': [False, True], 156 | 'name': 'calling_preapproved_contract' 157 | }, 158 | { 159 | 'addr': deploy_contract(w3, acct, lll_to_evm(preapproved_call_to(external_acct)).hex()), 160 | 'results': [False, False], 161 | 'should_fail': True, 162 | 'name': 'calling_unapproved_contract' 163 | }, 164 | { 165 | 'addr': deploy_contract(w3, acct, compiler.compile(success_cases[0]).hex()), 166 | 'results': [False, True], 167 | 'name': success_cases[0] 168 | }, 169 | { 170 | 'addr': deploy_contract(w3, acct, compiler.compile(success_cases[1]).hex()), 171 | 'results': [False, True], 172 | 'name': success_cases[0] 173 | }, 174 | { 175 | 'addr': deploy_contract(w3, acct, compiler.compile(failed_cases[0]).hex()), 176 | 'results': [False, False], 177 | 'should_fail': True, 178 | 'name': failed_cases[0] 179 | }, 180 | { 181 | 'addr': deploy_contract(w3, acct, compiler.compile(failed_cases[1]).hex()), 182 | 'results': [False, False], 183 | 'should_fail': True, 184 | 'name': failed_cases[1] 185 | }, 186 | { 187 | 'addr': deploy_contract(w3, acct, compiler.compile(failed_cases[2]).hex()), 188 | 'results': [False, False], 189 | 'should_fail': True, 190 | 'name': failed_cases[2] 191 | } 192 | ] 193 | 194 | for case in cases: 195 | run_test(purity_checker, case) 196 | -------------------------------------------------------------------------------- /tests/test_slashing.py: -------------------------------------------------------------------------------- 1 | from utils.common_assertions import ( 2 | assert_validator_empty, 3 | ) 4 | from utils.utils import encode_int32 5 | 6 | 7 | def test_invalid_signature_fails(casper, 8 | concise_casper, 9 | funded_account, 10 | validation_key, 11 | deposit_amount, 12 | induct_validator, 13 | mk_vote, 14 | fake_hash, 15 | assert_tx_failed): 16 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 17 | 18 | # construct double votes but one has an invalid signature 19 | valid_signed_vote = mk_vote( 20 | validator_index, 21 | concise_casper.recommended_target_hash(), 22 | concise_casper.current_epoch(), 23 | concise_casper.recommended_source_epoch(), 24 | validation_key 25 | ) 26 | invalid_signed_vote = mk_vote( 27 | validator_index, 28 | fake_hash, 29 | concise_casper.current_epoch(), 30 | concise_casper.recommended_source_epoch(), 31 | encode_int32(42) # not the validators key 32 | ) 33 | 34 | assert not concise_casper.slashable(valid_signed_vote, invalid_signed_vote) 35 | assert_tx_failed( 36 | lambda: casper.functions.slash(valid_signed_vote, invalid_signed_vote).transact() 37 | ) 38 | 39 | # flip the order of arguments 40 | assert not concise_casper.slashable(invalid_signed_vote, valid_signed_vote) 41 | assert_tx_failed( 42 | lambda: casper.functions.slash(invalid_signed_vote, valid_signed_vote).transact() 43 | ) 44 | 45 | 46 | def test_different_validators_fails(casper, 47 | concise_casper, 48 | funded_accounts, 49 | validation_keys, 50 | deposit_amount, 51 | induct_validators, 52 | mk_vote, 53 | fake_hash, 54 | assert_tx_failed): 55 | validator_indexes = induct_validators( 56 | funded_accounts, 57 | validation_keys, 58 | [deposit_amount] * len(funded_accounts) 59 | ) 60 | validator_index_1 = validator_indexes[0] 61 | key_1 = validation_keys[0] 62 | validator_index_2 = validator_indexes[1] 63 | key_2 = validation_keys[1] 64 | 65 | # construct conflicting vote from different validators 66 | valid_signed_vote = mk_vote( 67 | validator_index_1, 68 | concise_casper.recommended_target_hash(), 69 | concise_casper.current_epoch(), 70 | concise_casper.recommended_source_epoch(), 71 | key_1 72 | ) 73 | invalid_signed_vote = mk_vote( 74 | validator_index_2, 75 | fake_hash, 76 | concise_casper.current_epoch(), 77 | concise_casper.recommended_source_epoch(), 78 | key_2 # not the validators key 79 | ) 80 | 81 | assert not concise_casper.slashable(valid_signed_vote, invalid_signed_vote) 82 | assert_tx_failed( 83 | lambda: casper.functions.slash(valid_signed_vote, invalid_signed_vote).transact() 84 | ) 85 | 86 | # flip the order of arguments 87 | assert not concise_casper.slashable(invalid_signed_vote, valid_signed_vote) 88 | assert_tx_failed( 89 | lambda: casper.functions.slash(invalid_signed_vote, valid_signed_vote).transact() 90 | ) 91 | 92 | 93 | def test_same_msg_fails(casper, 94 | concise_casper, 95 | funded_account, 96 | validation_key, 97 | deposit_amount, 98 | induct_validator, 99 | mk_vote, 100 | assert_tx_failed): 101 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 102 | 103 | vote = mk_vote( 104 | validator_index, 105 | concise_casper.recommended_target_hash(), 106 | concise_casper.current_epoch(), 107 | concise_casper.recommended_source_epoch(), 108 | validation_key 109 | ) 110 | 111 | assert not concise_casper.slashable(vote, vote) 112 | assert_tx_failed(lambda: casper.functions.slash(vote, vote).transact()) 113 | 114 | 115 | def test_double_slash_fails(casper, 116 | concise_casper, 117 | funded_account, 118 | validation_key, 119 | deposit_amount, 120 | induct_validator, 121 | mk_slash_votes, 122 | assert_tx_failed): 123 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 124 | 125 | vote_1, vote_2 = mk_slash_votes(validator_index, validation_key) 126 | 127 | assert concise_casper.slashable(vote_1, vote_2) 128 | casper.functions.slash(vote_1, vote_2).transact() 129 | 130 | assert not concise_casper.slashable(vote_1, vote_2) 131 | assert_tx_failed( 132 | lambda: casper.functions.slash(vote_1, vote_2).transact() 133 | ) 134 | 135 | 136 | def test_slash_before_start_dynasty_fails(casper, 137 | concise_casper, 138 | funded_account, 139 | validation_key, 140 | deposit_amount, 141 | deposit_validator, 142 | mk_slash_votes, 143 | new_epoch, 144 | assert_tx_failed): 145 | new_epoch() 146 | validator_index = deposit_validator(funded_account, validation_key, deposit_amount) 147 | 148 | vote_1, vote_2 = mk_slash_votes(validator_index, validation_key) 149 | 150 | assert concise_casper.validators__start_dynasty(validator_index) > concise_casper.dynasty() 151 | 152 | assert not concise_casper.slashable(vote_1, vote_2) 153 | assert_tx_failed( 154 | lambda: casper.functions.slash(vote_1, vote_2).transact() 155 | ) 156 | 157 | 158 | def test_slash_no_dbl_prepare(casper, 159 | concise_casper, 160 | funded_account, 161 | validation_key, 162 | deposit_amount, 163 | induct_validator, 164 | mk_vote, 165 | fake_hash): 166 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 167 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 168 | 169 | vote_1 = mk_vote( 170 | validator_index, 171 | concise_casper.recommended_target_hash(), 172 | concise_casper.current_epoch(), 173 | concise_casper.recommended_source_epoch(), 174 | validation_key 175 | ) 176 | vote_2 = mk_vote( 177 | validator_index, 178 | fake_hash, 179 | concise_casper.current_epoch(), 180 | concise_casper.recommended_source_epoch(), 181 | validation_key 182 | ) 183 | 184 | next_dynasty = concise_casper.dynasty() + 1 185 | assert concise_casper.dynasty_wei_delta(concise_casper.dynasty() + 1) == 0 186 | 187 | assert concise_casper.slashable(vote_1, vote_2) 188 | casper.functions.slash(vote_1, vote_2).transact() 189 | 190 | assert concise_casper.total_slashed(concise_casper.current_epoch()) == deposit_amount 191 | assert concise_casper.dynasty_wei_delta(next_dynasty) == \ 192 | (-deposit_amount / concise_casper.deposit_scale_factor(concise_casper.current_epoch())) 193 | assert concise_casper.validators__is_slashed(validator_index) 194 | assert concise_casper.validators__end_dynasty(validator_index) == next_dynasty 195 | assert concise_casper.validators__total_deposits_at_logout(validator_index) == deposit_amount 196 | 197 | 198 | def test_slash_no_surround(casper, 199 | concise_casper, 200 | funded_account, 201 | validation_key, 202 | deposit_amount, 203 | new_epoch, 204 | induct_validator, 205 | mk_vote, 206 | fake_hash, 207 | assert_tx_failed): 208 | new_epoch() 209 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 210 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 211 | 212 | vote_1 = mk_vote( 213 | validator_index, 214 | concise_casper.recommended_target_hash(), 215 | concise_casper.current_epoch(), 216 | concise_casper.recommended_source_epoch() - 1, 217 | validation_key 218 | ) 219 | vote_2 = mk_vote( 220 | validator_index, 221 | fake_hash, 222 | concise_casper.current_epoch() - 1, 223 | concise_casper.recommended_source_epoch(), 224 | validation_key 225 | ) 226 | 227 | next_dynasty = concise_casper.dynasty() + 1 228 | assert concise_casper.dynasty_wei_delta(concise_casper.dynasty() + 1) == 0 229 | 230 | # ensure works both ways 231 | assert concise_casper.slashable(vote_1, vote_2) 232 | assert concise_casper.slashable(vote_2, vote_1) 233 | 234 | casper.functions.slash(vote_1, vote_2).transact() 235 | 236 | assert concise_casper.total_slashed(concise_casper.current_epoch()) == deposit_amount 237 | assert concise_casper.dynasty_wei_delta(next_dynasty) == \ 238 | (-deposit_amount / concise_casper.deposit_scale_factor(concise_casper.current_epoch())) 239 | assert concise_casper.validators__is_slashed(validator_index) 240 | assert concise_casper.validators__end_dynasty(validator_index) == next_dynasty 241 | assert concise_casper.validators__total_deposits_at_logout(validator_index) == deposit_amount 242 | 243 | 244 | def test_slash_after_logout_delay(casper, 245 | concise_casper, 246 | funded_account, 247 | validation_key, 248 | deposit_amount, 249 | induct_validator, 250 | send_vote, 251 | mk_suggested_vote, 252 | mk_slash_votes, 253 | new_epoch, 254 | fake_hash, 255 | logout_validator_via_signed_msg): 256 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 257 | scaled_deposit_size = concise_casper.validators__deposit(validator_index) 258 | 259 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 260 | 261 | logout_validator_via_signed_msg(validator_index, validation_key) 262 | end_dynasty = concise_casper.validators__end_dynasty(validator_index) 263 | assert concise_casper.validators__total_deposits_at_logout(validator_index) == deposit_amount 264 | 265 | assert concise_casper.dynasty_wei_delta(end_dynasty) == -scaled_deposit_size 266 | 267 | # step past validator's end_dynasty 268 | dynasty_logout_delay = concise_casper.DYNASTY_LOGOUT_DELAY() 269 | for _ in range(dynasty_logout_delay + 1): 270 | send_vote(mk_suggested_vote(validator_index, validation_key)) 271 | new_epoch() 272 | 273 | new_deposit_size = concise_casper.deposit_size(validator_index) 274 | new_scaled_deposit_size = concise_casper.validators__deposit(validator_index) 275 | # should have a bit more from rewards 276 | assert new_scaled_deposit_size > scaled_deposit_size 277 | 278 | end_dynasty = concise_casper.validators__end_dynasty(validator_index) 279 | assert concise_casper.dynasty() == end_dynasty + 1 280 | assert concise_casper.dynasty_wei_delta(concise_casper.dynasty() + 1) == 0 281 | 282 | vote_1, vote_2 = mk_slash_votes(validator_index, validation_key) 283 | assert concise_casper.slashable(vote_1, vote_2) 284 | casper.functions.slash(vote_1, vote_2).transact() 285 | 286 | assert concise_casper.total_slashed(concise_casper.current_epoch()) == new_deposit_size 287 | assert concise_casper.validators__is_slashed(validator_index) 288 | assert concise_casper.validators__end_dynasty(validator_index) == end_dynasty 289 | # unchanged 290 | assert concise_casper.validators__total_deposits_at_logout(validator_index) == deposit_amount 291 | 292 | # validator already out of current deposits. should not change dynasty_wei_delta 293 | assert concise_casper.dynasty_wei_delta(end_dynasty) == -new_scaled_deposit_size 294 | assert concise_casper.dynasty_wei_delta(concise_casper.dynasty() + 1) == 0 295 | 296 | 297 | def test_slash_after_logout_before_logout_delay(casper, 298 | concise_casper, 299 | funded_account, 300 | validation_key, 301 | deposit_amount, 302 | induct_validator, 303 | send_vote, 304 | mk_suggested_vote, 305 | mk_slash_votes, 306 | new_epoch, 307 | fake_hash, 308 | logout_validator_via_signed_msg): 309 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 310 | scaled_deposit_size = concise_casper.validators__deposit(validator_index) 311 | 312 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 313 | 314 | logout_validator_via_signed_msg(validator_index, validation_key) 315 | end_dynasty = concise_casper.validators__end_dynasty(validator_index) 316 | 317 | assert concise_casper.dynasty_wei_delta(end_dynasty) == -scaled_deposit_size 318 | 319 | # step forward but not up to end_dynasty 320 | send_vote(mk_suggested_vote(validator_index, validation_key)) 321 | new_epoch() 322 | 323 | new_deposit_size = concise_casper.deposit_size(validator_index) 324 | new_scaled_deposit_size = concise_casper.validators__deposit(validator_index) 325 | 326 | assert concise_casper.dynasty() < end_dynasty - 1 327 | assert concise_casper.dynasty_wei_delta(concise_casper.dynasty() + 1) == 0 328 | assert concise_casper.dynasty_wei_delta(end_dynasty) == -new_scaled_deposit_size 329 | 330 | vote_1, vote_2 = mk_slash_votes(validator_index, validation_key) 331 | assert concise_casper.slashable(vote_1, vote_2) 332 | casper.functions.slash(vote_1, vote_2).transact() 333 | 334 | assert concise_casper.total_slashed(concise_casper.current_epoch()) == new_deposit_size 335 | assert concise_casper.validators__is_slashed(validator_index) 336 | assert concise_casper.validators__end_dynasty(validator_index) == concise_casper.dynasty() + 1 337 | 338 | # remove deposit from next dynasty rather than end_dynasty 339 | assert concise_casper.dynasty_wei_delta(end_dynasty) == 0 340 | assert concise_casper.dynasty_wei_delta(concise_casper.dynasty() + 1) == \ 341 | -new_scaled_deposit_size 342 | # unchanged 343 | assert concise_casper.validators__total_deposits_at_logout(validator_index) == deposit_amount 344 | 345 | 346 | def test_total_slashed(casper, 347 | concise_casper, 348 | funded_account, 349 | validation_key, 350 | deposit_amount, 351 | new_epoch, 352 | induct_validator, 353 | send_vote, 354 | mk_suggested_vote, 355 | mk_slash_votes): 356 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 357 | 358 | vote_1, vote_2 = mk_slash_votes(validator_index, validation_key) 359 | casper.functions.slash(vote_1, vote_2).transact() 360 | 361 | current_epoch = concise_casper.current_epoch() 362 | assert concise_casper.total_slashed(current_epoch) == deposit_amount 363 | assert concise_casper.total_slashed(current_epoch + 1) == 0 364 | 365 | # step forwrd 366 | send_vote(mk_suggested_vote(validator_index, validation_key)) 367 | new_epoch() 368 | 369 | current_epoch = concise_casper.current_epoch() 370 | assert concise_casper.total_slashed(current_epoch - 1) == deposit_amount 371 | assert concise_casper.total_slashed(current_epoch) == deposit_amount 372 | 373 | 374 | def test_withdraw_after_slash(w3, 375 | casper, 376 | concise_casper, 377 | funded_accounts, 378 | validation_keys, 379 | deposit_amount, 380 | new_epoch, 381 | induct_validators, 382 | send_vote, 383 | mk_suggested_vote, 384 | mk_slash_votes): 385 | validator_indexes = induct_validators( 386 | funded_accounts, 387 | validation_keys, 388 | [deposit_amount] * len(funded_accounts) 389 | ) 390 | slashed_fraction_of_total_deposits = 1.0 / len(funded_accounts) 391 | 392 | # 0th gets slashed 393 | slashed_index = validator_indexes[0] 394 | slashed_key = validation_keys[0] 395 | slashed_addr = funded_accounts[0] 396 | # the rest remain 397 | logged_in_indexes = validator_indexes[1:] 398 | logged_in_keys = validation_keys[1:] 399 | 400 | vote_1, vote_2 = mk_slash_votes(slashed_index, slashed_key) 401 | casper.functions.slash(vote_1, vote_2).transact() 402 | 403 | current_epoch = concise_casper.current_epoch() 404 | assert concise_casper.total_slashed(current_epoch) == deposit_amount 405 | assert concise_casper.total_slashed(current_epoch + 1) == 0 406 | 407 | # slashed validator can withdraw after end_dynasty plus delay 408 | for _ in range(concise_casper.WITHDRAWAL_DELAY() + 2): 409 | for i, validator_index in enumerate(logged_in_indexes): 410 | send_vote(mk_suggested_vote(validator_index, logged_in_keys[i])) 411 | new_epoch() 412 | 413 | end_dynasty = concise_casper.validators__end_dynasty(slashed_index) 414 | assert concise_casper.dynasty() > end_dynasty 415 | end_epoch = concise_casper.dynasty_start_epoch(end_dynasty + 1) 416 | withdrawal_epoch = end_epoch + concise_casper.WITHDRAWAL_DELAY() 417 | assert concise_casper.current_epoch() == withdrawal_epoch 418 | 419 | prev_balance = w3.eth.getBalance(slashed_addr) 420 | casper.functions.withdraw(slashed_index).transact() 421 | balance = w3.eth.getBalance(slashed_addr) 422 | assert concise_casper.current_epoch() == end_epoch + concise_casper.WITHDRAWAL_DELAY() 423 | 424 | assert balance > prev_balance 425 | 426 | expected_slashed_fraction = slashed_fraction_of_total_deposits * 3 427 | expected_withdrawal_fraction = 1 - expected_slashed_fraction 428 | expected_withdrawal_amount = expected_withdrawal_fraction * deposit_amount 429 | withdrawal_amount = balance - prev_balance 430 | assert withdrawal_amount < deposit_amount 431 | # should be less than because of some loss due to inactivity during withdrawal period 432 | assert withdrawal_amount < expected_withdrawal_amount 433 | # ensure within proximity to expected_withdrawal_amount 434 | assert withdrawal_amount > expected_withdrawal_amount * 0.9 435 | 436 | assert_validator_empty(concise_casper, slashed_index) 437 | 438 | 439 | def test_withdraw_after_majority_slash(w3, 440 | casper, 441 | concise_casper, 442 | funded_accounts, 443 | validation_keys, 444 | deposit_amount, 445 | new_epoch, 446 | induct_validators, 447 | send_vote, 448 | mk_suggested_vote, 449 | mk_slash_votes): 450 | validator_indexes = induct_validators( 451 | funded_accounts, 452 | validation_keys, 453 | [deposit_amount] * len(funded_accounts) 454 | ) 455 | 456 | # 0th gets slashed 457 | slashed_indexes = validator_indexes[:-1] 458 | slashed_keys = validation_keys[:-1] 459 | slashed_addrs = funded_accounts[:-1] 460 | # the rest remain 461 | logged_in_index = validator_indexes[-1] 462 | logged_in_key = validation_keys[-1] 463 | 464 | assert len(slashed_indexes) / float(len(funded_accounts)) >= 1 / 3.0 465 | 466 | for slashed_index, slashed_key in zip(slashed_indexes, slashed_keys): 467 | vote_1, vote_2 = mk_slash_votes(slashed_index, slashed_key) 468 | casper.functions.slash(vote_1, vote_2).transact() 469 | 470 | current_epoch = concise_casper.current_epoch() 471 | assert concise_casper.total_slashed(current_epoch) == deposit_amount * len(slashed_indexes) 472 | assert concise_casper.total_slashed(current_epoch + 1) == 0 473 | 474 | # artificially simulate the slashed validators voting 475 | # normally if this occured, the validators would likely stop 476 | # voting and their deposits would have to bleed out. 477 | for i, validator_index in enumerate(validator_indexes): 478 | send_vote(mk_suggested_vote(validator_index, validation_keys[i])) 479 | new_epoch() 480 | 481 | # slashed validators can withdraw after end_dynasty plus delay 482 | for _ in range(concise_casper.WITHDRAWAL_DELAY() + 1): 483 | send_vote(mk_suggested_vote(logged_in_index, logged_in_key)) 484 | new_epoch() 485 | 486 | assert concise_casper.dynasty() > concise_casper.validators__end_dynasty(slashed_indexes[0]) 487 | 488 | prev_balances = [ 489 | w3.eth.getBalance(slashed_addr) 490 | for slashed_addr in slashed_addrs 491 | ] 492 | for slashed_index in slashed_indexes: 493 | casper.functions.withdraw(slashed_index).transact() 494 | 495 | for slashed_addr, prev_balance in zip(slashed_addrs, prev_balances): 496 | balance = w3.eth.getBalance(slashed_addr) 497 | assert balance == prev_balance 498 | 499 | for slashed_index in slashed_indexes: 500 | assert_validator_empty(concise_casper, slashed_index) 501 | -------------------------------------------------------------------------------- /tests/test_valcode_purity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from utils.valcodes import all_known_valcode_types 4 | 5 | 6 | def build_pass_fail_matrix(): 7 | matrix = [] 8 | pure_prefix = "pure_" 9 | impure_prefix = "impure_" 10 | for valcode_type in all_known_valcode_types(): 11 | if valcode_type.startswith(pure_prefix): 12 | matrix.append((valcode_type, True)) 13 | elif valcode_type.startswith(impure_prefix): 14 | matrix.append((valcode_type, False)) 15 | else: 16 | raise ValueError("Valcode keys should be prefixed with " 17 | "{} (pass) or {} (fail) to indicate " 18 | "as to if the test should pass or fail. " 19 | "Given: {}." 20 | .format(pure_prefix, impure_prefix, valcode_type)) 21 | return matrix 22 | 23 | 24 | @pytest.mark.parametrize( 25 | "valcode_type,should_succeed", 26 | build_pass_fail_matrix() 27 | ) 28 | def test_valcode_purity_checks(casper, 29 | funded_account, 30 | validation_key, 31 | assert_tx_failed, 32 | deposit_amount, 33 | deposit_validator, 34 | valcode_type, 35 | should_succeed, 36 | deploy_validation_contract): 37 | if should_succeed: 38 | deposit_validator( 39 | funded_account, 40 | validation_key, 41 | deposit_amount, 42 | valcode_type 43 | ) 44 | else: 45 | ''' 46 | Check to ensure the validation_addr can actually be deployed. 47 | 48 | This can help detect fails that are not due to the purity 49 | checker, instead are a result of a bug in the contract being 50 | tested. 51 | ''' 52 | deploy_validation_contract(funded_account, valcode_type) 53 | assert_tx_failed(lambda: deposit_validator( 54 | funded_account, 55 | validation_key, 56 | deposit_amount, 57 | valcode_type 58 | )) 59 | -------------------------------------------------------------------------------- /tests/test_voting.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize( 5 | 'amount, min_deposit_size, success', 6 | [ 7 | (2000 * 10**18, 2000 * 10**18, True), 8 | (1000 * 10**18, 100 * 10**18, True), 9 | (1500 * 10**18, 1499 * 10**18, True), 10 | (1, 1, True), 11 | 12 | # below min_deposit_size 13 | (999 * 10**18, 1000 * 10**18, False), 14 | (10 * 10**18, 1500 * 10**18, False), 15 | (0, 1, False), 16 | ] 17 | ) 18 | def test_deposit(casper, 19 | concise_casper, 20 | funded_account, 21 | validation_key, 22 | amount, 23 | success, 24 | deposit_validator, 25 | new_epoch, 26 | assert_tx_failed): 27 | start_epoch = concise_casper.START_EPOCH() 28 | new_epoch() 29 | assert concise_casper.current_epoch() == start_epoch + 1 30 | assert concise_casper.next_validator_index() == 1 31 | 32 | if not success: 33 | assert_tx_failed( 34 | lambda: deposit_validator(funded_account, validation_key, amount) 35 | ) 36 | return 37 | 38 | deposit_validator(funded_account, validation_key, amount) 39 | 40 | assert concise_casper.next_validator_index() == 2 41 | assert concise_casper.validator_indexes(funded_account) == 1 42 | assert concise_casper.deposit_size(1) == amount 43 | 44 | for i in range(2): 45 | new_epoch() 46 | 47 | assert concise_casper.dynasty() == 2 48 | assert concise_casper.total_curdyn_deposits_in_wei() == amount 49 | assert concise_casper.total_prevdyn_deposits_in_wei() == 0 50 | 51 | 52 | def test_vote_from_account(casper, 53 | concise_casper, 54 | funded_account, 55 | validation_key, 56 | deposit_amount, 57 | induct_validator, 58 | mk_suggested_vote, 59 | assert_tx_failed): 60 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 61 | assert_tx_failed( 62 | lambda: casper.functions.vote( 63 | mk_suggested_vote(validator_index, validation_key) 64 | ).transact({ 65 | 'from': funded_account 66 | }) 67 | ) 68 | 69 | 70 | def test_vote_from_null_sender(casper, 71 | concise_casper, 72 | funded_account, 73 | validation_key, 74 | deposit_amount, 75 | induct_validator, 76 | mk_suggested_vote, 77 | null_sender): 78 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 79 | casper.functions.vote( 80 | mk_suggested_vote(validator_index, validation_key) 81 | ).transact({ 82 | 'from': null_sender 83 | }) 84 | 85 | assert concise_casper.main_hash_justified() 86 | 87 | 88 | def test_vote_single_validator(casper, 89 | concise_casper, 90 | funded_account, 91 | validation_key, 92 | deposit_amount, 93 | new_epoch, 94 | induct_validator, 95 | mk_suggested_vote, 96 | send_vote): 97 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 98 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 99 | 100 | prev_dynasty = concise_casper.dynasty() 101 | for i in range(10): 102 | vote_msg = mk_suggested_vote(validator_index, validation_key) 103 | 104 | assert concise_casper.votable(vote_msg) 105 | assert concise_casper.validate_vote_signature(vote_msg) 106 | send_vote(mk_suggested_vote(validator_index, validation_key)) 107 | 108 | assert concise_casper.main_hash_justified() 109 | assert concise_casper.checkpoints__is_finalized(concise_casper.recommended_source_epoch()) 110 | 111 | new_epoch() 112 | assert concise_casper.dynasty() == prev_dynasty + 1 113 | prev_dynasty += 1 114 | 115 | 116 | def test_vote_target_epoch_twice(casper, 117 | concise_casper, 118 | funded_account, 119 | validation_key, 120 | deposit_amount, 121 | new_epoch, 122 | induct_validator, 123 | mk_suggested_vote, 124 | send_vote, 125 | assert_tx_failed): 126 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 127 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 128 | 129 | send_vote(mk_suggested_vote(validator_index, validation_key)) 130 | 131 | # second vote on same target epoch fails 132 | vote_msg = mk_suggested_vote(validator_index, validation_key) 133 | assert not concise_casper.votable(vote_msg) 134 | assert_tx_failed(lambda: send_vote(vote_msg)) 135 | 136 | 137 | @pytest.mark.parametrize( 138 | 'valcode_type,success', 139 | [ 140 | ('pure_greater_than_200k_gas', False), 141 | ('pure_between_100k-200k_gas', True), 142 | ] 143 | ) 144 | def test_vote_validate_signature_gas_limit(valcode_type, 145 | success, 146 | casper, 147 | concise_casper, 148 | funded_account, 149 | validation_key, 150 | deposit_amount, 151 | induct_validator, 152 | mk_suggested_vote, 153 | send_vote, 154 | assert_tx_failed): 155 | validator_index = induct_validator( 156 | funded_account, 157 | validation_key, 158 | deposit_amount, 159 | valcode_type 160 | ) 161 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 162 | 163 | vote_msg = mk_suggested_vote(validator_index, validation_key) 164 | 165 | if not success: 166 | assert_tx_failed( 167 | lambda: concise_casper.validate_vote_signature(vote_msg) 168 | ) 169 | assert_tx_failed( 170 | lambda: send_vote(mk_suggested_vote(validator_index, validation_key)) 171 | ) 172 | return 173 | assert concise_casper.validate_vote_signature(vote_msg) 174 | send_vote(vote_msg) 175 | 176 | 177 | def test_non_finalization_loss(casper, 178 | concise_casper, 179 | funded_account, 180 | validation_key, 181 | deposit_amount, 182 | new_epoch, 183 | induct_validator, 184 | mk_suggested_vote, 185 | send_vote, 186 | assert_tx_failed): 187 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 188 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 189 | 190 | send_vote(mk_suggested_vote(validator_index, validation_key)) 191 | new_epoch() 192 | 193 | send_vote(mk_suggested_vote(validator_index, validation_key)) 194 | new_epoch() 195 | 196 | ds_prev_non_finalized = concise_casper.deposit_size(validator_index) 197 | for i in range(5): 198 | new_epoch() 199 | ds_cur_non_finalized = concise_casper.deposit_size(validator_index) 200 | assert ds_cur_non_finalized < ds_prev_non_finalized 201 | ds_prev_non_finalized = ds_cur_non_finalized 202 | 203 | 204 | def test_mismatched_epoch_and_hash(casper, 205 | concise_casper, 206 | funded_account, 207 | validation_key, 208 | deposit_amount, 209 | induct_validator, 210 | mk_vote, 211 | new_epoch, 212 | send_vote, 213 | assert_tx_failed): 214 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 215 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 216 | 217 | # step forward one epoch to ensure that validator is allowed 218 | # to vote on (current_epoch - 1) 219 | new_epoch() 220 | 221 | target_hash = concise_casper.recommended_target_hash() 222 | mismatched_target_epoch = concise_casper.current_epoch() - 1 223 | source_epoch = concise_casper.recommended_source_epoch() 224 | 225 | mismatched_vote = mk_vote( 226 | validator_index, 227 | target_hash, 228 | mismatched_target_epoch, 229 | source_epoch, 230 | validation_key 231 | ) 232 | 233 | assert not concise_casper.votable(mismatched_vote) 234 | assert_tx_failed( 235 | lambda: send_vote(mismatched_vote) 236 | ) 237 | 238 | 239 | def test_consensus_after_non_finalization_streak(casper, 240 | concise_casper, 241 | funded_account, 242 | validation_key, 243 | deposit_amount, 244 | new_epoch, 245 | induct_validator, 246 | mk_suggested_vote, 247 | send_vote, 248 | assert_tx_failed): 249 | validator_index = induct_validator(funded_account, validation_key, deposit_amount) 250 | assert concise_casper.total_curdyn_deposits_in_wei() == deposit_amount 251 | 252 | # finalize an epoch as a base to the test 253 | send_vote(mk_suggested_vote(validator_index, validation_key)) 254 | new_epoch() 255 | 256 | send_vote(mk_suggested_vote(validator_index, validation_key)) 257 | new_epoch() 258 | 259 | # step forward 5 epochs without finalization 260 | for i in range(5): 261 | new_epoch() 262 | 263 | assert not concise_casper.main_hash_justified() 264 | assert not concise_casper.checkpoints__is_finalized(concise_casper.recommended_source_epoch()) 265 | 266 | send_vote(mk_suggested_vote(validator_index, validation_key)) 267 | assert concise_casper.main_hash_justified() 268 | assert not concise_casper.checkpoints__is_finalized(concise_casper.recommended_source_epoch()) 269 | 270 | new_epoch() 271 | send_vote(mk_suggested_vote(validator_index, validation_key)) 272 | 273 | assert concise_casper.main_hash_justified() 274 | assert concise_casper.checkpoints__is_finalized(concise_casper.recommended_source_epoch()) 275 | -------------------------------------------------------------------------------- /tests/utils/common_assertions.py: -------------------------------------------------------------------------------- 1 | def assert_validator_empty(casper, validator_index): 2 | assert casper.deposit_size(validator_index) == 0 3 | assert casper.validators__deposit(validator_index) == 0 4 | assert casper.validators__start_dynasty(validator_index) == 0 5 | assert casper.validators__end_dynasty(validator_index) == 0 6 | assert not casper.validators__is_slashed(validator_index) 7 | assert casper.validators__total_deposits_at_logout(validator_index) == 0 8 | assert not casper.validators__addr(validator_index) 9 | assert not casper.validators__withdrawal_addr(validator_index) 10 | -------------------------------------------------------------------------------- /tests/utils/utils.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | 4 | def encode_int32(val): 5 | return Web3.toBytes(val).rjust(32, b'\x00') 6 | -------------------------------------------------------------------------------- /tests/utils/valcodes.py: -------------------------------------------------------------------------------- 1 | from vyper import optimizer, compile_lll 2 | from vyper.parser.parser_utils import LLLnode 3 | from web3 import Web3 4 | 5 | 6 | # We must know a pure opcode so we can do control tests 7 | PURE_OPCODE_HEX = 0x59 8 | PURE_EXPRESSION_LLL = ['msize'] 9 | 10 | # Expensive opcode for testing gas limits 11 | ECRECOVER_LLL = ['call', 3000, 1, 0, 0, 128, 0, 32] 12 | ECRECOVER_GASCOST = 3000 13 | 14 | 15 | def generate_pure_ecrecover_LLL_source(address): 16 | return [ 17 | 'seq', 18 | ['return', [0], 19 | ['lll', 20 | ['seq', 21 | ['calldatacopy', 0, 0, 128], 22 | ['call', 3000, 1, 0, 0, 128, 0, 32], 23 | ['mstore', 24 | 0, 25 | ['eq', 26 | ['mload', 0], 27 | Web3.toInt(hexstr=address)]], 28 | ['return', 0, 32]], 29 | [0]]] 30 | ] 31 | 32 | 33 | def format_LLL_source(address, expression): 34 | return [ 35 | 'seq', 36 | ['return', [0], 37 | ['lll', 38 | ['seq', 39 | expression, # impure_expression goes here 40 | ['calldatacopy', 0, 0, 128], 41 | ['call', 3000, 1, 0, 0, 128, 0, 32], 42 | ['mstore', 43 | 0, 44 | ['eq', 45 | ['mload', 0], 46 | Web3.toInt(hexstr=address)]], 47 | ['return', 0, 32]], 48 | [0]]] 49 | ] 50 | 51 | 52 | def generate_impure_opcodes_as_LLL_source(address): 53 | impure_expressions = [ 54 | ['balance', 1337], 55 | ['origin'], 56 | ['caller'], 57 | ['gasprice'], 58 | ['extcodesize', 1337], 59 | ['extcodecopy', 1337, 0, 0, 1], 60 | ['blockhash', 1337], 61 | ['coinbase'], 62 | ['timestamp'], 63 | ['number'], 64 | ['difficulty'], 65 | ['gaslimit'], 66 | ['sload', 0], 67 | ['sstore', 1, 1], 68 | ['create', 0, 0, 1], 69 | ['selfdestruct', 1337], 70 | ] 71 | valcodes = {} 72 | for expression in impure_expressions: 73 | key = "impure_{}".format(expression[0]) 74 | valcodes[key] = format_LLL_source(address, expression) 75 | return valcodes 76 | 77 | 78 | def format_ecrecover_bytecode(address, opcode): 79 | pure_ecrecover_bytecode = ( 80 | "61003f56{start:02x}5060806000600037602060006080600060006001610" 81 | "bb8f15073{address}6000511460005260206000f35b61000461003f036100" 82 | "0460003961000461003f036000f3" 83 | ) 84 | return bytes.fromhex( 85 | pure_ecrecover_bytecode.format( 86 | start=opcode, 87 | address=address[2:] 88 | ) 89 | ) 90 | 91 | 92 | def generate_unused_opcodes_as_evm_bytecode(address): 93 | unused_opcodes = [ 94 | 0x46, 95 | 0x47, 96 | 0x48, 97 | 0x49, 98 | 0x4a, 99 | 0x4b, 100 | 0x4c, 101 | 0x4d, 102 | 0x4e, 103 | 0x4f, 104 | ] 105 | valcodes = {} 106 | for opcode in unused_opcodes: 107 | key = "impure_unused_bytecode_{}".format("{:02x}".format(opcode)) 108 | valcodes[key] = format_ecrecover_bytecode(address, opcode) 109 | return valcodes 110 | 111 | 112 | def generate_all_valcodes(address): 113 | return { 114 | 'pure_ecrecover': generate_pure_ecrecover_LLL_source(address), 115 | 'pure_LLL_source_as_control': format_LLL_source( 116 | address, PURE_EXPRESSION_LLL), 117 | 'pure_bytecode_as_control': format_ecrecover_bytecode( 118 | address, PURE_OPCODE_HEX), 119 | 'pure_greater_than_200k_gas': format_LLL_source( 120 | address, ['seq'] + [ECRECOVER_LLL] * int(2e5 / ECRECOVER_GASCOST)), 121 | 'pure_between_100k-200k_gas': format_LLL_source( 122 | address, ['seq'] + [ECRECOVER_LLL] * int(1e5 / ECRECOVER_GASCOST)), 123 | **generate_impure_opcodes_as_LLL_source(address), 124 | **generate_unused_opcodes_as_evm_bytecode(address), 125 | } 126 | 127 | 128 | def all_known_valcode_types(): 129 | return generate_all_valcodes('0x00').keys() 130 | 131 | 132 | def compile_valcode_to_evm_bytecode(valcode_type, address): 133 | valcodes = generate_all_valcodes(address) 134 | valcode = valcodes[valcode_type] 135 | # We assume bytes are compiled evm code 136 | if type(valcode) is bytes: 137 | return valcode 138 | # We assume lists are uncompiled LLL seqs 139 | elif type(valcode) is list: 140 | lll_node = LLLnode.from_list(valcode) 141 | optimized = optimizer.optimize(lll_node) 142 | assembly = compile_lll.compile_to_assembly(optimized) 143 | evm = compile_lll.assembly_to_evm(assembly) 144 | return evm 145 | # Any other types are unacceptable 146 | else: 147 | raise ValueError('Valcode must be of types list (uncompiled LLL), or ' 148 | 'bytes (compiled bytecode). Given: ' 149 | '{}'.format(type(valcode))) 150 | --------------------------------------------------------------------------------