diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9fa54..db2105e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Supported `polkadot-sdk` rev: `unstable2507` - Instruct the LLVM backend and linker to `--relax` (may lead to smaller contract code size). - Standard JSON mode: Don't forward EVM bytecode related output selections to solc. - The supported `polkadot-sdk` release is `unstable2507`. +- The `INVALID` opcode and OOB memory accesses now consume all remaining gas. ### Fixed: - The missing `STOP` instruction at the end of `code` blocks. diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json index 8e3ca89..747fc0c 100644 --- a/crates/integration/codesize.json +++ b/crates/integration/codesize.json @@ -1,10 +1,10 @@ { "Baseline": 911, - "Computation": 2293, - "DivisionArithmetics": 14353, - "ERC20": 16936, + "Computation": 2337, + "DivisionArithmetics": 14488, + "ERC20": 17041, "Events": 1672, "FibonacciIterative": 1454, - "Flipper": 2083, - "SHA1": 7727 + "Flipper": 2106, + "SHA1": 7814 } \ No newline at end of file diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs index 4e47276..9822f17 100644 --- a/crates/integration/src/tests.rs +++ b/crates/integration/src/tests.rs @@ -669,3 +669,48 @@ fn sbrk_bounds_checks() { "not seeing a trap means the contract did not catch the OOB" ); } + +#[test] +fn invalid_opcode_works() { + let code = &build_yul(&[( + "invalid.yul", + r#"object "Test" { + code { + invalid() + } + object "Test_deployed" { + code { + invalid() + } + } +}"#, + )]) + .unwrap()["invalid.yul:Test"]; + + let results = Specs { + actions: vec![ + Instantiate { + origin: TestAddress::Alice, + value: 0, + gas_limit: Some(GAS_LIMIT), + storage_deposit_limit: None, + code: Code::Bytes(code.to_vec()), + data: Default::default(), + salt: OptionalHex::default(), + }, + VerifyCall(VerifyCallExpectation { + success: false, + ..Default::default() + }), + ], + differential: false, + ..Default::default() + } + .run(); + + let CallResult::Instantiate { result, .. } = results.last().unwrap() else { + unreachable!() + }; + + assert_eq!(result.weight_consumed, GAS_LIMIT); +} diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/revive.rs b/crates/llvm-context/src/polkavm/context/function/runtime/revive.rs index 90b930b..345a180 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/revive.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/revive.rs @@ -51,11 +51,11 @@ impl RuntimeFunction for WordToPointer { )?; let block_continue = context.append_basic_block("offset_pointer_ok"); - let block_trap = context.append_basic_block("offset_pointer_overflow"); - context.build_conditional_branch(is_overflow, block_trap, block_continue)?; + let block_invalid = context.append_basic_block("offset_pointer_overflow"); + context.build_conditional_branch(is_overflow, block_invalid, block_continue)?; - context.set_basic_block(block_trap); - context.build_call(context.intrinsics().trap, &[], "invalid_trap"); + context.set_basic_block(block_invalid); + context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]); context.build_unreachable(); context.set_basic_block(block_continue); diff --git a/crates/llvm-context/src/polkavm/evm/return.rs b/crates/llvm-context/src/polkavm/evm/return.rs index 6da9b2e..84a7bd9 100644 --- a/crates/llvm-context/src/polkavm/evm/return.rs +++ b/crates/llvm-context/src/polkavm/evm/return.rs @@ -52,12 +52,14 @@ pub fn stop(context: &mut Context) -> anyhow::Result<()> { /// Translates the `invalid` instruction. /// Burns all gas using an out-of-bounds memory store, causing a panic. pub fn invalid(context: &mut Context) -> anyhow::Result<()> { - crate::polkavm::evm::memory::store( - context, - context.word_type().const_all_ones(), - context.word_const(0), - )?; - context.build_call(context.intrinsics().trap, &[], "invalid_trap"); + let invalid_block = context.append_basic_block("explicit_invalid"); + context.build_unconditional_branch(invalid_block); + context.set_basic_block(invalid_block); + context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]); + context.build_unreachable(); + + context.set_basic_block(context.append_basic_block("dead_code")); + Ok(()) } diff --git a/crates/runtime-api/src/polkavm_imports.c b/crates/runtime-api/src/polkavm_imports.c index 935b211..81a99b9 100644 --- a/crates/runtime-api/src/polkavm_imports.c +++ b/crates/runtime-api/src/polkavm_imports.c @@ -69,6 +69,8 @@ POLKAVM_IMPORT(uint64_t, code_size, uint32_t) POLKAVM_IMPORT(void, code_hash, uint32_t, uint32_t) +POLKAVM_IMPORT(void, consume_all_gas) + POLKAVM_IMPORT(uint32_t, delegate_call, uint64_t, uint64_t, uint64_t, uint32_t, uint64_t, uint64_t) POLKAVM_IMPORT(void, deposit_event, uint32_t, uint32_t, uint32_t, uint32_t) diff --git a/crates/runtime-api/src/polkavm_imports.rs b/crates/runtime-api/src/polkavm_imports.rs index 073e6d4..7158ed5 100644 --- a/crates/runtime-api/src/polkavm_imports.rs +++ b/crates/runtime-api/src/polkavm_imports.rs @@ -48,6 +48,8 @@ pub static HASH_KECCAK_256: &str = "hash_keccak_256"; pub static INSTANTIATE: &str = "instantiate"; +pub static INVALID: &str = "consume_all_gas"; + pub static NOW: &str = "now"; pub static ORIGIN: &str = "origin"; @@ -70,7 +72,7 @@ pub static VALUE_TRANSFERRED: &str = "value_transferred"; /// All imported runtime API symbols. /// Useful for configuring common attributes and linkage. -pub static IMPORTS: [&str; 33] = [ +pub static IMPORTS: [&str; 34] = [ ADDRESS, BALANCE, BALANCE_OF, @@ -94,6 +96,7 @@ pub static IMPORTS: [&str; 33] = [ GET_STORAGE, HASH_KECCAK_256, INSTANTIATE, + INVALID, NOW, ORIGIN, REF_TIME_LEFT,