diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index aff40034..80dd9392 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -11,6 +11,8 @@ pub static KERNEL: Lazy = Lazy::new(combined_kernel); pub(crate) fn combined_kernel() -> Kernel { let files = vec![ + "global jumped_to_0: PANIC", + "global jumped_to_1: PANIC", include_str!("asm/core/bootloader.asm"), include_str!("asm/core/call.asm"), include_str!("asm/core/create.asm"), diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 8a6c3afb..9629d647 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -7,15 +7,7 @@ // Post stack: (empty) global process_normalized_txn: // stack: retdest - PUSH 0 // TODO: Load block's base fee - %mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) - ADD - // stack: priority_fee + base_fee, retdest - %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) - // stack: max_fee, priority_fee + base_fee, retdest - %min - // stack: computed_fee, retdest - %mstore_txn_field(@TXN_FIELD_COMPUTED_FEE_PER_GAS) + %compute_fees // stack: retdest // Compute this transaction's intrinsic gas and store it. @@ -42,9 +34,8 @@ global buy_gas: // stack: gas_cost, retdest %mload_txn_field(@TXN_FIELD_ORIGIN) // stack: sender_addr, gas_cost, retdest - %deduct_eth // TODO: It should be transferred to coinbase instead? + %deduct_eth // stack: deduct_eth_status, retdest -global txn_failure_insufficient_balance: %jumpi(panic) // stack: retdest @@ -113,13 +104,12 @@ global process_message_txn_insufficient_balance: PANIC // TODO global process_message_txn_return: - // Refund leftover gas. // stack: retdest %mload_txn_field(@TXN_FIELD_INTRINSIC_GAS) %mload_txn_field(@TXN_FIELD_GAS_LIMIT) SUB // stack: leftover_gas, retdest - %refund_leftover_gas_cost + %pay_coinbase_and_refund_sender // stack: retdest JUMP @@ -151,17 +141,68 @@ global process_message_txn_after_call: // stack: success, leftover_gas, new_ctx, retdest POP // TODO: Success will go into the receipt when we support that. // stack: leftover_gas, new_ctx, retdest - %refund_leftover_gas_cost + %pay_coinbase_and_refund_sender // stack: new_ctx, retdest POP JUMP -%macro refund_leftover_gas_cost +%macro pay_coinbase_and_refund_sender // stack: leftover_gas + DUP1 + // stack: leftover_gas, leftover_gas + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + SUB + // stack: used_gas, leftover_gas + %mload_global_metadata(@GLOBAL_METADATA_REFUND_COUNTER) + // stack: refund, used_gas, leftover_gas + DUP2 %div_const(2) // max_refund = used_gas/2 + // stack: max_refund, refund, used_gas, leftover_gas + %min + %stack (refund, used_gas, leftover_gas) -> (leftover_gas, refund, refund, used_gas) + ADD + // stack: leftover_gas', refund, used_gas + SWAP2 + // stack: used_gas, refund, leftover_gas' + SUB + // stack: used_gas', leftover_gas' + + // Pay the coinbase. + %mload_txn_field(@TXN_FIELD_COMPUTED_PRIORITY_FEE_PER_GAS) + MUL + // stack: used_gas_tip, leftover_gas' + %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BENEFICIARY) + // stack: coinbase, used_gas_tip, leftover_gas' + %add_eth + // stack: leftover_gas' + + // Refund gas to the origin. %mload_txn_field(@TXN_FIELD_COMPUTED_FEE_PER_GAS) MUL // stack: leftover_gas_cost %mload_txn_field(@TXN_FIELD_ORIGIN) // stack: origin, leftover_gas_cost %add_eth + // stack: (empty) +%endmacro + +// Sets @TXN_FIELD_MAX_FEE_PER_GAS and @TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS. +%macro compute_fees + // stack: (empty) + %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BASE_FEE) + %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 + // stack: max_fee, max_priority_fee, base_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 + %min + // stack: computed_priority_fee, base_fee + %stack (computed_priority_fee, base_fee) -> (computed_priority_fee, base_fee, computed_priority_fee) + ADD + // stack: computed_fee, computed_priority_fee + %mstore_txn_field(@TXN_FIELD_COMPUTED_FEE_PER_GAS) + %mstore_txn_field(@TXN_FIELD_COMPUTED_PRIORITY_FEE_PER_GAS) + // stack: (empty) %endmacro diff --git a/evm/src/cpu/kernel/asm/util/basic_macros.asm b/evm/src/cpu/kernel/asm/util/basic_macros.asm index 17003bd8..d501ea32 100644 --- a/evm/src/cpu/kernel/asm/util/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/util/basic_macros.asm @@ -219,8 +219,8 @@ DUP2 DUP2 // stack: x, y, x, y - LT - // stack: x < y, x, y + GT + // stack: x > y, x, y %select_bool // stack: min %endmacro @@ -230,8 +230,8 @@ DUP2 DUP2 // stack: x, y, x, y - GT - // stack: x > y, x, y + LT + // stack: x < y, x, y %select_bool // stack: max %endmacro diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index cd6c0d7e..cee02e86 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -39,10 +39,13 @@ pub(crate) enum GlobalMetadata { BlockGasLimit = 19, BlockChainId = 20, BlockBaseFee = 21, + + /// Gas to refund at the end of the transaction. + RefundCounter = 22, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 20; + pub(crate) const COUNT: usize = 21; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -66,6 +69,7 @@ impl GlobalMetadata { Self::BlockGasLimit, Self::BlockChainId, Self::BlockBaseFee, + Self::RefundCounter, ] } @@ -92,6 +96,7 @@ impl GlobalMetadata { Self::BlockGasLimit => "GLOBAL_METADATA_BLOCK_GAS_LIMIT", Self::BlockChainId => "GLOBAL_METADATA_BLOCK_CHAIN_ID", Self::BlockBaseFee => "GLOBAL_METADATA_BLOCK_BASE_FEE", + Self::RefundCounter => "GLOBAL_METADATA_REFUND_COUNTER", } } } diff --git a/evm/src/cpu/kernel/constants/txn_fields.rs b/evm/src/cpu/kernel/constants/txn_fields.rs index c2931f05..f4364c6f 100644 --- a/evm/src/cpu/kernel/constants/txn_fields.rs +++ b/evm/src/cpu/kernel/constants/txn_fields.rs @@ -9,9 +9,6 @@ pub(crate) enum NormalizedTxnField { Nonce = 2, MaxPriorityFeePerGas = 3, MaxFeePerGas = 4, - /// The actual computed gas price for this transaction in the block. - /// This is not technically a transaction field, as it depends on the block's base fee. - ComputedFeePerGas = 5, GasLimit = 6, IntrinsicGas = 7, To = 8, @@ -22,10 +19,15 @@ pub(crate) enum NormalizedTxnField { R = 12, S = 13, Origin = 14, + + /// The actual computed gas price for this transaction in the block. + /// This is not technically a transaction field, as it depends on the block's base fee. + ComputedFeePerGas = 15, + ComputedPriorityFeePerGas = 16, } impl NormalizedTxnField { - pub(crate) const COUNT: usize = 15; + pub(crate) const COUNT: usize = 16; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -34,7 +36,6 @@ impl NormalizedTxnField { Self::Nonce, Self::MaxPriorityFeePerGas, Self::MaxFeePerGas, - Self::ComputedFeePerGas, Self::GasLimit, Self::IntrinsicGas, Self::To, @@ -44,6 +45,8 @@ impl NormalizedTxnField { Self::R, Self::S, Self::Origin, + Self::ComputedFeePerGas, + Self::ComputedPriorityFeePerGas, ] } @@ -55,7 +58,6 @@ impl NormalizedTxnField { NormalizedTxnField::Nonce => "TXN_FIELD_NONCE", NormalizedTxnField::MaxPriorityFeePerGas => "TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS", NormalizedTxnField::MaxFeePerGas => "TXN_FIELD_MAX_FEE_PER_GAS", - NormalizedTxnField::ComputedFeePerGas => "TXN_FIELD_COMPUTED_FEE_PER_GAS", NormalizedTxnField::GasLimit => "TXN_FIELD_GAS_LIMIT", NormalizedTxnField::IntrinsicGas => "TXN_FIELD_INTRINSIC_GAS", NormalizedTxnField::To => "TXN_FIELD_TO", @@ -65,6 +67,10 @@ impl NormalizedTxnField { NormalizedTxnField::R => "TXN_FIELD_R", NormalizedTxnField::S => "TXN_FIELD_S", NormalizedTxnField::Origin => "TXN_FIELD_ORIGIN", + NormalizedTxnField::ComputedFeePerGas => "TXN_FIELD_COMPUTED_FEE_PER_GAS", + NormalizedTxnField::ComputedPriorityFeePerGas => { + "TXN_FIELD_COMPUTED_PRIORITY_FEE_PER_GAS" + } } } } diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 9bde0106..c01e8af1 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use eth_trie_utils::partial_trie::PartialTrie; -use ethereum_types::{Address, BigEndianHash, H256}; +use ethereum_types::{Address, BigEndianHash, H256, U256}; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::hash::hash_types::RichField; @@ -24,7 +24,7 @@ use crate::generation::state::GenerationState; use crate::generation::trie_extractor::read_state_trie_value; use crate::memory::segments::Segment; use crate::proof::{BlockMetadata, PublicValues, TrieRoots}; -use crate::witness::memory::MemoryAddress; +use crate::witness::memory::{MemoryAddress, MemoryChannel}; use crate::witness::transition::transition; pub mod mpt; @@ -33,6 +33,7 @@ pub(crate) mod rlp; pub(crate) mod state; mod trie_extractor; use crate::generation::trie_extractor::read_trie; +use crate::witness::util::mem_write_log; #[derive(Clone, Debug, Deserialize, Serialize, Default)] /// Inputs needed for trace generation. @@ -67,6 +68,37 @@ pub struct TrieInputs { pub storage_tries: Vec<(Address, PartialTrie)>, } +fn apply_metadata_memops, const D: usize>( + state: &mut GenerationState, + metadata: &BlockMetadata, +) { + let fields = [ + ( + GlobalMetadata::BlockBeneficiary, + U256::from_big_endian(&metadata.block_beneficiary.0), + ), + (GlobalMetadata::BlockTimestamp, metadata.block_timestamp), + (GlobalMetadata::BlockNumber, metadata.block_number), + (GlobalMetadata::BlockDifficulty, metadata.block_difficulty), + (GlobalMetadata::BlockGasLimit, metadata.block_gaslimit), + (GlobalMetadata::BlockChainId, metadata.block_chain_id), + (GlobalMetadata::BlockBaseFee, metadata.block_base_fee), + ]; + + let channel = MemoryChannel::GeneralPurpose(0); + let ops = fields.map(|(field, val)| { + mem_write_log( + channel, + MemoryAddress::new(0, Segment::GlobalMetadata, field as usize), + state, + val, + ) + }); + + state.memory.apply_ops(&ops); + state.traces.memory_ops.extend(ops); +} + pub(crate) fn generate_traces, const D: usize>( all_stark: &AllStark, inputs: GenerationInputs, @@ -75,6 +107,8 @@ pub(crate) fn generate_traces, const D: usize>( ) -> anyhow::Result<([Vec>; NUM_TABLES], PublicValues)> { let mut state = GenerationState::::new(inputs.clone(), &KERNEL.code); + apply_metadata_memops(&mut state, &inputs.block_metadata); + generate_bootstrap_kernel::(&mut state); timed!(timing, "simulate CPU", simulate_cpu(&mut state)?); diff --git a/evm/tests/basic_smart_contract.rs b/evm/tests/basic_smart_contract.rs index c127ae33..71d11933 100644 --- a/evm/tests/basic_smart_contract.rs +++ b/evm/tests/basic_smart_contract.rs @@ -3,7 +3,7 @@ use std::time::Duration; use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; -use ethereum_types::U256; +use ethereum_types::{Address, U256}; use hex_literal::hex; use keccak_hash::keccak; use plonky2::field::goldilocks_field::GoldilocksField; @@ -30,13 +30,17 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); + let beneficiary = hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); 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 value = U256::from(100u32); let push1 = get_push_opcode(1); let add = get_opcode("ADD"); @@ -45,12 +49,12 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { let code_gas = 3 + 3 + 3; let code_hash = keccak(code); + let beneficiary_account_before = AccountRlp::default(); let sender_account_before = AccountRlp { nonce: 5.into(), balance: eth_to_wei(100_000.into()), ..AccountRlp::default() }; - let to_account_before = AccountRlp { code_hash, ..AccountRlp::default() @@ -83,8 +87,12 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { // Generated using a little py-evm script. let txn = hex!("f861050a8255f094a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0648242421ba02c89eb757d9deeb1f5b3859a9d4d679951ef610ac47ad4608dc142beb1b7e313a05af7e9fbab825455d36c36c7f4cfcafbeafa9a77bdff936b52afb36d4fe4bcdd"); + let value = U256::from(100u32); - let block_metadata = BlockMetadata::default(); + let block_metadata = BlockMetadata { + block_beneficiary: Address::from(beneficiary), + ..BlockMetadata::default() + }; let mut contract_code = HashMap::new(); contract_code.insert(code_hash, code.to_vec()); @@ -102,6 +110,11 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { let expected_state_trie_after = { let txdata_gas = 2 * 16; let gas_used = 21_000 + code_gas + 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, @@ -113,6 +126,11 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { }; let mut children = core::array::from_fn(|_| PartialTrie::Empty.into()); + children[beneficiary_nibbles.get_nibble(0) as usize] = PartialTrie::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] = PartialTrie::Leaf { nibbles: sender_nibbles.truncate_n_nibbles_front(1), value: rlp::encode(&sender_account_after).to_vec(), @@ -123,7 +141,6 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { value: rlp::encode(&to_account_after).to_vec(), } .into(); - // TODO: Beneficiary should receive gas... PartialTrie::Branch { children, value: vec![], diff --git a/evm/tests/simple_transfer.rs b/evm/tests/simple_transfer.rs index a04e5af3..094bc070 100644 --- a/evm/tests/simple_transfer.rs +++ b/evm/tests/simple_transfer.rs @@ -3,7 +3,7 @@ use std::time::Duration; use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; -use ethereum_types::U256; +use ethereum_types::{Address, U256}; use hex_literal::hex; use keccak_hash::keccak; use plonky2::field::goldilocks_field::GoldilocksField; @@ -29,20 +29,26 @@ fn test_simple_transfer() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); + let beneficiary = hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); 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 value = U256::from(100u32); + let beneficiary_account_before = AccountRlp::default(); let sender_account_before = AccountRlp { nonce: 5.into(), balance: eth_to_wei(100_000.into()), storage_root: PartialTrie::Empty.calc_hash(), code_hash: keccak([]), }; + let to_account_before = AccountRlp::default(); let state_trie_before = PartialTrie::Leaf { nibbles: sender_nibbles, @@ -57,8 +63,12 @@ fn test_simple_transfer() -> anyhow::Result<()> { // Generated using a little py-evm script. let txn = hex!("f861050a8255f094a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0648242421ba02c89eb757d9deeb1f5b3859a9d4d679951ef610ac47ad4608dc142beb1b7e313a05af7e9fbab825455d36c36c7f4cfcafbeafa9a77bdff936b52afb36d4fe4bcdd"); + let value = U256::from(100u32); - let block_metadata = BlockMetadata::default(); + let block_metadata = BlockMetadata { + block_beneficiary: Address::from(beneficiary), + ..BlockMetadata::default() + }; let inputs = GenerationInputs { signed_txns: vec![txn.to_vec()], @@ -74,6 +84,11 @@ fn test_simple_transfer() -> anyhow::Result<()> { let expected_state_trie_after = { 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, @@ -81,10 +96,15 @@ fn test_simple_transfer() -> anyhow::Result<()> { }; let to_account_after = AccountRlp { balance: value, - ..AccountRlp::default() + ..to_account_before }; let mut children = core::array::from_fn(|_| PartialTrie::Empty.into()); + children[beneficiary_nibbles.get_nibble(0) as usize] = PartialTrie::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] = PartialTrie::Leaf { nibbles: sender_nibbles.truncate_n_nibbles_front(1), value: rlp::encode(&sender_account_after).to_vec(),