From 6941aba15f17ad1ec5a04b3f594a2806438a102b Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Fri, 23 Aug 2024 16:05:08 +0100 Subject: [PATCH] EVM/Kernel: FA bridge whitelisted code hashes --- .../kernel_evm/evm_evaluation/src/runner.rs | 1 + .../evm_execution/src/fa_bridge/mod.rs | 27 ++++++++--- .../evm_execution/src/fa_bridge/test_utils.rs | 3 ++ .../kernel_evm/evm_execution/src/handler.rs | 45 ++++++++++++++++++ etherlink/kernel_evm/evm_execution/src/lib.rs | 47 +++++++++++++++++++ .../src/precompiles/fa_bridge.rs | 2 + .../evm_execution/src/precompiles/mod.rs | 1 + etherlink/kernel_evm/kernel/src/apply.rs | 8 ++++ etherlink/kernel_evm/kernel/src/block.rs | 7 +++ .../kernel/src/blueprint_storage.rs | 1 + .../kernel_evm/kernel/src/configuration.rs | 14 ++++-- etherlink/kernel_evm/kernel/src/inbox.rs | 23 ++++++++- etherlink/kernel_evm/kernel/src/simulation.rs | 37 +++++++++++---- etherlink/kernel_evm/kernel/src/stage_one.rs | 10 ++++ etherlink/kernel_evm/kernel/src/storage.rs | 20 ++++++++ 15 files changed, 225 insertions(+), 21 deletions(-) diff --git a/etherlink/kernel_evm/evm_evaluation/src/runner.rs b/etherlink/kernel_evm/evm_evaluation/src/runner.rs index dd686f7d8c69..b244af130739 100644 --- a/etherlink/kernel_evm/evm_evaluation/src/runner.rs +++ b/etherlink/kernel_evm/evm_evaluation/src/runner.rs @@ -244,6 +244,7 @@ fn execute_transaction( false, true, None, + None, ) } diff --git a/etherlink/kernel_evm/evm_execution/src/fa_bridge/mod.rs b/etherlink/kernel_evm/evm_execution/src/fa_bridge/mod.rs index 6c0c732ce8bd..37acf7ecf00b 100644 --- a/etherlink/kernel_evm/evm_execution/src/fa_bridge/mod.rs +++ b/etherlink/kernel_evm/evm_execution/src/fa_bridge/mod.rs @@ -36,7 +36,7 @@ use std::borrow::Cow; use deposit::FaDeposit; use evm::{Config, ExitReason}; use host::runtime::Runtime; -use primitive_types::{H160, U256}; +use primitive_types::{H160, H256, U256}; use tezos_ethereum::block::BlockConstants; use tezos_evm_logging::{log, Level::Info}; use ticket_table::TicketTable; @@ -124,6 +124,7 @@ pub fn execute_fa_deposit<'a, Host: Runtime>( caller: H160, deposit: &FaDeposit, allocated_ticks: u64, + fa_bridge_whitelist: Option>, ) -> Result { log!(host, Info, "Going to execute a {}", deposit.display()); @@ -139,6 +140,7 @@ pub fn execute_fa_deposit<'a, Host: Runtime>( // Warm-cold access only used for evaluation (for checking EVM compatibility), but not in production false, None, + fa_bridge_whitelist, ); handler.begin_initial_transaction(false, Some(FA_DEPOSIT_PROXY_GAS_LIMIT))?; @@ -311,12 +313,23 @@ fn inner_execute_proxy( // At very least we can protect from typos and other mistakes. if let Some(account) = handler.get_account(proxy)? { if let Ok(true) = account.code_exists(handler.borrow_host()) { - handler.execute_call( - proxy, - None, - input, - TransactionContext::new(caller, proxy, U256::zero()), - ) + // We also need to check if we are allowed to execute this code: + // either whitelisting is disabled or the code hash of this contract + // is in the list. + let code_hash = account.code_hash(handler.borrow_host())?; + if handler.is_fa_bridge_whitelisted_contract(&code_hash) { + handler.execute_call( + proxy, + None, + input, + TransactionContext::new(caller, proxy, U256::zero()), + ) + } else { + Ok(create_outcome_error!( + "Proxy contract code is not allowed: {}", + proxy + )) + } } else { Ok(create_outcome_error!( "Proxy contract does not have code: {}", diff --git a/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs b/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs index edaeb0a52ce3..311bcf9c896c 100644 --- a/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs +++ b/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs @@ -86,6 +86,7 @@ pub fn deploy_mock_wrapper( false, false, None, + None, ) .expect("Failed to deploy") .unwrap() @@ -110,6 +111,7 @@ pub fn run_fa_deposit( *caller, deposit, 1_000_000_000, + None, ) .expect("Failed to execute deposit") } @@ -341,6 +343,7 @@ pub fn fa_bridge_precompile_call_withdraw( U256::from(21000), false, None, + None, ); handler.begin_initial_transaction(false, None).unwrap(); diff --git a/etherlink/kernel_evm/evm_execution/src/handler.rs b/etherlink/kernel_evm/evm_execution/src/handler.rs index ffdc0c098d60..62989ede9046 100644 --- a/etherlink/kernel_evm/evm_execution/src/handler.rs +++ b/etherlink/kernel_evm/evm_execution/src/handler.rs @@ -284,6 +284,9 @@ pub struct EvmHandler<'a, Host: Runtime> { /// Whether warm/cold storage and address access is enabled /// If not, all access are considered warm pub enable_warm_cold_access: bool, + /// If specified, the FA bridge has to check the proxy contracts against + /// this list of allowed code hashes. + pub fa_bridge_whitelist: Option>, /// Tracer configuration for debugging. tracer: Option, /// State of the storage during a given execution. @@ -377,6 +380,7 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { effective_gas_price: U256, enable_warm_cold_access: bool, tracer: Option, + fa_bridge_whitelist: Option>, ) -> Self { Self { host, @@ -392,6 +396,7 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { enable_warm_cold_access, tracer, storage_state: vec![], + fa_bridge_whitelist, } } @@ -1979,6 +1984,13 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { ExitReason::Succeed(_) => vec![], } } + + pub fn is_fa_bridge_whitelisted_contract(&self, code: &H256) -> bool { + match self.fa_bridge_whitelist.as_ref() { + Some(whitelist) => whitelist.contains(code), + None => true, + } + } } #[allow(unused_variables)] @@ -2694,6 +2706,7 @@ mod test { gas_price, false, None, + None, ); let result = handler @@ -2729,6 +2742,7 @@ mod test { gas_price, false, None, + None, ); let code_hash: H256 = CODE_HASH_DEFAULT; @@ -2772,6 +2786,7 @@ mod test { gas_price, false, None, + None, ); let code_hash: H256 = CODE_HASH_DEFAULT; @@ -2819,6 +2834,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(213_u64); @@ -2881,6 +2897,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(213_u64); @@ -2975,6 +2992,7 @@ mod test { gas_price, false, None, + None, ); let input_value = U256::from(2026_u32); @@ -3070,6 +3088,7 @@ mod test { gas_price, false, None, + None, ); let input_value = U256::from(1025_u32); // transaction depth for contract below is callarg - 1 @@ -3163,6 +3182,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(312); @@ -3235,6 +3255,7 @@ mod test { gas_price, false, None, + None, ); let value = U256::zero(); @@ -3292,6 +3313,7 @@ mod test { gas_price, false, None, + None, ); let value = U256::zero(); @@ -3358,6 +3380,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(117); @@ -3421,6 +3444,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(210_u64); @@ -3483,6 +3507,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(210_u64); @@ -3554,6 +3579,7 @@ mod test { gas_price, false, None, + None, ); let hash_of_unavailable_block = handler.block_hash(U256::zero()); @@ -3582,6 +3608,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(210_u64); @@ -3643,6 +3670,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(210_u64); @@ -3695,6 +3723,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(210_u64); @@ -3782,6 +3811,7 @@ mod test { gas_price, false, None, + None, ); // { (SELFDESTRUCT 0x10) } @@ -3831,6 +3861,7 @@ mod test { U256::one(), false, None, + None, ); set_balance(&mut handler, &caller, U256::from(10000)); @@ -3875,6 +3906,7 @@ mod test { gas_price, false, None, + None, ); let address_1 = H160::from_low_u64_be(210_u64); @@ -3951,6 +3983,7 @@ mod test { gas_price, false, None, + None, ); let hash = handler.code_hash(H160::from_low_u64_le(1)); @@ -3981,6 +4014,7 @@ mod test { U256::one(), false, None, + None, ); set_balance(&mut handler, &caller, U256::from(1000000000)); @@ -4024,6 +4058,7 @@ mod test { gas_price, false, None, + None, ); let address_1 = H160::from_low_u64_be(210_u64); @@ -4107,6 +4142,7 @@ mod test { gas_price, false, None, + None, ); let address_1 = H160::from_low_u64_be(210_u64); @@ -4208,6 +4244,7 @@ mod test { gas_price, false, None, + None, ); let target_destruct = @@ -4274,6 +4311,7 @@ mod test { gas_price, true, None, + None, ); let contrac_addr = @@ -4345,6 +4383,7 @@ mod test { U256::from(21000), false, None, + None, ); handler @@ -4406,6 +4445,7 @@ mod test { gas_price, false, None, + None, ); let address = H160::from_low_u64_be(210_u64); @@ -4493,6 +4533,7 @@ mod test { gas_price, false, None, + None, ); let initial_code = [1; 49153]; // MAX_INIT_CODE_SIZE + 1 @@ -4532,6 +4573,7 @@ mod test { U256::one(), false, None, + None, ); set_balance(&mut handler, &caller, U256::from(1000000000)); @@ -4573,6 +4615,7 @@ mod test { U256::from(21000), false, None, + None, ); let address1 = H160::from_low_u64_be(210_u64); @@ -4653,6 +4696,7 @@ mod test { gas_price, false, None, + None, ); // SUICIDE would charge 25,000 gas when the destination is non-existent, @@ -4725,6 +4769,7 @@ mod test { gas_price, false, None, + None, ); // CALL would charge 25,000 gas when the destination is non-existent, diff --git a/etherlink/kernel_evm/evm_execution/src/lib.rs b/etherlink/kernel_evm/evm_execution/src/lib.rs index e8f031979f72..6dcde440d6c8 100644 --- a/etherlink/kernel_evm/evm_execution/src/lib.rs +++ b/etherlink/kernel_evm/evm_execution/src/lib.rs @@ -180,6 +180,7 @@ pub fn run_transaction<'a, Host>( retriable: bool, enable_warm_cold_access: bool, tracer: Option, + fa_bridge_whitelist: Option>, ) -> Result, EthereumError> where Host: Runtime, @@ -205,6 +206,7 @@ where effective_gas_price, enable_warm_cold_access, tracer, + fa_bridge_whitelist, ); let call_data_for_tracing = if tracer.is_some() { @@ -568,6 +570,7 @@ mod test { false, false, None, + None, ); let expected_result = Ok(Some(ExecutionOutcome { @@ -635,6 +638,7 @@ mod test { false, false, None, + None, ); let expected_result = Ok(Some(ExecutionOutcome { @@ -696,6 +700,7 @@ mod test { false, false, None, + None, ); let expected_result = Ok(Some(ExecutionOutcome { @@ -754,6 +759,7 @@ mod test { false, false, None, + None, ); let new_address = @@ -791,6 +797,7 @@ mod test { false, false, None, + None, ); assert!(result2.is_ok(), "execution should have succeeded"); let result = result2.unwrap(); @@ -822,6 +829,7 @@ mod test { false, false, None, + None, ); assert!(result3.is_ok(), "execution should have succeeded"); let result = result3.unwrap(); @@ -852,6 +860,7 @@ mod test { false, false, None, + None, ); assert!(result2.is_ok(), "execution should have succeeded"); let result = result2.unwrap(); @@ -909,6 +918,7 @@ mod test { false, false, None, + None, ); assert!(result.is_ok()); @@ -962,6 +972,7 @@ mod test { false, false, None, + None, ); let expected_result = Ok(Some(ExecutionOutcome { @@ -1017,6 +1028,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000; // base cost @@ -1165,6 +1177,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost @@ -1230,6 +1243,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost @@ -1294,6 +1308,7 @@ mod test { false, false, None, + None, ); // Assert @@ -1353,6 +1368,7 @@ mod test { false, false, None, + None, ); let expected_result = Ok(Some(ExecutionOutcome { @@ -1413,6 +1429,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost @@ -1475,6 +1492,7 @@ mod test { false, false, None, + None, ); // Assert @@ -1589,6 +1607,7 @@ mod test { false, false, None, + None, ); // Assert @@ -1670,6 +1689,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost @@ -1765,6 +1785,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost @@ -1882,6 +1903,7 @@ mod test { false, false, None, + None, ); let log_record1 = Log { @@ -1996,6 +2018,7 @@ mod test { false, false, None, + None, ); let log_record1 = Log { @@ -2099,6 +2122,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost + 5124; // execution gas cost (taken at face value from tests) @@ -2218,6 +2242,7 @@ mod test { false, false, None, + None, ); let expected_result = Ok(Some(ExecutionOutcome { @@ -2318,6 +2343,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost @@ -2402,6 +2428,7 @@ mod test { false, false, None, + None, ); let expected_gas = 21000 // base cost @@ -2482,6 +2509,7 @@ mod test { false, false, None, + None, ); let expected_result = Err(EthereumError::EthereumAccountError( @@ -2534,6 +2562,7 @@ mod test { false, false, None, + None, ); let result = unwrap_outcome!(result); @@ -2592,6 +2621,7 @@ mod test { false, false, None, + None, ); let result = unwrap_outcome!(&result, false); @@ -2665,6 +2695,7 @@ mod test { false, false, None, + None, ); // Assert @@ -2735,6 +2766,7 @@ mod test { false, false, None, + None, ); // Assert @@ -2800,6 +2832,7 @@ mod test { false, false, None, + None, ) } @@ -2907,6 +2940,7 @@ mod test { false, false, None, + None, ); let result = unwrap_outcome!(&result, false); @@ -2990,6 +3024,7 @@ mod test { false, false, None, + None, ); let result_init = unwrap_outcome!(&result_init, true); @@ -3100,6 +3135,7 @@ mod test { false, false, None, + None, ); let result_init = unwrap_outcome!(&result_init, true); @@ -3181,6 +3217,7 @@ mod test { false, false, None, + None, ); let result = unwrap_outcome!(&result, false); @@ -3229,6 +3266,7 @@ mod test { false, false, None, + None, ); let result = unwrap_outcome!(result); @@ -3286,6 +3324,7 @@ mod test { false, false, None, + None, ); let path = account_path(&caller).unwrap(); @@ -3376,6 +3415,7 @@ mod test { false, false, None, + None, ); // Get info on contract that should not be created @@ -3475,6 +3515,7 @@ mod test { retriable, false, None, + None, ); (host, result, account, initial_balance, initial_caller_nonce) @@ -3607,6 +3648,7 @@ mod test { false, false, None, + None, ); unwrap_outcome!(result, true); @@ -3693,6 +3735,7 @@ mod test { false, false, None, + None, ); unwrap_outcome!(result, false); @@ -3733,6 +3776,7 @@ mod test { false, false, None, + None, ); let internal_address_nonce = @@ -3814,6 +3858,7 @@ mod test { false, false, None, + None, ) .unwrap() .unwrap(); @@ -3905,6 +3950,7 @@ mod test { false, false, None, + None, ) .unwrap() .unwrap(); @@ -3966,6 +4012,7 @@ mod test { false, false, None, + None, ); // The origin address is empty but when you start a transaction the nonce is bump diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles/fa_bridge.rs b/etherlink/kernel_evm/evm_execution/src/precompiles/fa_bridge.rs index 5d85a859c2ab..3d7bb7096c0c 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles/fa_bridge.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles/fa_bridge.rs @@ -203,6 +203,7 @@ mod tests { U256::from(21000), false, None, + None, ); handler @@ -323,6 +324,7 @@ mod tests { U256::from(21000), false, None, + None, ); handler.begin_initial_transaction(false, None).unwrap(); diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs b/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs index 76a9a7f27927..cbc7205df35c 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs @@ -351,6 +351,7 @@ mod test_helpers { gas_price, false, None, + None, ); let value = transfer.map(|t| t.value); diff --git a/etherlink/kernel_evm/kernel/src/apply.rs b/etherlink/kernel_evm/kernel/src/apply.rs index 14bca0689753..9d88c4c494c2 100644 --- a/etherlink/kernel_evm/kernel/src/apply.rs +++ b/etherlink/kernel_evm/kernel/src/apply.rs @@ -332,6 +332,7 @@ fn apply_ethereum_transaction_common( is_delayed: bool, limits: &Limits, tracer_input: Option, + fa_bridge_whitelist: Option>, ) -> Result, anyhow::Error> { let effective_gas_price = block_constants.base_fee_per_gas(); let (caller, gas_limit) = match is_valid_ethereum_transaction_common( @@ -371,6 +372,7 @@ fn apply_ethereum_transaction_common( retriable, false, tracer_input, + fa_bridge_whitelist, ) { Ok(outcome) => outcome, Err(err) => { @@ -435,6 +437,7 @@ fn apply_fa_deposit( block_constants: &BlockConstants, precompiles: &PrecompileBTreeMap, allocated_ticks: u64, + fa_bridge_whitelist: Option>, ) -> Result, Error> { let caller = H160::zero(); let outcome = execute_fa_deposit( @@ -446,6 +449,7 @@ fn apply_fa_deposit( caller, fa_deposit, allocated_ticks, + fa_bridge_whitelist, ) .map_err(Error::InvalidRunTransaction)?; @@ -565,6 +569,7 @@ pub fn apply_transaction( sequencer_pool_address: Option, limits: &Limits, tracer_input: Option, + fa_bridge_whitelist: Option>, ) -> Result, anyhow::Error> { let tracer_input = get_tracer_configuration(H256(transaction.tx_hash), tracer_input); let apply_result = match &transaction.content { @@ -579,6 +584,7 @@ pub fn apply_transaction( false, limits, tracer_input, + fa_bridge_whitelist, )?, TransactionContent::EthereumDelayed(tx) => apply_ethereum_transaction_common( host, @@ -591,6 +597,7 @@ pub fn apply_transaction( true, limits, tracer_input, + fa_bridge_whitelist, )?, TransactionContent::Deposit(deposit) => { log!(host, Benchmarking, "Transaction type: DEPOSIT"); @@ -605,6 +612,7 @@ pub fn apply_transaction( block_constants, precompiles, allocated_ticks, + fa_bridge_whitelist, )? } }; diff --git a/etherlink/kernel_evm/kernel/src/block.rs b/etherlink/kernel_evm/kernel/src/block.rs index 7bde65c0e5a2..4f74213b5b73 100644 --- a/etherlink/kernel_evm/kernel/src/block.rs +++ b/etherlink/kernel_evm/kernel/src/block.rs @@ -88,6 +88,7 @@ fn compute( sequencer_pool_address: Option, limits: &Limits, tracer_input: Option, + fa_bridge_whitelist: Option>, ) -> Result { log!( host, @@ -146,6 +147,7 @@ fn compute( sequencer_pool_address, limits, tracer_input, + fa_bridge_whitelist.clone(), )? { ExecutionResult::Valid(ExecutionInfo { receipt_info, @@ -271,6 +273,7 @@ fn compute_bip( chain_id: U256, block_fees: &BlockFees, coinbase: H160, + fa_bridge_whitelist: Option>, ) -> anyhow::Result { let constants: BlockConstants = block_in_progress.constants(chain_id, block_fees, GAS_LIMIT, coinbase); @@ -286,6 +289,7 @@ fn compute_bip( sequencer_pool_address, limits, tracer_input, + fa_bridge_whitelist, )?; match &result { BlockComputationResult::RebootNeeded => { @@ -446,6 +450,7 @@ pub fn produce( chain_id, &block_fees, coinbase, + config.fa_bridge_whitelist.clone(), ) { Ok(BlockComputationResult::Finished { included_delayed_transactions, @@ -530,6 +535,7 @@ pub fn produce( chain_id, &block_fees, coinbase, + config.fa_bridge_whitelist.clone(), ) { Ok(BlockComputationResult::Finished { included_delayed_transactions, @@ -1417,6 +1423,7 @@ mod tests { None, &limits, None, + None, ) .expect("Should safely ask for a reboot"); diff --git a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs index d89d716a55d5..03c73c27c79e 100644 --- a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs +++ b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs @@ -521,6 +521,7 @@ mod tests { }, limits: Limits::default(), enable_fa_bridge: false, + fa_bridge_whitelist: None, }; let dummy_tx_hash = Hash([0u8; TRANSACTION_HASH_SIZE]); diff --git a/etherlink/kernel_evm/kernel/src/configuration.rs b/etherlink/kernel_evm/kernel/src/configuration.rs index fbfd79f46e34..030782641e26 100644 --- a/etherlink/kernel_evm/kernel/src/configuration.rs +++ b/etherlink/kernel_evm/kernel/src/configuration.rs @@ -3,14 +3,15 @@ use crate::{ delayed_inbox::DelayedInbox, storage::{ dal_slots, enable_dal, evm_node_flag, is_enable_fa_bridge, - max_blueprint_lookahead_in_seconds, read_admin, read_delayed_transaction_bridge, - read_kernel_governance, read_kernel_security_governance, - read_maximum_allowed_ticks, read_maximum_gas_per_transaction, - read_sequencer_governance, sequencer, + load_fa_bridge_whitelist, max_blueprint_lookahead_in_seconds, read_admin, + read_delayed_transaction_bridge, read_kernel_governance, + read_kernel_security_governance, read_maximum_allowed_ticks, + read_maximum_gas_per_transaction, read_sequencer_governance, sequencer, }, tick_model::constants::{MAXIMUM_GAS_LIMIT, MAX_ALLOWED_TICKS}, }; use evm_execution::read_ticketer; +use primitive_types::H256; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_evm_logging::{log, Level::*}; use tezos_smart_rollup_debug::Runtime; @@ -72,6 +73,7 @@ pub struct Configuration { pub mode: ConfigurationMode, pub limits: Limits, pub enable_fa_bridge: bool, + pub fa_bridge_whitelist: Option>, } impl Default for Configuration { @@ -81,6 +83,7 @@ impl Default for Configuration { mode: ConfigurationMode::Proxy, limits: Limits::default(), enable_fa_bridge: false, + fa_bridge_whitelist: None, } } } @@ -199,6 +202,7 @@ pub fn fetch_configuration(host: &mut Host) -> Configuration { let enable_fa_bridge = is_enable_fa_bridge(host).unwrap_or_default(); let dal: Option = fetch_dal_configuration(host); let evm_node_flag = evm_node_flag(host).unwrap_or(false); + let fa_bridge_whitelist = load_fa_bridge_whitelist(host).unwrap_or_default(); match sequencer { Some(sequencer) => { let delayed_bridge = read_delayed_transaction_bridge(host) @@ -227,6 +231,7 @@ pub fn fetch_configuration(host: &mut Host) -> Configuration { }, limits, enable_fa_bridge, + fa_bridge_whitelist, }, Err(err) => { log!(host, Fatal, "The kernel failed to created the delayed inbox, reverting configuration to proxy ({:?})", err); @@ -242,6 +247,7 @@ pub fn fetch_configuration(host: &mut Host) -> Configuration { mode: ConfigurationMode::Proxy, limits, enable_fa_bridge, + fa_bridge_whitelist, }, } } diff --git a/etherlink/kernel_evm/kernel/src/inbox.rs b/etherlink/kernel_evm/kernel/src/inbox.rs index af3caca96d78..bba10e3d5ec9 100644 --- a/etherlink/kernel_evm/kernel/src/inbox.rs +++ b/etherlink/kernel_evm/kernel/src/inbox.rs @@ -29,6 +29,7 @@ use crate::upgrade::*; use crate::Error; use crate::{simulation, upgrade}; use evm_execution::fa_bridge::deposit::FaDeposit; +use primitive_types::H256; use rlp::{Decodable, DecoderError, Encodable}; use sha3::{Digest, Keccak256}; use tezos_crypto_rs::hash::ContractKt1Hash; @@ -534,6 +535,7 @@ fn read_and_dispatch_input( inbox_is_empty: &mut bool, res: &mut Mode::Inbox, enable_fa_bridge: bool, + fa_bridge_whitelist: Option>, ) -> anyhow::Result { let input: InputResult = read_input( host, @@ -561,7 +563,11 @@ fn read_and_dispatch_input( // kernel enters in simulation mode, reading will be done by the // simulation and all the previous and next transactions are // discarded. - simulation::start_simulation_mode(host, enable_fa_bridge)?; + simulation::start_simulation_mode( + host, + enable_fa_bridge, + fa_bridge_whitelist, + )?; Ok(ReadStatus::FinishedIgnore) } InputResult::Input(input) => { @@ -576,6 +582,7 @@ pub fn read_proxy_inbox( smart_rollup_address: [u8; 20], tezos_contracts: &TezosContracts, enable_fa_bridge: bool, + fa_bridge_whitelist: Option>, ) -> Result, anyhow::Error> { let mut res = ProxyInboxContent { transactions: vec![], @@ -594,6 +601,7 @@ pub fn read_proxy_inbox( &mut inbox_is_empty, &mut res, enable_fa_bridge, + fa_bridge_whitelist.clone(), ) { Err(err) => // If we failed to read or dispatch the input. @@ -644,6 +652,7 @@ pub fn read_sequencer_inbox( delayed_inbox: &mut DelayedInbox, enable_fa_bridge: bool, dal: Option, + fa_bridge_whitelist: Option>, ) -> Result { // The mutable variable is used to retrieve the information of whether the // inbox was empty or not. As we consume all the inbox in one go, if the @@ -681,6 +690,7 @@ pub fn read_sequencer_inbox( &mut inbox_is_empty, delayed_inbox, enable_fa_bridge, + fa_bridge_whitelist.clone(), ) { Err(err) => // If we failed to read or dispatch the input. @@ -858,6 +868,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap() .unwrap(); @@ -887,6 +898,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap() .unwrap(); @@ -942,6 +954,7 @@ mod tests { kernel_security_governance: None, }, false, + None, ) .unwrap() .unwrap(); @@ -987,6 +1000,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap(); @@ -1035,6 +1049,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap(); @@ -1072,6 +1087,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap(); @@ -1125,6 +1141,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap() .unwrap(); @@ -1144,6 +1161,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap() .unwrap(); @@ -1205,6 +1223,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap() .unwrap(); @@ -1228,6 +1247,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap(); assert!(inbox_content.is_some()); @@ -1238,6 +1258,7 @@ mod tests { SMART_ROLLUP_ADDRESS, &TezosContracts::default(), false, + None, ) .unwrap(); assert!(inbox_content.is_none()); diff --git a/etherlink/kernel_evm/kernel/src/simulation.rs b/etherlink/kernel_evm/kernel/src/simulation.rs index d8fbaac7974d..a234695c837a 100644 --- a/etherlink/kernel_evm/kernel/src/simulation.rs +++ b/etherlink/kernel_evm/kernel/src/simulation.rs @@ -25,7 +25,7 @@ use evm_execution::handler::ExtendedExitReason; use evm_execution::trace::TracerInput; use evm_execution::{account_storage, handler::ExecutionOutcome, precompiles}; use evm_execution::{run_transaction, EthereumError}; -use primitive_types::{H160, U256}; +use primitive_types::{H160, H256, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp}; use sha3::{Digest, Keccak256}; use tezos_ethereum::block::BlockConstants; @@ -372,6 +372,7 @@ impl Evaluation { host: &mut Host, tracer_input: Option, enable_fa_withdrawals: bool, + fa_bridge_whitelist: Option>, ) -> Result, Error> { let chain_id = retrieve_chain_id(host)?; let block_fees = retrieve_block_fees(host)?; @@ -469,6 +470,7 @@ impl Evaluation { false, false, tracer_input, + fa_bridge_whitelist, ) { Ok(Some(outcome)) if !self.with_da_fees => { let result: SimulationResult = @@ -505,6 +507,7 @@ impl TxValidation { transaction: &EthereumTransactionCommon, caller: &H160, enable_fa_withdrawals: bool, + fa_bridge_whitelist: Option>, ) -> Result, anyhow::Error> { let chain_id = retrieve_chain_id(host)?; let block_fees = retrieve_block_fees(host)?; @@ -575,6 +578,7 @@ impl TxValidation { false, false, None, + fa_bridge_whitelist, ) { Ok(Some(ExecutionOutcome { reason: ExtendedExitReason::OutOfTicks, @@ -610,6 +614,7 @@ impl TxValidation { &self, host: &mut Host, enable_fa_withdrawals: bool, + fa_bridge_whitelist: Option>, ) -> Result, anyhow::Error> { let tx = &self.transaction; let evm_account_storage = account_storage::init_account_storage()?; @@ -642,7 +647,13 @@ impl TxValidation { } // Check if running the transaction (assuming it is valid) would run out // of ticks, or fail validation for another reason. - Self::validate(host, tx, &caller, enable_fa_withdrawals) + Self::validate( + host, + tx, + &caller, + enable_fa_withdrawals, + fa_bridge_whitelist, + ) } } @@ -786,17 +797,24 @@ impl VersionedEncoding for SimulationResult pub fn start_simulation_mode( host: &mut Host, enable_fa_withdrawals: bool, + fa_bridge_whitelist: Option>, ) -> Result<(), anyhow::Error> { log!(host, Debug, "Starting simulation mode "); let simulation = parse_inbox(host)?; match simulation { Message::Evaluation(simulation) => { let tracer_input = read_tracer_input(host)?; - let outcome = simulation.run(host, tracer_input, enable_fa_withdrawals)?; + let outcome = simulation.run( + host, + tracer_input, + enable_fa_withdrawals, + fa_bridge_whitelist, + )?; storage::store_simulation_result(host, outcome) } Message::TxValidation(tx_validation) => { - let outcome = tx_validation.run(host, enable_fa_withdrawals)?; + let outcome = + tx_validation.run(host, enable_fa_withdrawals, fa_bridge_whitelist)?; storage::store_simulation_result(host, outcome) } } @@ -935,6 +953,7 @@ mod tests { false, false, None, + None, ); assert!(outcome.is_ok(), "contract should have been created"); let outcome = outcome.unwrap(); @@ -962,7 +981,7 @@ mod tests { with_da_fees: false, timestamp: None, }; - let outcome = evaluation.run(&mut host, None, false); + let outcome = evaluation.run(&mut host, None, false, None); assert!(outcome.is_ok(), "evaluation should have succeeded"); let outcome = outcome.unwrap(); @@ -988,7 +1007,7 @@ mod tests { with_da_fees: false, timestamp: None, }; - let outcome = evaluation.run(&mut host, None, false); + let outcome = evaluation.run(&mut host, None, false, None); assert!(outcome.is_ok(), "simulation should have succeeded"); let outcome = outcome.unwrap(); @@ -1020,7 +1039,7 @@ mod tests { with_da_fees: false, timestamp: None, }; - let outcome = evaluation.run(&mut host, None, false); + let outcome = evaluation.run(&mut host, None, false, None); assert!(outcome.is_ok(), "evaluation should have succeeded"); let outcome = outcome.unwrap(); @@ -1252,7 +1271,7 @@ mod tests { .unwrap(), ) .unwrap(); - let result = simulation.run(&mut host, false); + let result = simulation.run(&mut host, false, None); println!("{result:?}"); assert!(result.is_ok()); assert_eq!( @@ -1301,7 +1320,7 @@ mod tests { .unwrap(), ) .unwrap(); - let result = simulation.run(&mut host, false); + let result = simulation.run(&mut host, false, None); assert!(result.is_ok()); assert_eq!( diff --git a/etherlink/kernel_evm/kernel/src/stage_one.rs b/etherlink/kernel_evm/kernel/src/stage_one.rs index 00ff6fad7890..2d7680f7f320 100644 --- a/etherlink/kernel_evm/kernel/src/stage_one.rs +++ b/etherlink/kernel_evm/kernel/src/stage_one.rs @@ -12,6 +12,7 @@ use crate::delayed_inbox::DelayedInbox; use crate::inbox::{read_proxy_inbox, read_sequencer_inbox}; use crate::inbox::{ProxyInboxContent, StageOneStatus}; use anyhow::Ok; +use primitive_types::H256; use std::ops::Add; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_ethereum::block::L2Block; @@ -25,12 +26,14 @@ pub fn fetch_proxy_blueprints( smart_rollup_address: [u8; RAW_ROLLUP_ADDRESS_SIZE], tezos_contracts: &TezosContracts, enable_fa_bridge: bool, + fa_bridge_whitelist: Option>, ) -> Result { if let Some(ProxyInboxContent { transactions }) = read_proxy_inbox( host, smart_rollup_address, tezos_contracts, enable_fa_bridge, + fa_bridge_whitelist, )? { let timestamp = current_timestamp(host); let blueprint = Blueprint { @@ -103,6 +106,7 @@ fn fetch_sequencer_blueprints( sequencer: PublicKey, dal: Option, enable_fa_bridge: bool, + fa_bridge_whitelist: Option>, ) -> Result { match read_sequencer_inbox( host, @@ -113,6 +117,7 @@ fn fetch_sequencer_blueprints( delayed_inbox, enable_fa_bridge, dal, + fa_bridge_whitelist, )? { StageOneStatus::Done => { // Check if there are timed-out transactions in the delayed inbox @@ -154,12 +159,14 @@ pub fn fetch_blueprints( sequencer.clone(), dal.clone(), config.enable_fa_bridge, + config.fa_bridge_whitelist.clone(), ), ConfigurationMode::Proxy => fetch_proxy_blueprints( host, smart_rollup_address, &config.tezos_contracts, config.enable_fa_bridge, + config.fa_bridge_whitelist.clone(), ), } } @@ -223,6 +230,7 @@ mod tests { }, limits: Limits::default(), enable_fa_bridge: false, + fa_bridge_whitelist: None, } } @@ -236,6 +244,7 @@ mod tests { mode: ConfigurationMode::Proxy, limits: Limits::default(), enable_fa_bridge: false, + fa_bridge_whitelist: None, } } @@ -451,6 +460,7 @@ mod tests { DEFAULT_SR_ADDRESS, &conf.tezos_contracts, false, + None, ) .unwrap() { diff --git a/etherlink/kernel_evm/kernel/src/storage.rs b/etherlink/kernel_evm/kernel/src/storage.rs index c7258cfcc0fd..d62fe84fe393 100644 --- a/etherlink/kernel_evm/kernel/src/storage.rs +++ b/etherlink/kernel_evm/kernel/src/storage.rs @@ -183,6 +183,10 @@ const EVM_NODE_FLAG: RefPath = RefPath::assert_from(b"/__evm_node"); const MAX_BLUEPRINT_LOOKAHEAD_IN_SECONDS: RefPath = RefPath::assert_from(b"/evm/max_blueprint_lookahead_in_seconds"); +// If this key is set, kernel needs to initialize the whitelist used to check FA bridge proxy contract code. +// In order to do that, load the stored value and split into 32-byte chunks +const FA_BRIDGE_WHITELIST: RefPath = RefPath::assert_from(b"/evm/fa_bridge_whitelist"); + pub fn store_read_slice( host: &Host, path: &T, @@ -1168,6 +1172,22 @@ pub fn max_blueprint_lookahead_in_seconds(host: &impl Runtime) -> anyhow::Result Ok(i64::from_le_bytes(bytes)) } +pub fn load_fa_bridge_whitelist( + host: &impl Runtime, +) -> anyhow::Result>> { + if let Some(ValueType::Value) = host.store_has(&FA_BRIDGE_WHITELIST)? { + let bytes = host.store_read_all(&FA_BRIDGE_WHITELIST)?; + if bytes.len() % 32 == 0 { + let whitelist = bytes.chunks_exact(32).map(H256::from_slice).collect(); + Ok(Some(whitelist)) + } else { + anyhow::bail!("Incorrect whitelist length") + } + } else { + Ok(None) + } +} + #[cfg(test)] mod internal_for_tests { use super::*; -- GitLab