From 6e3036017ecc10c548c2cfe399e765c7b0ca0059 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 23 May 2023 15:06:26 +0200 Subject: [PATCH] Support for type-2 transactions (#1052) * Type-2 txns * Minor * Minor * Fix add11_yml block metadata * Fix simple_transfer test * Minor --- evm/src/cpu/kernel/asm/core/process_txn.asm | 24 ++-- .../cpu/kernel/asm/transactions/type_2.asm | 109 +++++++++++++++++- evm/src/cpu/kernel/asm/util/assertions.asm | 34 ++++++ evm/tests/add11_yml.rs | 6 +- evm/tests/simple_transfer.rs | 19 +-- 5 files changed, 170 insertions(+), 22 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 022c1882..2eea69dd 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -18,22 +18,27 @@ global process_normalized_txn: // Assert gas_limit >= intrinsic_gas. %mload_txn_field(@TXN_FIELD_INTRINSIC_GAS) %mload_txn_field(@TXN_FIELD_GAS_LIMIT) - %assert_ge + %assert_ge(invalid_txn) + + // Assert block gas limit >= txn gas limit. + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + %mload_global_metadata(@GLOBAL_METADATA_BLOCK_GAS_LIMIT) + %assert_ge(invalid_txn) %mload_txn_field(@TXN_FIELD_ORIGIN) // stack: sender, retdest // Check that txn nonce matches account nonce. DUP1 %nonce - DUP1 %eq_const(@MAX_NONCE) %assert_zero // EIP-2681 + DUP1 %eq_const(@MAX_NONCE) %assert_zero(invalid_txn) // EIP-2681 // stack: sender_nonce, sender, retdest %mload_txn_field(@TXN_FIELD_NONCE) // stack: tx_nonce, sender_nonce, sender, retdest - %assert_eq + %assert_eq(invalid_txn) // stack: sender, retdest // Assert sender has no code. - DUP1 %ext_code_empty %assert_nonzero + DUP1 %ext_code_empty %assert_nonzero(invalid_txn) // stack: sender, retdest // Assert sender balance >= gas_limit * gas_price + value. @@ -44,7 +49,7 @@ global process_normalized_txn: MUL %mload_txn_field(@TXN_FIELD_VALUE) ADD - %assert_le + %assert_le(invalid_txn) // stack: retdest // Assert chain ID matches block metadata @@ -58,7 +63,7 @@ global process_normalized_txn: %mload_global_metadata(@GLOBAL_METADATA_BLOCK_CHAIN_ID) MUL // stack: filtered_block_chain_id, filtered_tx_chain_id, retdest - %assert_eq + %assert_eq(invalid_txn) // stack: retdest global buy_gas: @@ -348,8 +353,9 @@ process_message_txn_fail: %mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) // stack: max_fee, max_priority_fee, base_fee - DUP3 DUP2 %assert_ge // Assert max_fee >= base_fee + DUP3 DUP2 %assert_ge(invalid_txn) // Assert max_fee >= base_fee // stack: max_fee, max_priority_fee, base_fee + DUP2 DUP2 %assert_ge(invalid_txn) // Assert max_fee >= max_priority_fee %stack (max_fee, max_priority_fee, base_fee) -> (max_fee, base_fee, max_priority_fee, base_fee) SUB // stack: max_fee - base_fee, max_priority_fee, base_fee @@ -399,3 +405,7 @@ contract_creation_fault_4: %delete_all_touched_addresses %delete_all_selfdestructed_addresses JUMP + + +invalid_txn: + %jump(txn_loop) diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index d9586c21..464814eb 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -34,6 +34,113 @@ global process_type_2_txn: POP // stack: retdest - // TODO: Check signature. +// From EIP-1559: +// The signature_y_parity, signature_r, signature_s elements of this transaction represent a secp256k1 signature over +// keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list])) +type_2_compute_signed_data: + %alloc_rlp_block + // stack: rlp_start, retdest + %mload_txn_field(@TXN_FIELD_CHAIN_ID) + // stack: chain_id, rlp_start, retdest + DUP2 + // stack: rlp_pos, chain_id, rlp_start, retdest + %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + %mload_txn_field(@TXN_FIELD_NONCE) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_TO) + DUP1 %jumpi(nonzero_to) + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_scalar + %jump(after_to) +nonzero_to: + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_160 + // stack: rlp_pos, rlp_start, retdest + +after_to: + %mload_txn_field(@TXN_FIELD_VALUE) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + // Encode txn data. + %mload_txn_field(@TXN_FIELD_DATA_LEN) + PUSH 0 // ADDR.virt + PUSH @SEGMENT_TXN_DATA + PUSH 0 // ADDR.context + // stack: ADDR: 3, len, rlp_pos, rlp_start, retdest + PUSH after_serializing_txn_data + // stack: after_serializing_txn_data, ADDR: 3, len, rlp_pos, rlp_start, retdest + SWAP5 + // stack: rlp_pos, ADDR: 3, len, after_serializing_txn_data, rlp_start, retdest + %jump(encode_rlp_string) + +after_serializing_txn_data: + // Instead of manually encoding the access list, we just copy the raw RLP from the transaction. + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START) + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) + %stack (al_len, al_start, rlp_pos, rlp_start, retdest) -> + ( + 0, @SEGMENT_RLP_RAW, rlp_pos, + 0, @SEGMENT_RLP_RAW, al_start, + al_len, + after_serializing_access_list, + rlp_pos, rlp_start, retdest) + %jump(memcpy) +after_serializing_access_list: + // stack: rlp_pos, rlp_start, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) ADD + // stack: rlp_pos, rlp_start, retdest + %prepend_rlp_list_prefix + // stack: prefix_start_pos, rlp_len, retdest + + // Store a `2` in front of the RLP + %decrement + %stack (pos) -> (0, @SEGMENT_RLP_RAW, pos, 2, pos) + MSTORE_GENERAL + // stack: pos, rlp_len, retdest + + // Hash the RLP + the leading `2` + SWAP1 %increment SWAP1 + PUSH @SEGMENT_RLP_RAW + PUSH 0 // context + // stack: ADDR: 3, len, retdest + KECCAK_GENERAL + // stack: hash, retdest + + %mload_txn_field(@TXN_FIELD_S) + %mload_txn_field(@TXN_FIELD_R) + %mload_txn_field(@TXN_FIELD_Y_PARITY) %add_const(27) // ecrecover interprets v as y_parity + 27 + + PUSH store_origin + // stack: store_origin, v, r, s, hash, retdest + SWAP4 + // stack: hash, v, r, s, store_origin, retdest + %jump(ecrecover) + +store_origin: + // stack: address, retdest + // If ecrecover returned u256::MAX, that indicates failure. + DUP1 + %eq_const(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + %jumpi(panic) + + // stack: address, retdest + %mstore_txn_field(@TXN_FIELD_ORIGIN) + // stack: retdest %jump(process_normalized_txn) diff --git a/evm/src/cpu/kernel/asm/util/assertions.asm b/evm/src/cpu/kernel/asm/util/assertions.asm index 0051219c..017ca10f 100644 --- a/evm/src/cpu/kernel/asm/util/assertions.asm +++ b/evm/src/cpu/kernel/asm/util/assertions.asm @@ -8,17 +8,31 @@ global panic: %jumpi(panic) %endmacro +%macro assert_zero(ret) + %jumpi($ret) +%endmacro + // Consumes the top element and asserts that it is nonzero. %macro assert_nonzero ISZERO %jumpi(panic) %endmacro +%macro assert_nonzero(ret) + ISZERO + %jumpi($ret) +%endmacro + %macro assert_eq EQ %assert_nonzero %endmacro +%macro assert_eq(ret) + EQ + %assert_nonzero($ret) +%endmacro + %macro assert_lt // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x < y) == !(x >= y). @@ -26,6 +40,11 @@ global panic: %assert_zero %endmacro +%macro assert_lt(ret) + GE + %assert_zero($ret) +%endmacro + %macro assert_le // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x <= y) == !(x > y). @@ -33,6 +52,11 @@ global panic: %assert_zero %endmacro +%macro assert_le(ret) + GT + %assert_zero($ret) +%endmacro + %macro assert_gt // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x > y) == !(x <= y). @@ -40,6 +64,11 @@ global panic: %assert_zero %endmacro +%macro assert_gt(ret) + LE + %assert_zero($ret) +%endmacro + %macro assert_ge // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x >= y) == !(x < y). @@ -47,6 +76,11 @@ global panic: %assert_zero %endmacro +%macro assert_ge(ret) + LT + %assert_zero($ret) +%endmacro + %macro assert_eq_const(c) %eq_const($c) %assert_nonzero diff --git a/evm/tests/add11_yml.rs b/evm/tests/add11_yml.rs index d4e47378..a17d688e 100644 --- a/evm/tests/add11_yml.rs +++ b/evm/tests/add11_yml.rs @@ -81,8 +81,12 @@ fn add11_yml() -> anyhow::Result<()> { let block_metadata = BlockMetadata { block_beneficiary: Address::from(beneficiary), + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_gaslimit: 0xff112233445566u64.into(), + block_chain_id: 1.into(), block_base_fee: 0xa.into(), - ..BlockMetadata::default() }; let mut contract_code = HashMap::new(); diff --git a/evm/tests/simple_transfer.rs b/evm/tests/simple_transfer.rs index 250631e3..028900fa 100644 --- a/evm/tests/simple_transfer.rs +++ b/evm/tests/simple_transfer.rs @@ -37,15 +37,12 @@ fn test_simple_transfer() -> anyhow::Result<()> { let sender = hex!("2c7536e3605d9c16a7a3d7b1898e529396a65c23"); let to = hex!("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"); - let beneficiary_state_key = keccak(beneficiary); let sender_state_key = keccak(sender); let to_state_key = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); let to_nibbles = Nibbles::from_bytes_be(to_state_key.as_bytes()).unwrap(); - let beneficiary_account_before = AccountRlp::default(); let sender_account_before = AccountRlp { nonce: 5.into(), balance: eth_to_wei(100_000.into()), @@ -72,7 +69,12 @@ fn test_simple_transfer() -> anyhow::Result<()> { let block_metadata = BlockMetadata { block_beneficiary: Address::from(beneficiary), - ..BlockMetadata::default() + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_gaslimit: 0xff112233445566u64.into(), + block_chain_id: 1.into(), + block_base_fee: 0xa.into(), }; let mut contract_code = HashMap::new(); @@ -94,10 +96,6 @@ fn test_simple_transfer() -> anyhow::Result<()> { let txdata_gas = 2 * 16; let gas_used = 21_000 + txdata_gas; - let beneficiary_account_after = AccountRlp { - balance: beneficiary_account_before.balance + gas_used * 10, - ..beneficiary_account_before - }; let sender_account_after = AccountRlp { balance: sender_account_before.balance - value - gas_used * 10, nonce: sender_account_before.nonce + 1, @@ -109,11 +107,6 @@ fn test_simple_transfer() -> anyhow::Result<()> { }; let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[beneficiary_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: beneficiary_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&beneficiary_account_after).to_vec(), - } - .into(); children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { nibbles: sender_nibbles.truncate_n_nibbles_front(1), value: rlp::encode(&sender_account_after).to_vec(),