def test_add_tx_checks_batching_period_start( batcher_contract: Any, config_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number, chain) batcher_contract.addTransaction(0, 0, b"\x00") batcher_contract.addTransaction(1, 0, b"\x00") with brownie.reverts("BatcherContract: batch not open yet"): batcher_contract.addTransaction(2, 0, b"\x00") mine_until(config.start_block_number + config.batch_span, chain) batcher_contract.addTransaction(1, 0, b"\x00") batcher_contract.addTransaction(2, 0, b"\x00") with brownie.reverts("BatcherContract: batch not open yet"): batcher_contract.addTransaction(3, 0, b"\x00")
def execute_cipher_batch( *, config_contract: Any, batcher_contract: Any, executor_contract: Any, chain: Chain, owner: Account, keypers: Accounts, config_change_heads_up_blocks: int, threshold: int, batch_size: int, tx_size: int, ) -> TransactionReceipt: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=100, keypers=keypers, threshold=1, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain) batch = [make_bytes(tx_size) for _ in range(batch_size)] for tx in batch: batcher_contract.addTransaction(0, 0, tx) cipher_batch_hash = compute_batch_hash(batch) mine_until(config.start_block_number + config.batch_span, chain) decrypted_transactions = [make_bytes(tx_size) for _ in range(batch_size)] return executor_contract.executeCipherBatch(0, cipher_batch_hash, decrypted_transactions, 0, {"from": keypers[0]})
def test_check_and_increment_half_steps( executor_contract: Any, config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number + config.batch_span * 10, chain) for batch_index in range(3): assert executor_contract.numExecutionHalfSteps() == 2 * batch_index with brownie.reverts("ExecutorContract: unexpected half step"): executor_contract.executePlainBatch(batch_index, []) executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[0]}) assert executor_contract.numExecutionHalfSteps() == 2 * batch_index + 1 with brownie.reverts("ExecutorContract: unexpected half step"): executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[0]}) executor_contract.executePlainBatch(batch_index, []) assert executor_contract.numExecutionHalfSteps() == 2 * (batch_index + 1)
def test_add_tx_emits_event( batcher_contract: Any, config_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain) tx = batcher_contract.addTransaction(0, 0, b"\x11") assert len(tx.events) == 1 assert tx.events[0] == { "batchIndex": 0, "transactionType": 0, "transaction": "0x11", "batchHash": encode_hex(keccak(b"\x11" + b"\x00" * 32)), } mine_until(config.start_block_number + 2 * config.batch_span + 1, chain) tx = batcher_contract.addTransaction(2, 1, b"\x22") assert len(tx.events) == 1 assert tx.events[0] == { "batchIndex": 2, "transactionType": 1, "transaction": "0x22", "batchHash": encode_hex(keccak(b"\x22" + b"\x00" * 32)), }
def test_withdraw( deposit_contract: Any, deposit_token_contract: Any, owner: Account, accounts: Accounts, chain: Chain, web3: Web3, ) -> None: withdrawal_delay = 10 withdrawal_delay_data = encode_data(withdrawal_delay) deposit_token_contract.send(deposit_contract, 100, withdrawal_delay_data) recipient = accounts[-1] chain.mine(withdrawal_delay) with brownie.reverts(): deposit_contract.withdraw(recipient, {"from": owner}) tx = deposit_contract.requestWithdrawal({"from": owner}) check_deposit_changed_event(tx, 100, withdrawal_delay, tx.block_number) assert deposit_contract.getWithdrawalRequestedBlock( owner) == tx.block_number withdraw_block_number = tx.block_number + withdrawal_delay with brownie.reverts(): deposit_contract.withdraw(recipient, {"from": owner}) mine_until(withdraw_block_number - 2, chain) with brownie.reverts(): deposit_contract.withdraw(recipient, {"from": owner}) assert web3.eth.block_number == withdraw_block_number - 1 # one block mined by sending tx tx = deposit_contract.withdraw(recipient, {"from": owner}) check_deposit_changed_event(tx, 0, 0, 0, withdrawn=True) check_deposit(deposit_contract, owner, 0, 0, 0) assert deposit_token_contract.balanceOf(recipient) == 100
def test_skip_cipher_execution_checks_timeout( executor_contract: Any, config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, execution_timeout=20, ) schedule_config(config_contract, config, owner=owner) for batch_index in range(3): mine_until( config.start_block_number + (batch_index + 1) * config.batch_span + config.execution_timeout - 2, chain, ) with brownie.reverts( "ExecutorContract: execution timeout not reached yet"): executor_contract.skipCipherExecution(batch_index) executor_contract.skipCipherExecution(batch_index) executor_contract.executePlainBatch(batch_index, [])
def test_skip_cipher_execution( executor_contract: Any, config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, execution_timeout=20, ) schedule_config(config_contract, config, owner=owner) for batch_index in range(3): mine_until( config.start_block_number + (batch_index + 1) * config.batch_span + config.execution_timeout - 1, chain, ) tx = executor_contract.skipCipherExecution(batch_index) assert executor_contract.numExecutionHalfSteps() == batch_index * 2 + 1 assert len(tx.events) == 1 assert tx.events["CipherExecutionSkipped"][0] == { "numExecutionHalfSteps": batch_index * 2 + 1 } executor_contract.executePlainBatch(batch_index, [])
def test_skip_cipher_execution_checks_half_step( executor_contract: Any, config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, execution_timeout=20, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) for batch_index in range(3): mine_until( config.start_block_number + batch_index * config.batch_span + config.execution_timeout - 1, chain, ) executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[0]}) with brownie.reverts("ExecutorContract: unexpected half step"): executor_contract.skipCipherExecution(batch_index) executor_contract.executePlainBatch(batch_index, [])
def test_cipher_execution_stores_receipt( executor_contract: Any, config_contract: Any, mock_batcher_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number + config.batch_span, chain) executor_contract.executeCipherBatch(0, ZERO_HASH32, [], 0, {"from": keypers[0]}) receipt = executor_contract.cipherExecutionReceipts(0) assert receipt[0] is True # executed assert receipt[1] == keypers[0] # executor assert receipt[2] == 0 # half step assert bytes(receipt[3]) == ZERO_HASH32 # batch hash
def test_check_keyper( executor_contract: Any, config_contract: Any, mock_batcher_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, non_owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number + config.batch_span, chain) batch_index = 0 with brownie.reverts("ExecutorContract: sender is not specified keyper"): executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 1, {"from": keypers[0]}) with brownie.reverts("ExecutorContract: sender is not specified keyper"): executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[1]}) with brownie.reverts("ExecutorContract: sender is not specified keyper"): executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": non_owner}) executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 1, {"from": keypers[1]})
def test_check_cipher_batch_hash( executor_contract: Any, config_contract: Any, mock_batcher_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number, chain) batch_hash = make_bytes(32) mock_batcher_contract.setBatchHash(0, 0, batch_hash) mine_until(config.start_block_number + config.batch_span - 1, chain) with brownie.reverts("ExecutorContract: incorrect cipher batch hash"): executor_contract.executeCipherBatch(0, ZERO_HASH32, [], 0, {"from": keypers[0]}) with brownie.reverts("ExecutorContract: incorrect cipher batch hash"): executor_contract.executeCipherBatch(0, make_bytes(32), [b""], 0, {"from": keypers[0]}) executor_contract.executeCipherBatch(0, batch_hash, [b""], 0, {"from": keypers[0]})
def test_check_plain_batch_hash( executor_contract: Any, config_contract: Any, mock_batcher_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number, chain) batch = make_batch(3) mock_batcher_contract.setBatchHash(0, 1, compute_batch_hash(batch)) mine_until(config.start_block_number + config.batch_span - 1, chain) executor_contract.executeCipherBatch(0, ZERO_HASH32, [], 0, {"from": keypers[0]}) with brownie.reverts("ExecutorContract: batch hash does not match"): executor_contract.executePlainBatch(0, []) with brownie.reverts("ExecutorContract: batch hash does not match"): executor_contract.executePlainBatch(0, batch[:-1]) with brownie.reverts("ExecutorContract: batch hash does not match"): executor_contract.executePlainBatch(0, batch + [make_bytes()]) executor_contract.executePlainBatch(0, batch)
def test_check_batching_period_is_over( executor_contract: Any, config_contract: Any, mock_batcher_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) for batch_index in range(3): mine_until( config.start_block_number + (batch_index + 1) * config.batch_span - 2, chain) with brownie.reverts(): executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[0]}) executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[0]}) plain_batch = make_batch() mock_batcher_contract.setBatchHash(batch_index, 1, compute_batch_hash(plain_batch)) executor_contract.executePlainBatch(batch_index, plain_batch)
def configured( config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=100, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain)
def test_cipher_execution_skips_after_timeout( executor_contract: Any, config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, execution_timeout=20, keypers=keypers, ) schedule_config(config_contract, config, owner=owner) batch_index = 0 forbidden_from_block = (config.start_block_number + (batch_index + 1) * config.batch_span + config.execution_timeout) mine_until( forbidden_from_block - 2, chain, ) chain.snapshot() # test that skipCipherExecution still fails with brownie.reverts( "ExecutorContract: execution timeout not reached yet"): executor_contract.skipCipherExecution(batch_index) # test that we could still executeCipherBatch for block forbidden_from_block -1 chain.revert() tx = executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[0]}) assert tx.events["BatchExecuted"] # test that calling executeCipherBatch after the timeout results in skipping the cipher # execution chain.revert() mine_until( forbidden_from_block - 1, chain, ) tx = executor_contract.executeCipherBatch(batch_index, ZERO_HASH32, [], 0, {"from": keypers[0]}) assert len(tx.events) == 1 assert tx.events["CipherExecutionSkipped"]["numExecutionHalfSteps"] == 1
def test_add_tx_fails_if_not_active( batcher_contract: Any, config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number, chain) with brownie.reverts(): batcher_contract.addTransaction(0, 0, b"\x00")
def test_add_tx_checks_fee( batcher_contract: Any, config_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain) batcher_contract.setMinFee(100, {"from": owner}) with brownie.reverts(): batcher_contract.addTransaction(0, 0, b"\x00", {"value": 99}) batcher_contract.addTransaction(0, 0, b"\x00", {"value": 100})
def test_call_target_function( executor_contract: Any, config_contract: Any, mock_target_contract: Any, mock_batcher_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], mock_target_function_selector: bytes, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, target_address=to_canonical_address(mock_target_contract.address), target_function_selector=mock_target_function_selector, transaction_gas_limit=5000, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number + config.batch_span, chain) batch = make_batch(3) cipher_batch_hash = Hash32(b"\xde" * 32) mock_batcher_contract.setBatchHash(0, 0, cipher_batch_hash) tx = executor_contract.executeCipherBatch(0, cipher_batch_hash, batch, 0, {"from": keypers[0]}) assert len(tx.events["Called"]) == len(batch) for i, transaction in enumerate(batch): assert tx.events["Called"][i]["transaction"] == encode_hex(transaction) assert (config.transaction_gas_limit - 500 < tx.events["Called"][0]["gas"] <= config.transaction_gas_limit) batch = make_batch(3) mock_batcher_contract.setBatchHash(0, 1, compute_batch_hash(batch)) tx = executor_contract.executePlainBatch(0, batch) assert len(tx.events["Called"]) == len(batch) for i, transaction in enumerate(batch): assert tx.events["Called"][i]["transaction"] == encode_hex(transaction) assert (config.transaction_gas_limit - 500 < tx.events["Called"][0]["gas"] <= config.transaction_gas_limit)
def test_emit_event( executor_contract: Any, config_contract: Any, mock_batcher_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, keypers=keypers, threshold=0, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number + config.batch_span * 10, chain) for batch_index in range(3): cipher_batch = make_batch(3) cipher_batch_hash = Hash32(b"\xfc" * 32) mock_batcher_contract.setBatchHash(batch_index, 0, cipher_batch_hash) tx = executor_contract.executeCipherBatch(batch_index, cipher_batch_hash, cipher_batch, 0, {"from": keypers[0]}) assert len(tx.events["BatchExecuted"]) == 1 assert tx.events["BatchExecuted"][0] == { "numExecutionHalfSteps": batch_index * 2 + 1, "batchHash": encode_hex(compute_batch_hash(cipher_batch)), } plain_batch = make_batch(3) mock_batcher_contract.setBatchHash(batch_index, 1, compute_batch_hash(plain_batch)) tx = executor_contract.executePlainBatch(batch_index, plain_batch) assert len(tx.events["BatchExecuted"]) == 1 assert tx.events["BatchExecuted"][0] == { "numExecutionHalfSteps": batch_index * 2 + 2, "batchHash": encode_hex(compute_batch_hash(plain_batch)), }
def test_check_batching_is_active( executor_contract: Any, config_contract: Any, chain: Chain, config_change_heads_up_blocks: int, owner: Account, keypers: List[Account], ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, keypers=keypers, threshold=0, batch_span=0, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number + config.batch_span, chain) with brownie.reverts("ExecutorContract: config is inactive"): executor_contract.executeCipherBatch(0, ZERO_HASH32, [], 0)
def test_add_tx_checks_batching_period_end( batcher_contract: Any, config_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=20, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number + 18, chain) batcher_contract.addTransaction(0, 0, b"\x00") assert chain.height == config.start_block_number + 19 with brownie.reverts("BatcherContract: batch already closed"): batcher_contract.addTransaction(0, 0, b"\x00") batcher_contract.addTransaction(1, 0, b"\x00")
def test_add_tx_updates_batch_size( batcher_contract: Any, config_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain) assert batcher_contract.batchSizes(0) == 0 batcher_contract.addTransaction(0, 0, b"\x00" * 3) assert batcher_contract.batchSizes(0) == 3 batcher_contract.addTransaction(0, 1, b"\x00" * 5) assert batcher_contract.batchSizes(0) == 8
def test_add_tx_checks_batch_size( batcher_contract: Any, config_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=20, transaction_size_limit=100, batch_size_limit=100, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain) for _ in range(10): batcher_contract.addTransaction(0, 0, b"\x00" * 10) with brownie.reverts(): batcher_contract.addTransaction(0, 0, b"\x00")
def test_add_tx_updates_hash_chain( batcher_contract: Any, config_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, ) -> None: config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain) for tx_type in [0, 1]: assert bytes(batcher_contract.batchHashes(0, tx_type)) == b"\x00" * 32 batcher_contract.addTransaction(0, tx_type, b"\x11") assert bytes(batcher_contract.batchHashes( 0, tx_type)) == keccak(b"\x11" + b"\x00" * 32) batcher_contract.addTransaction(0, tx_type, b"\x22") assert bytes(batcher_contract.batchHashes( 0, tx_type)) == keccak(b"\x22" + keccak(b"\x11" + b"\x00" * 32))
def test_add_tx_pays_fee( batcher_contract: Any, config_contract: Any, fee_bank_contract: Any, config_change_heads_up_blocks: int, chain: Chain, owner: Account, accounts: Sequence[Account], ) -> None: fee_receiver = accounts[1] config = make_batch_config( start_batch_index=0, start_block_number=chain.height + config_change_heads_up_blocks + 20, batch_span=10, fee_receiver=to_canonical_address(fee_receiver.address), ) schedule_config(config_contract, config, owner=owner) mine_until(config.start_block_number - 1, chain) initial_balance = fee_bank_contract.deposits(config.fee_receiver) batcher_contract.addTransaction(0, 0, b"\x00", {"value": 150}) assert fee_bank_contract.deposits( config.fee_receiver) == initial_balance + 150