diff --git a/crates/interpreter/src/instructions/bitwise.rs b/crates/interpreter/src/instructions/bitwise.rs index 586af76c..a8412b3a 100644 --- a/crates/interpreter/src/instructions/bitwise.rs +++ b/crates/interpreter/src/instructions/bitwise.rs @@ -135,7 +135,7 @@ mod tests { #[test] fn test_shift_left() { let mut host = DummyHost::new(Env::default()); - let mut interpreter = Interpreter::new(Contract::default(), u64::MAX, false); + let mut interpreter = Interpreter::new(Contract::default(), u64::MAX, false, Vec::new()); struct TestCase { value: U256, @@ -216,7 +216,7 @@ mod tests { #[test] fn test_logical_shift_right() { let mut host = DummyHost::new(Env::default()); - let mut interpreter = Interpreter::new(Contract::default(), u64::MAX, false); + let mut interpreter = Interpreter::new(Contract::default(), u64::MAX, false, Vec::new()); struct TestCase { value: U256, @@ -297,7 +297,7 @@ mod tests { #[test] fn test_arithmetic_shift_right() { let mut host = DummyHost::new(Env::default()); - let mut interpreter = Interpreter::new(Contract::default(), u64::MAX, false); + let mut interpreter = Interpreter::new(Contract::default(), u64::MAX, false, Vec::new()); struct TestCase { value: U256, diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index 926dd963..75f2cdd8 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -10,7 +10,7 @@ use crate::{ SStoreResult, Transfer, MAX_INITCODE_SIZE, }; use core::cmp::min; -use revm_primitives::{Asset, BLOCK_HASH_HISTORY}; +use revm_primitives::{Asset, BASE_ASSET_ID, BLOCK_HASH_HISTORY}; /// EIP-1884: Repricing for trie-size-dependent opcodes pub fn selfbalance(interpreter: &mut Interpreter, host: &mut H) { @@ -21,10 +21,33 @@ pub fn selfbalance(interpreter: &mut Interpreter, return; }; - // TODO: take MNAs into account here push!(interpreter, balance); } +/// EIP-1884: Repricing for trie-size-dependent opcodes +pub fn self_mna_balances( + interpreter: &mut Interpreter, + host: &mut H, +) { + check!(interpreter, ISTANBUL); + gas!(interpreter, gas::LOW); + + for asset_id in interpreter.asset_ids.iter() { + // Get the balance of the contract for the asset_id + let Some((balance, _)) = host.balance(*asset_id, interpreter.contract.address) else { + interpreter.instruction_result = InstructionResult::FatalExternalError; + return; + }; + + // Push balance and asset_id to the stack + push!(interpreter, balance); + push_b256!(interpreter, *asset_id); + } + + // Push the number of assets to the stack + push!(interpreter, U256::from(interpreter.asset_ids.len() as u64)); +} + pub fn extcodesize(interpreter: &mut Interpreter, host: &mut H) { pop_address!(interpreter, address); let Some((code, is_cold)) = host.code(address) else { @@ -224,15 +247,24 @@ fn pop_transferred_assets(interpreter: &mut Interpreter, transferred_assets: &mu } } -fn get_transferred_assets(interpreter: &mut Interpreter) -> Vec { - let mut transferred_assets = Vec::new(); - pop_transferred_assets(interpreter, &mut transferred_assets); - transferred_assets +pub fn create( + interpreter: &mut Interpreter, + host: &mut H, +) { + create_inner::(interpreter, host, false); } -pub fn create( +pub fn mna_create( + interpreter: &mut Interpreter, + host: &mut H, +) { + create_inner::(interpreter, host, true); +} + +fn create_inner( interpreter: &mut Interpreter, host: &mut H, + is_mna_create: bool, ) { // Dev: deploying smart contracts is not allowed for general public // TODO: implement a way to allow deploying smart contracts by Sablier @@ -246,7 +278,18 @@ pub fn create( } let mut transferred_assets = Vec::::new(); - pop_transferred_assets(interpreter, transferred_assets.as_mut()); + + if is_mna_create { + pop_transferred_assets(interpreter, transferred_assets.as_mut()); + } else { + pop!(interpreter, value); + if value != U256::ZERO { + transferred_assets.push(Asset { + id: BASE_ASSET_ID, + amount: value, + }); + } + } pop!(interpreter, code_offset, len); let len = as_usize_or_fail!(interpreter, len); @@ -307,12 +350,37 @@ pub fn create( } pub fn call(interpreter: &mut Interpreter, host: &mut H) { + call_inner::(interpreter, host, false); +} + +pub fn mna_call(interpreter: &mut Interpreter, host: &mut H) { + call_inner::(interpreter, host, true); +} + +fn call_inner( + interpreter: &mut Interpreter, + host: &mut H, + is_mna_call: bool, +) { pop!(interpreter, local_gas_limit); pop_address!(interpreter, to); // max gas limit is not possible in real ethereum situation. let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - let transferred_assets = get_transferred_assets(interpreter); + let mut transferred_assets = Vec::::new(); + + if is_mna_call { + pop_transferred_assets(interpreter, transferred_assets.as_mut()); + } else { + pop!(interpreter, value); + if value != U256::ZERO { + transferred_assets.push(Asset { + id: BASE_ASSET_ID, + amount: value, + }); + } + } + if interpreter.is_static && !transferred_assets.is_empty() { interpreter.instruction_result = InstructionResult::CallNotAllowedInsideStatic; return; @@ -367,12 +435,37 @@ pub fn call(interpreter: &mut Interpreter, host: & } pub fn call_code(interpreter: &mut Interpreter, host: &mut H) { + call_code_inner::(interpreter, host, false); +} + +pub fn mna_call_code(interpreter: &mut Interpreter, host: &mut H) { + call_code_inner::(interpreter, host, true); +} + +fn call_code_inner( + interpreter: &mut Interpreter, + host: &mut H, + is_mna_call_code: bool, +) { pop!(interpreter, local_gas_limit); pop_address!(interpreter, to); // max gas limit is not possible in real ethereum situation. let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - let transferred_assets = get_transferred_assets(interpreter); + let mut transferred_assets = Vec::::new(); + + if is_mna_call_code { + pop_transferred_assets(interpreter, transferred_assets.as_mut()); + } else { + pop!(interpreter, value); + if value != U256::ZERO { + transferred_assets.push(Asset { + id: BASE_ASSET_ID, + amount: value, + }); + } + } + let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else { return; }; @@ -457,7 +550,7 @@ pub fn delegate_call(interpreter: &mut Interpreter address: interpreter.contract.address, caller: interpreter.contract.caller, code_address: to, - apparent_assets: interpreter.contract.assets.clone(), + apparent_assets: interpreter.contract.call_assets.clone(), scheme: CallScheme::DelegateCall, }, is_static: interpreter.is_static, @@ -513,6 +606,14 @@ pub fn static_call(interpreter: &mut Interpreter, } pub fn balance(interpreter: &mut Interpreter, host: &mut H) { + pop_address!(interpreter, address); + push_b256!(interpreter, BASE_ASSET_ID); + push_b256!(interpreter, address.into_word()); + + mna_balance::(interpreter, host); +} + +pub fn mna_balance(interpreter: &mut Interpreter, host: &mut H) { pop_address!(interpreter, address); pop!(interpreter, asset_id); diff --git a/crates/interpreter/src/instructions/opcode.rs b/crates/interpreter/src/instructions/opcode.rs index be77a88a..85327c01 100644 --- a/crates/interpreter/src/instructions/opcode.rs +++ b/crates/interpreter/src/instructions/opcode.rs @@ -216,18 +216,18 @@ opcodes! { // 0x2B // 0x2C // 0x2D - // 0x2E - // 0x2F - 0x30 => ADDRESS => system::address, - 0x31 => BALANCE => host::balance::, - 0x32 => ORIGIN => host_env::origin, - 0x33 => CALLER => system::caller, - 0x34 => CALLVALUES => system::callvalues, - 0x35 => CALLDATALOAD => system::calldataload, - 0x36 => CALLDATASIZE => system::calldatasize, - 0x37 => CALLDATACOPY => system::calldatacopy, - 0x38 => CODESIZE => system::codesize, - 0x39 => CODECOPY => system::codecopy, + 0x2E => MNABALANCE => host::mna_balance::, + 0x2F => MNACALLVALUES => system::mna_callvalues, + 0x30 => ADDRESS => system::address, + 0x31 => BALANCE => host::balance::, + 0x32 => ORIGIN => host_env::origin, + 0x33 => CALLER => system::caller, + 0x34 => CALLVALUE => system::callvalue, + 0x35 => CALLDATALOAD => system::calldataload, + 0x36 => CALLDATASIZE => system::calldatasize, + 0x37 => CALLDATACOPY => system::calldatacopy, + 0x38 => CODESIZE => system::codesize, + 0x39 => CODECOPY => system::codecopy, 0x3A => GASPRICE => host_env::gasprice, 0x3B => EXTCODESIZE => host::extcodesize::, @@ -413,15 +413,15 @@ opcodes! { // 0xEB // 0xEC // 0xED - // 0xEE - // 0xEF + 0xEE => MNACALL => host::mna_call::, + 0xEF => MNACALLCODE => host::mna_call_code::, 0xF0 => CREATE => host::create::, 0xF1 => CALL => host::call::, 0xF2 => CALLCODE => host::call_code::, 0xF3 => RETURN => control::ret, 0xF4 => DELEGATECALL => host::delegate_call::, 0xF5 => CREATE2 => host::create::, - // 0xF6 + 0xF6 => MNACREATE => host::mna_create::, // 0xF7 // 0xF8 // 0xF9 @@ -511,7 +511,9 @@ impl OpCode { | OpCode::CALLDATACOPY | OpCode::RETURNDATACOPY | OpCode::CALL + | OpCode::MNACALL | OpCode::CALLCODE + | OpCode::MNACALLCODE | OpCode::DELEGATECALL | OpCode::STATICCALL ) @@ -652,13 +654,13 @@ const fn opcode_gas_info(opcode: u8, spec: SpecId) -> OpInfo { 0x2B => OpInfo::none(), 0x2C => OpInfo::none(), 0x2D => OpInfo::none(), - 0x2E => OpInfo::none(), - 0x2F => OpInfo::none(), + MNABALANCE => OpInfo::dynamic_gas(), + MNACALLVALUES => OpInfo::gas(gas::BASE), ADDRESS => OpInfo::gas(gas::BASE), BALANCE => OpInfo::dynamic_gas(), ORIGIN => OpInfo::gas(gas::BASE), CALLER => OpInfo::gas(gas::BASE), - CALLVALUES => OpInfo::gas(gas::BASE), + CALLVALUE => OpInfo::gas(gas::BASE), CALLDATALOAD => OpInfo::gas(gas::VERYLOW), CALLDATASIZE => OpInfo::gas(gas::BASE), CALLDATACOPY => OpInfo::dynamic_gas(), @@ -905,15 +907,15 @@ const fn opcode_gas_info(opcode: u8, spec: SpecId) -> OpInfo { 0xEB => OpInfo::none(), 0xEC => OpInfo::none(), 0xED => OpInfo::none(), - 0xEE => OpInfo::none(), - 0xEF => OpInfo::none(), + MNACALL => OpInfo::gas_block_end(0), + MNACALLCODE => OpInfo::gas_block_end(0), CREATE => OpInfo::gas_block_end(0), CALL => OpInfo::gas_block_end(0), CALLCODE => OpInfo::gas_block_end(0), RETURN => OpInfo::gas_block_end(0), DELEGATECALL => OpInfo::gas_block_end(0), CREATE2 => OpInfo::gas_block_end(0), - 0xF6 => OpInfo::none(), + MNACREATE => OpInfo::gas_block_end(0), 0xF7 => OpInfo::none(), 0xF8 => OpInfo::none(), 0xF9 => OpInfo::none(), diff --git a/crates/interpreter/src/instructions/system.rs b/crates/interpreter/src/instructions/system.rs index 75cb641d..b033a4cd 100644 --- a/crates/interpreter/src/instructions/system.rs +++ b/crates/interpreter/src/instructions/system.rs @@ -1,6 +1,6 @@ use crate::{ gas, - primitives::{Spec, B256, KECCAK_EMPTY, U256}, + primitives::{Spec, B256, BASE_ASSET_ID, KECCAK_EMPTY, U256}, Host, InstructionResult, Interpreter, }; use core::ptr; @@ -82,16 +82,32 @@ pub fn calldatasize(interpreter: &mut Interpreter, _host: &mut push!(interpreter, U256::from(interpreter.contract.input.len())); } -pub fn callvalues(interpreter: &mut Interpreter, _host: &mut H) { +pub fn callvalue(interpreter: &mut Interpreter, _host: &mut H) { + gas!(interpreter, gas::BASE); + + let base_asset_amount = interpreter + .contract + .call_assets + .iter() + .find(|asset| asset.id == BASE_ASSET_ID) + .map(|asset| asset.amount) + .unwrap_or(U256::ZERO); + push!(interpreter, base_asset_amount); +} + +pub fn mna_callvalues(interpreter: &mut Interpreter, _host: &mut H) { // TODO: make the gas cost proportional to the MNAs number gas!(interpreter, gas::BASE); - for asset in &interpreter.contract.assets { + for asset in &interpreter.contract.call_assets { push!(interpreter, asset.amount); push!(interpreter, asset.id.into()); } - push!(interpreter, U256::from(interpreter.contract.assets.len())); + push!( + interpreter, + U256::from(interpreter.contract.call_assets.len()) + ); } pub fn calldatacopy(interpreter: &mut Interpreter, _host: &mut H) { diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index b68a10ba..7714b7ad 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -13,7 +13,7 @@ use crate::{ CreateInputs, CreateOutcome, Gas, Host, InstructionResult, }; use core::cmp::min; -use revm_primitives::U256; +use revm_primitives::{B256, U256}; /// EVM bytecode interpreter. #[derive(Debug)] @@ -47,6 +47,9 @@ pub struct Interpreter { /// Set inside CALL or CREATE instructions and RETURN or REVERT instructions. Additionally those instructions will set /// InstructionResult to CallOrCreate/Return/Revert so we know the reason. pub next_action: InterpreterAction, + + // The ids of the assets recognized by the VM + pub asset_ids: Vec, } /// The result of an interpreter operation. @@ -113,7 +116,7 @@ impl InterpreterAction { impl Interpreter { /// Create new interpreter - pub fn new(contract: Contract, gas_limit: u64, is_static: bool) -> Self { + pub fn new(contract: Contract, gas_limit: u64, is_static: bool, asset_ids: Vec) -> Self { Self { instruction_pointer: contract.bytecode.as_ptr(), contract, @@ -124,6 +127,7 @@ impl Interpreter { shared_memory: EMPTY_SHARED_MEMORY, stack: Stack::new(), next_action: InterpreterAction::None, + asset_ids, } } @@ -364,7 +368,7 @@ mod tests { #[test] fn object_safety() { - let mut interp = Interpreter::new(Contract::default(), u64::MAX, false); + let mut interp = Interpreter::new(Contract::default(), u64::MAX, false, Vec::new()); let mut host = crate::DummyHost::default(); let table: InstructionTable = diff --git a/crates/interpreter/src/interpreter/contract.rs b/crates/interpreter/src/interpreter/contract.rs index 641eb329..e18183ab 100644 --- a/crates/interpreter/src/interpreter/contract.rs +++ b/crates/interpreter/src/interpreter/contract.rs @@ -16,8 +16,8 @@ pub struct Contract { pub address: Address, /// Caller of the EVM. pub caller: Address, - /// Assets sent to the contract. - pub assets: Vec, + /// Assets sent to the contract in the current call. + pub call_assets: Vec, } impl Contract { @@ -39,7 +39,7 @@ impl Contract { hash, address, caller, - assets, + call_assets: assets, } } diff --git a/crates/revm/benches/bench.rs b/crates/revm/benches/bench.rs index 20c769d0..22302d6b 100644 --- a/crates/revm/benches/bench.rs +++ b/crates/revm/benches/bench.rs @@ -5,7 +5,7 @@ use revm::{ db::BenchmarkDB, interpreter::{analysis::to_analysed, BytecodeLocked, Contract, DummyHost, Interpreter}, primitives::{ - address, bytes, hex, Asset, BerlinSpec, Bytecode, BytecodeState, Bytes, TransactTo, B256, + address, bytes, hex, Asset, BerlinSpec, Bytecode, BytecodeState, Bytes, TransactTo, BASE_ASSET_ID, U256, }, Evm, @@ -118,7 +118,7 @@ fn bench_eval(g: &mut BenchmarkGroup<'_, WallTime>, evm: &mut Evm<'static, (), B // replace memory with empty memory to use it inside interpreter. // Later return memory back. let temp = core::mem::replace(&mut shared_memory, EMPTY_SHARED_MEMORY); - let mut interpreter = Interpreter::new(contract.clone(), u64::MAX, false); + let mut interpreter = Interpreter::new(contract.clone(), u64::MAX, false, Vec::new()); let res = interpreter.run(temp, &instruction_table, &mut host); shared_memory = interpreter.take_memory(); host.clear(); diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index d047b90b..f65777f5 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -207,7 +207,12 @@ impl EvmContext { Ok(FrameOrResult::new_call_frame( inputs.return_memory_offset.clone(), checkpoint, - Interpreter::new(contract, gas.limit(), inputs.is_static), + Interpreter::new( + contract, + gas.limit(), + inputs.is_static, + self.journaled_state.state.asset_ids.clone(), + ), )) } else { self.journaled_state.checkpoint_commit(); diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index 6acf703a..c62f0194 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -306,7 +306,12 @@ impl InnerEvmContext { Ok(FrameOrResult::new_create_frame( created_address, checkpoint, - Interpreter::new(contract, gas.limit(), false), + Interpreter::new( + contract, + gas.limit(), + false, + self.journaled_state.state.asset_ids.clone(), + ), )) }