diff --git a/CHANGELOG.md b/CHANGELOG.md index 86cb475..aa88479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This is a development pre-release. Supported `polkadot-sdk` rev: `2509.0.0` +### Added +- Support for `selfdestruct`. + ### Changed - Emulated EVM heap memory accesses of zero length are never out of bounds. diff --git a/crates/integration/contracts/Selfdestruct.sol b/crates/integration/contracts/Selfdestruct.sol new file mode 100644 index 0000000..1d5f2fb --- /dev/null +++ b/crates/integration/contracts/Selfdestruct.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +// TODO: This currently fails the differential test. +// The pallet doesn't send the correct balance back. + +/* runner.json +{ + "differential": false, + "actions": [ + { + "Upload": { + "code": { + "Solidity": { + "contract": "SelfdestructTester" + } + } + } + }, + { + "Instantiate": { + "code": { + "Solidity": { + "contract": "Selfdestruct" + } + }, + "value": 123456789 + } + }, + { + "Call": { + "dest": { + "Instantiated": 0 + } + } + } + ] +} +*/ + +contract Selfdestruct { + address tester; + uint value; + + constructor() payable { + require(msg.value > 0, "the test should have value"); + value = msg.value; + + SelfdestructTester s = new SelfdestructTester{value: msg.value}(); + tester = address(s); + } + + fallback() external { + (bool success, ) = tester.call(hex""); + require(success, "the call to the self destructing contract should succeed"); + } +} + +contract SelfdestructTester { + constructor() payable {} + + fallback() external { + selfdestruct(payable(msg.sender)); + } +} diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs index ae8bb3f..447db43 100644 --- a/crates/integration/src/tests.rs +++ b/crates/integration/src/tests.rs @@ -63,6 +63,7 @@ test_spec!(layout_at, "LayoutAt", "LayoutAt.sol"); test_spec!(shift_arithmetic_right, "SAR", "SAR.sol"); test_spec!(add_mod_mul_mod, "AddModMulModTester", "AddModMulMod.sol"); test_spec!(memory_bounds, "MemoryBounds", "MemoryBounds.sol"); +test_spec!(selfdestruct, "Selfdestruct", "Selfdestruct.sol"); fn instantiate(path: &str, contract: &str) -> Vec { vec![Instantiate { diff --git a/crates/llvm-context/src/polkavm/evm/return.rs b/crates/llvm-context/src/polkavm/evm/return.rs index 3ad665d..6da9b2e 100644 --- a/crates/llvm-context/src/polkavm/evm/return.rs +++ b/crates/llvm-context/src/polkavm/evm/return.rs @@ -60,3 +60,16 @@ pub fn invalid(context: &mut Context) -> anyhow::Result<()> { context.build_call(context.intrinsics().trap, &[], "invalid_trap"); Ok(()) } + +/// Translates the `selfdestruct` instruction. +pub fn selfdestruct<'ctx>( + context: &mut Context<'ctx>, + address: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> { + let address_pointer = context.build_address_argument_store(address)?; + context.build_runtime_call( + revive_runtime_api::polkavm_imports::TERMINATE, + &[address_pointer.to_int(context).into()], + ); + Ok(()) +} diff --git a/crates/resolc/src/tests/unit/unsupported_opcodes.rs b/crates/resolc/src/tests/unit/unsupported_opcodes.rs index 6322ea7..032501b 100644 --- a/crates/resolc/src/tests/unit/unsupported_opcodes.rs +++ b/crates/resolc/src/tests/unit/unsupported_opcodes.rs @@ -98,26 +98,3 @@ contract ExternalCodeCopy { build_solidity(sources(&[("test.sol", code)])).unwrap(); } - -#[test] -#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")] -fn selfdestruct_yul() { - let solidity = r#" -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract MinimalDestructible { - address payable public owner; - - constructor() { - owner = payable(msg.sender); - } - - function destroy() public { - require(msg.sender == owner, "Only the owner can call this function."); - selfdestruct(owner); - } -}"#; - - build_solidity(sources(&[("test.sol", solidity)])).unwrap(); -} diff --git a/crates/runtime-api/src/polkavm_imports.c b/crates/runtime-api/src/polkavm_imports.c index ffdec9e..935b211 100644 --- a/crates/runtime-api/src/polkavm_imports.c +++ b/crates/runtime-api/src/polkavm_imports.c @@ -101,4 +101,6 @@ POLKAVM_IMPORT(void, set_immutable_data, uint32_t, uint32_t); POLKAVM_IMPORT(uint32_t, set_storage_or_clear, uint32_t, uint32_t, uint32_t) +POLKAVM_IMPORT(void, terminate, uint32_t) + POLKAVM_IMPORT(void, value_transferred, uint32_t) diff --git a/crates/runtime-api/src/polkavm_imports.rs b/crates/runtime-api/src/polkavm_imports.rs index 3881e32..073e6d4 100644 --- a/crates/runtime-api/src/polkavm_imports.rs +++ b/crates/runtime-api/src/polkavm_imports.rs @@ -64,11 +64,13 @@ pub static SET_IMMUTABLE_DATA: &str = "set_immutable_data"; pub static SET_STORAGE: &str = "set_storage_or_clear"; +pub static TERMINATE: &str = "terminate"; + pub static VALUE_TRANSFERRED: &str = "value_transferred"; /// All imported runtime API symbols. /// Useful for configuring common attributes and linkage. -pub static IMPORTS: [&str; 32] = [ +pub static IMPORTS: [&str; 33] = [ ADDRESS, BALANCE, BALANCE_OF, @@ -100,6 +102,7 @@ pub static IMPORTS: [&str; 32] = [ RETURNDATASIZE, SET_IMMUTABLE_DATA, SET_STORAGE, + TERMINATE, VALUE_TRANSFERRED, ]; diff --git a/crates/yul/src/parser/statement/expression/function_call/mod.rs b/crates/yul/src/parser/statement/expression/function_call/mod.rs index 20bd34d..b62b0cf 100644 --- a/crates/yul/src/parser/statement/expression/function_call/mod.rs +++ b/crates/yul/src/parser/statement/expression/function_call/mod.rs @@ -661,6 +661,14 @@ impl FunctionCall { Name::Invalid => { revive_llvm_context::polkavm_evm_return::invalid(context).map(|_| None) } + Name::SelfDestruct => { + let arguments = self.pop_arguments_llvm::<1>(context)?; + revive_llvm_context::polkavm_evm_return::selfdestruct( + context, + arguments[0].into_int_value(), + ) + .map(|_| None) + } Name::Log0 => { let arguments = self.pop_arguments_llvm::<2>(context)?; @@ -962,13 +970,6 @@ impl FunctionCall { location ) } - Name::SelfDestruct => { - let _arguments = self.pop_arguments_llvm::<1>(context)?; - anyhow::bail!( - "{} The `SELFDESTRUCT` instruction is not supported", - location - ) - } } }