diff --git a/crates/integration/contracts/Delegate.sol b/crates/integration/contracts/Delegate.sol new file mode 100644 index 0000000..84dfa97 --- /dev/null +++ b/crates/integration/contracts/Delegate.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +/* runner.json +{ + "differential": true, + "actions": [ + { + "Upload": { + "code": { + "Solidity": { + "contract": "Logic" + } + } + } + }, + { + "Instantiate": { + "code": { + "Solidity": { + "contract": "Tester" + } + } + } + }, + { + "Call": { + "dest": { + "Instantiated": 0 + }, + "value": 123, + "data": "6466414b0000000000000000000000000000000000000000000000000000000000000020" + } + } + ] +} +*/ + +contract Logic { + // NOTE: storage layout must be the same as contract Tester + uint256 public num; + address public sender; + uint256 public value; + + event DidSetVars(); + + function setVars(uint256 _num) public payable returns (uint256) { + num = _num * 2; + sender = msg.sender; + value = msg.value; + emit DidSetVars(); + return _num; + } +} + +contract Tester { + uint256 public num; + address public sender; + uint256 public value; + + function setVars(uint256 _num) public payable returns (bool, bytes memory) { + Logic impl = new Logic(); + + // Tester's storage is set, Logic is not modified. + (bool success, bytes memory data) = address(impl).delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + + assert(success); + assert(impl.num() == 0); + assert(impl.sender() == address(0)); + assert(impl.value() == 0); + assert(this.num() == _num * 2); + assert(this.sender() == msg.sender); + assert(this.value() == msg.value); + + return (success, data); + } +} diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs index 0e792ed..1e2fc97 100644 --- a/crates/integration/src/tests.rs +++ b/crates/integration/src/tests.rs @@ -43,6 +43,7 @@ test_spec!(call, "Caller", "Call.sol"); test_spec!(transfer, "Transfer", "Transfer.sol"); test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol"); test_spec!(immutables, "Immutables", "Immutables.sol"); +test_spec!(delegate, "Delegate", "Delegate.sol"); fn instantiate(path: &str, contract: &str) -> Vec { vec![Instantiate { diff --git a/crates/llvm-context/src/polkavm/const/runtime_api.rs b/crates/llvm-context/src/polkavm/const/runtime_api.rs index 9de82e7..61f6810 100644 --- a/crates/llvm-context/src/polkavm/const/runtime_api.rs +++ b/crates/llvm-context/src/polkavm/const/runtime_api.rs @@ -29,6 +29,8 @@ pub mod imports { pub static CALL: &str = "call"; + pub static DELEGATE_CALL: &str = "delegate_call"; + pub static CALLER: &str = "caller"; pub static CODE_SIZE: &str = "code_size"; @@ -61,7 +63,7 @@ pub mod imports { /// All imported runtime API symbols. /// Useful for configuring common attributes and linkage. - pub static IMPORTS: [&str; 23] = [ + pub static IMPORTS: [&str; 24] = [ SBRK, MEMORY_SIZE, ADDRESS, @@ -69,6 +71,7 @@ pub mod imports { BALANCE_OF, BLOCK_NUMBER, CALL, + DELEGATE_CALL, CALLER, CHAIN_ID, CODE_SIZE, diff --git a/crates/llvm-context/src/polkavm/evm/call.rs b/crates/llvm-context/src/polkavm/evm/call.rs index 2247250..954adf4 100644 --- a/crates/llvm-context/src/polkavm/evm/call.rs +++ b/crates/llvm-context/src/polkavm/evm/call.rs @@ -14,7 +14,7 @@ const REENTRANT_CALL_FLAG: u32 = 0b0000_1000; #[allow(clippy::too_many_arguments)] pub fn call<'ctx, D>( context: &mut Context<'ctx, D>, - _gas: inkwell::values::IntValue<'ctx>, + gas: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>, value: Option>, input_offset: inkwell::values::IntValue<'ctx>, @@ -38,10 +38,9 @@ where let output_offset = context.safe_truncate_int_to_xlen(output_offset)?; let output_length = context.safe_truncate_int_to_xlen(output_length)?; - // TODO: What to supply here? Is there a weight to gas? - let _gas = context + let gas = context .builder() - .build_int_truncate(_gas, context.integer_type(64), "gas")?; + .build_int_truncate(gas, context.integer_type(64), "gas")?; let input_pointer = context.build_heap_gep(input_offset, input_length)?; let output_pointer = context.build_heap_gep(output_offset, output_length)?; @@ -61,7 +60,7 @@ where let arguments = &[ flags.as_basic_value_enum(), address_pointer.value.as_basic_value_enum(), - context.integer_const(64, 0).as_basic_value_enum(), + gas.as_basic_value_enum(), context.integer_const(64, 0).as_basic_value_enum(), context.sentinel_pointer().value.as_basic_value_enum(), value_pointer.value.as_basic_value_enum(), @@ -103,20 +102,79 @@ where #[allow(clippy::too_many_arguments)] pub fn delegate_call<'ctx, D>( - _context: &mut Context<'ctx, D>, - _gas: inkwell::values::IntValue<'ctx>, - _address: inkwell::values::IntValue<'ctx>, + context: &mut Context<'ctx, D>, + gas: inkwell::values::IntValue<'ctx>, + address: inkwell::values::IntValue<'ctx>, _value: Option>, - _input_offset: inkwell::values::IntValue<'ctx>, - _input_length: inkwell::values::IntValue<'ctx>, - _output_offset: inkwell::values::IntValue<'ctx>, - _output_length: inkwell::values::IntValue<'ctx>, + input_offset: inkwell::values::IntValue<'ctx>, + input_length: inkwell::values::IntValue<'ctx>, + output_offset: inkwell::values::IntValue<'ctx>, + output_length: inkwell::values::IntValue<'ctx>, _constants: Vec>, ) -> anyhow::Result> where D: Dependency + Clone, { - todo!() + let address_pointer = context.build_address_argument_store(address)?; + + let input_offset = context.safe_truncate_int_to_xlen(input_offset)?; + let input_length = context.safe_truncate_int_to_xlen(input_length)?; + let output_offset = context.safe_truncate_int_to_xlen(output_offset)?; + let output_length = context.safe_truncate_int_to_xlen(output_length)?; + + let gas = context + .builder() + .build_int_truncate(gas, context.integer_type(64), "gas")?; + + let input_pointer = context.build_heap_gep(input_offset, input_length)?; + let output_pointer = context.build_heap_gep(output_offset, output_length)?; + + let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length"); + context.build_store(output_length_pointer, output_length)?; + + let flags = context.xlen_type().const_int(0u64, false); + + let argument_type = revive_runtime_api::calling_convention::delegate_call(context.llvm()); + let argument_pointer = context.build_alloca_at_entry(argument_type, "delegate_call_arguments"); + let arguments = &[ + flags.as_basic_value_enum(), + address_pointer.value.as_basic_value_enum(), + gas.as_basic_value_enum(), + context.integer_const(64, 0).as_basic_value_enum(), + input_pointer.value.as_basic_value_enum(), + input_length.as_basic_value_enum(), + output_pointer.value.as_basic_value_enum(), + output_length_pointer.value.as_basic_value_enum(), + ]; + revive_runtime_api::calling_convention::spill( + context.builder(), + argument_pointer.value, + argument_type, + arguments, + )?; + + let name = runtime_api::imports::DELEGATE_CALL; + let argument_pointer = context.builder().build_ptr_to_int( + argument_pointer.value, + context.xlen_type(), + "delegate_call_argument_pointer", + )?; + let success = context + .build_runtime_call(name, &[argument_pointer.into()]) + .unwrap_or_else(|| panic!("{name} should return a value")) + .into_int_value(); + + let is_success = context.builder().build_int_compare( + inkwell::IntPredicate::EQ, + success, + context.xlen_type().const_zero(), + "is_success", + )?; + + Ok(context + .builder() + .build_int_z_extend(is_success, context.word_type(), "success")? + .as_basic_value_enum()) } /// Translates the Yul `linkersymbol` instruction. diff --git a/crates/runtime-api/src/calling_convention.rs b/crates/runtime-api/src/calling_convention.rs index c92a266..31683c6 100644 --- a/crates/runtime-api/src/calling_convention.rs +++ b/crates/runtime-api/src/calling_convention.rs @@ -106,3 +106,28 @@ pub fn call(context: &Context) -> StructType { true, ) } + +/// Returns a packed struct argument type for the `delegate_call` API. +pub fn delegate_call(context: &Context) -> StructType { + context.struct_type( + &[ + // flags: u32, + context.i32_type().as_basic_type_enum(), + // address_ptr: + context.ptr_type(Default::default()).as_basic_type_enum(), + // ref_time_limit: u64, + context.i64_type().as_basic_type_enum(), + // proof_size_limit: u64, + context.i64_type().as_basic_type_enum(), + // input_data_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // input_data_len: u32, + context.i32_type().as_basic_type_enum(), + // output_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // output_len_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + ], + true, + ) +} diff --git a/crates/runtime-api/src/polkavm_imports.c b/crates/runtime-api/src/polkavm_imports.c index 9e6f05d..9d38ff9 100644 --- a/crates/runtime-api/src/polkavm_imports.c +++ b/crates/runtime-api/src/polkavm_imports.c @@ -90,7 +90,7 @@ POLKAVM_IMPORT(uint32_t, take_storage, uint32_t, uint32_t, uint32_t, uint32_t) POLKAVM_IMPORT(uint32_t, call, uint32_t) -POLKAVM_IMPORT(uint32_t, delegate_call, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) +POLKAVM_IMPORT(uint32_t, delegate_call, uint32_t) POLKAVM_IMPORT(uint32_t, instantiate, uint32_t)