diff --git a/prdoc/pr_10554.prdoc b/prdoc/pr_10554.prdoc new file mode 100644 index 0000000000000..5298a312dd691 --- /dev/null +++ b/prdoc/pr_10554.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] add EVM gas call syscalls' +doc: +- audience: Runtime Dev + description: |- + This PR adds two new syscalls for calls accepting EVM gas instead of Weight and Deposit. + + This is an important change for the initial release as it will align PVM contracts closer to EVM (the problem can't be solved in the Solidity compiler). +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/call_with_gas.rs b/substrate/frame/revive/fixtures/contracts/call_with_gas.rs new file mode 100644 index 0000000000000..10d918c9ed06d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_with_gas.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +include!("../panic_handler.rs"); + +use uapi::{input, CallFlags, HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: &[u8; 20], + gas: u64, + ); + + let mut value = [0; 32]; + api::value_transferred(&mut value); + + api::call_evm(CallFlags::empty(), callee_addr, gas, &value, &[], None).unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_evm.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_evm.rs new file mode 100644 index 0000000000000..cffc33efb6b99 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_evm.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] +include!("../panic_handler.rs"); + +use uapi::{input, CallFlags, HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + address: &[u8; 20], + gas: u64, + ); + + let mut key = [0u8; 32]; + key[0] = 1u8; + + let mut value = [0u8; 32]; + let value = &mut &mut value[..]; + value[0] = 2u8; + + api::set_storage(StorageFlags::empty(), &key, value); + api::get_storage(StorageFlags::empty(), &key, value).unwrap(); + assert!(value[0] == 2u8); + + api::delegate_call_evm(CallFlags::empty(), address, gas, &[], None).unwrap(); + + api::get_storage(StorageFlags::empty(), &key, value).unwrap(); + assert!(value[0] == 1u8); +} + diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index fc163113453b4..3b4ae34ff9964 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -5189,3 +5189,271 @@ fn existential_deposit_shall_not_charged_twice() { assert_eq!(get_balance(&callee_account), Contracts::min_balance()); }); } +<<<<<<< HEAD +======= + +#[test] +fn self_destruct_by_syscall_tracing_works() { + use crate::{ + evm::{PrestateTrace, PrestateTracer, PrestateTracerConfig, Tracer}, + Trace, + }; + + let (binary, _code_hash) = compile_module("self_destruct_by_syscall").unwrap(); + + struct TestCase { + description: &'static str, + create_tracer: Box Tracer>, + expected_trace_fn: Box) -> Trace>, + modify_trace_fn: Option Trace>>, + } + + let test_cases = vec![ + TestCase { + description: "CallTracer", + create_tracer: Box::new(|| Tracer::CallTracer(CallTracer::new(Default::default()))), + expected_trace_fn: Box::new(|addr, _binary| { + Trace::Call(CallTrace { + from: ALICE_ADDR, + to: addr, + call_type: CallType::Call, + value: Some(U256::zero()), + gas: 0.into(), + gas_used: 0.into(), + calls: vec![CallTrace { + from: addr, + to: DJANGO_ADDR, + gas: 0.into(), + + call_type: CallType::Selfdestruct, + value: Some(Pallet::::convert_native_to_evm(100_000u64)), + ..Default::default() + }], + ..Default::default() + }) + }), + modify_trace_fn: Some(Box::new(|mut actual_trace| { + if let Trace::Call(trace) = &mut actual_trace { + trace.gas = 0.into(); + trace.gas_used = 0.into(); + trace.calls[0].gas = 0.into(); + } + actual_trace + })), + }, + TestCase { + description: "PrestateTracer (diff mode)", + create_tracer: Box::new(|| { + Tracer::PrestateTracer(PrestateTracer::new(PrestateTracerConfig { + diff_mode: true, + disable_storage: false, + disable_code: false, + })) + }), + expected_trace_fn: Box::new(|addr, binary| { + use alloy_core::hex; + + let json = r#"{ + "pre": { + "{{ALICE_ADDR}}": { + "balance": "{{ALICE_BALANCE_PRE}}", + "nonce": 1 + }, + "{{DJANGO_ADDR}}": { + "balance": "{{DJANGO_BALANCE}}" + }, + "{{CONTRACT_ADDR}}": { + "balance": "{{CONTRACT_BALANCE}}", + "nonce": 1, + "code": "{{CONTRACT_CODE}}" + } + }, + "post": { + "{{ALICE_ADDR}}": { + "balance": "{{ALICE_BALANCE_POST}}" + }, + "{{DJANGO_ADDR}}": { + "balance": "{{DJANGO_BALANCE_POST}}" + }, + "{{CONTRACT_ADDR}}": { + "balance": "0x0" + } + } + }"#; + + let alice_balance_pre = Pallet::::evm_balance(&ALICE_ADDR); + let alice_balance_post = alice_balance_pre - 50_000_000u64; + let django_balance = Pallet::::evm_balance(&DJANGO_ADDR); + let contract_balance = Pallet::::evm_balance(&addr); + let django_balance_post = contract_balance; + + let json = json + .replace("{{ALICE_ADDR}}", &format!("{:#x}", ALICE_ADDR)) + .replace("{{ALICE_BALANCE_PRE}}", &format!("{:#x}", alice_balance_pre)) + .replace("{{ALICE_BALANCE_POST}}", &format!("{:#x}", alice_balance_post)) + .replace("{{CONTRACT_ADDR}}", &format!("{:#x}", addr)) + .replace("{{DJANGO_ADDR}}", &format!("{:#x}", DJANGO_ADDR)) + .replace("{{CONTRACT_ADDR}}", &format!("{:#x}", addr)) + .replace("{{DJANGO_BALANCE}}", &format!("{:#x}", django_balance)) + .replace("{{CONTRACT_BALANCE}}", &format!("{:#x}", contract_balance)) + .replace("{{DJANGO_BALANCE_POST}}", &format!("{:#x}", django_balance_post)) + .replace("{{CONTRACT_CODE}}", &format!("0x{}", hex::encode(&binary))); + + let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); + Trace::Prestate(expected) + }), + modify_trace_fn: None, + }, + TestCase { + description: "PrestateTracer (prestate mode)", + create_tracer: Box::new(|| { + Tracer::PrestateTracer(PrestateTracer::new(PrestateTracerConfig { + diff_mode: false, + disable_storage: false, + disable_code: false, + })) + }), + expected_trace_fn: Box::new(|addr, binary| { + use alloy_core::hex; + + let json = r#"{ + "{{ALICE_ADDR}}": { + "balance": "{{ALICE_BALANCE}}", + "nonce": 1 + }, + "{{CONTRACT_ADDR}}": { + "balance": "{{CONTRACT_BALANCE}}", + "nonce": 1, + "code": "{{CONTRACT_CODE}}" + }, + "{{DJANGO_ADDR}}": { + "balance": "{{DJANGO_BALANCE}}" + } + }"#; + + let alice_balance = Pallet::::evm_balance(&ALICE_ADDR); + let contract_balance = Pallet::::evm_balance(&addr); + let django_balance = Pallet::::evm_balance(&DJANGO_ADDR); + + let json = json + .replace("{{ALICE_ADDR}}", &format!("{:#x}", ALICE_ADDR)) + .replace("{{CONTRACT_ADDR}}", &format!("{:#x}", addr)) + .replace("{{DJANGO_ADDR}}", &format!("{:#x}", DJANGO_ADDR)) + .replace("{{ALICE_BALANCE}}", &format!("{:#x}", alice_balance)) + .replace("{{CONTRACT_BALANCE}}", &format!("{:#x}", contract_balance)) + .replace("{{DJANGO_BALANCE}}", &format!("{:#x}", django_balance)) + .replace("{{CONTRACT_CODE}}", &format!("0x{}", hex::encode(&binary))); + + let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); + Trace::Prestate(expected) + }), + modify_trace_fn: None, + }, + ]; + + for TestCase { description, create_tracer, expected_trace_fn, modify_trace_fn } in test_cases { + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary.clone())) + .native_value(100_000) + .build_and_unwrap_contract(); + + get_contract(&addr); + + let expected_trace = expected_trace_fn(addr, binary.clone()); + let mut tracer = create_tracer(); + trace(tracer.as_tracing(), || { + builder::call(addr).build().unwrap(); + }); + + let mut trace = tracer.collect_trace().unwrap(); + + if let Some(modify_trace_fn) = modify_trace_fn { + trace = modify_trace_fn(trace); + } + let trace_wrapped = match trace { + crate::evm::Trace::Call(ct) => Trace::Call(ct), + crate::evm::Trace::Prestate(pt) => Trace::Prestate(pt), + }; + + assert_eq!(trace_wrapped, expected_trace, "Trace mismatch for: {}", description); + }); + } +} + +#[test] +fn delegate_call_with_gas_limit() { + let (caller_binary, _caller_code_hash) = compile_module("delegate_call_evm").unwrap(); + let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(300_000) + .build_and_unwrap_contract(); + + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + // fails, not enough gas + assert_err!( + builder::bare_call(caller_addr) + .native_value(1337) + .data((callee_addr, 100u64).encode()) + .build() + .result, + Error::::ContractTrapped, + ); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((callee_addr, 100_000_000_000u64).encode()) + .build()); + }); +} + +#[test] +fn call_with_gas_limit() { + let (caller_binary, _caller_code_hash) = compile_module("call_with_gas").unwrap(); + let (callee_binary, _callee_code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(300_000) + .build_and_unwrap_contract(); + + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + // fails, not enough gas + assert_err!( + builder::bare_call(caller_addr) + .data((callee_addr, 1u64).encode()) + .build() + .result, + Error::::ContractTrapped, + ); + + // succeeds, not enough gas but call stipend will be added + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((callee_addr, 100u64).encode()) + .build()); + + // succeeds, enough gas + assert_ok!(builder::call(caller_addr) + .data((callee_addr, 100_000_000_000u64).encode()) + .build()); + }); +} +>>>>>>> 5a1128b9 ([pallet-revive] add EVM gas call syscalls (#10554)) diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index aeeafb95cfecd..2a73193aabc9c 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -631,8 +631,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { flags: CallFlags, call_type: CallType, callee_ptr: u32, - deposit_ptr: u32, - weight: Weight, + resources: &CallResources, input_data_ptr: u32, input_data_len: u32, output_ptr: u32, @@ -647,8 +646,6 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { None => self.charge_gas(call_type.cost())?, }; - let deposit_limit = memory.read_u256(deposit_ptr)?; - // we do check this in exec.rs but we want to error out early if input_data_len > limits::CALLDATA_BYTES { Err(>::CallDataTooLarge)?; @@ -686,6 +683,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { dust_transfer: Pallet::::has_dust(value), })?; } +<<<<<<< HEAD self.ext.call( weight, deposit_limit, @@ -695,12 +693,26 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { flags.contains(CallFlags::ALLOW_REENTRY), read_only, ) +======= + + let reentrancy = if flags.contains(CallFlags::ALLOW_REENTRY) { + ReentrancyProtection::AllowReentry + } else { + ReentrancyProtection::Strict + }; + + self.ext.call(resources, &callee, value, input_data, reentrancy, read_only) +>>>>>>> 5a1128b9 ([pallet-revive] add EVM gas call syscalls (#10554)) }, CallType::DelegateCall => { if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { return Err(Error::::InvalidCallFlags.into()); } +<<<<<<< HEAD self.ext.delegate_call(weight, deposit_limit, callee, input_data) +======= + self.ext.delegate_call(resources, callee, input_data) +>>>>>>> 5a1128b9 ([pallet-revive] add EVM gas call syscalls (#10554)) }, }; diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index 6e491c26ae01e..04fe6e6c7369e 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -298,14 +298,55 @@ pub mod env { let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + let weight = Weight::from_parts(ref_time_limit, proof_size_limit); + + self.charge_gas(RuntimeCosts::CopyFromContract(32))?; + let deposit_limit = memory.read_u256(deposit_ptr)?; self.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, CallType::Call { value_ptr }, callee_ptr, - deposit_ptr, - Weight::from_parts(ref_time_limit, proof_size_limit), + &CallResources::from_weight_and_deposit(weight, deposit_limit), + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Make a call to another contract. + /// See [`pallet_revive_uapi::HostFn::call_evm`]. + #[stable] + fn call_evm( + &mut self, + memory: &mut M, + flags: u32, + callee: u32, + value_ptr: u32, + gas: u64, + input_data: u64, + output_data: u64, + ) -> Result { + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + let resources = if gas == u64::MAX { + CallResources::NoLimits + } else { + self.charge_gas(RuntimeCosts::CopyFromContract(32))?; + let value = memory.read_u256(value_ptr)?; + // We also need to detect the 2300: We need to add something scaled. + let add_stipend = !value.is_zero() || gas == revm::interpreter::gas::CALL_STIPEND; + CallResources::from_ethereum_gas(gas.into(), add_stipend) + }; + + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { value_ptr }, + callee, + &resources, input_data_ptr, input_data_len, output_ptr, @@ -329,14 +370,50 @@ pub mod env { let (flags, address_ptr) = extract_hi_lo(flags_and_callee); let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + let weight = Weight::from_parts(ref_time_limit, proof_size_limit); + + self.charge_gas(RuntimeCosts::CopyFromContract(32))?; + let deposit_limit = memory.read_u256(deposit_ptr)?; self.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, CallType::DelegateCall, address_ptr, - deposit_ptr, - Weight::from_parts(ref_time_limit, proof_size_limit), + &CallResources::from_weight_and_deposit(weight, deposit_limit), + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Same as `delegate_call` but with EVM gas. + /// See [`pallet_revive_uapi::HostFn::delegate_call_evm`]. + #[stable] + fn delegate_call_evm( + &mut self, + memory: &mut M, + flags: u32, + callee: u32, + gas: u64, + input_data: u64, + output_data: u64, + ) -> Result { + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + let resources = if gas == u64::MAX { + CallResources::NoLimits + } else { + CallResources::from_ethereum_gas(gas.into(), false) + }; + + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::DelegateCall, + callee, + &resources, input_data_ptr, input_data_len, output_ptr, diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index c85d175237a6f..65fe94aa587c0 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -119,6 +119,20 @@ pub trait HostFn: private::Sealed { output: Option<&mut &mut [u8]>, ) -> Result; + /// Same as [HostFn::call] but receives the one-dimensional EVM gas argument. + /// + /// Adds the EVM gas stipend for non-zero value calls. + /// + /// If gas is `u64::MAX`, the call will run with uncapped limits. + fn call_evm( + flags: CallFlags, + callee: &[u8; 20], + gas: u64, + value: &[u8; 32], + input_data: &[u8], + output: Option<&mut &mut [u8]>, + ) -> Result; + /// Stores the address of the caller into the supplied buffer. /// /// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the @@ -207,6 +221,17 @@ pub trait HostFn: private::Sealed { output: Option<&mut &mut [u8]>, ) -> Result; + /// Same as [HostFn::delegate_call] but receives the one-dimensional EVM gas argument. + /// + /// If gas is `u64::MAX`, the call will run with uncapped limits. + fn delegate_call_evm( + flags: CallFlags, + address: &[u8; 20], + gas: u64, + input_data: &[u8], + output: Option<&mut &mut [u8]>, + ) -> Result; + /// Deposit a contract event with the data buffer and optional list of topics. There is a limit /// on the maximum number of topics specified by `event_topics`. /// diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index fdfd234d7de5e..2e87404a7b558 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -64,6 +64,14 @@ mod sys { input_data: u64, output_data: u64, ) -> ReturnCode; + pub fn call_evm( + flags: u32, + callee: u32, + value_ptr: u32, + gas: u64, + input_data: u64, + output_data: u64, + ) -> ReturnCode; pub fn delegate_call( flags_and_callee: u64, ref_time_limit: u64, @@ -80,6 +88,13 @@ mod sys { output_data: u64, address_and_salt: u64, ) -> ReturnCode; + pub fn delegate_call_evm( + flags: u32, + callee: u32, + gas: u64, + input_data: u64, + output_data: u64, + ) -> ReturnCode; pub fn terminate(beneficiary_ptr: *const u8); pub fn call_data_copy(out_ptr: *mut u8, out_len: u32, offset: u32); pub fn call_data_load(out_ptr: *mut u8, offset: u32); @@ -227,6 +242,36 @@ impl HostFn for HostFnImpl { ret_code.into() } + fn call_evm( + flags: CallFlags, + callee: &[u8; 20], + gas: u64, + value_ptr: &[u8; 32], + input: &[u8], + mut output: Option<&mut &mut [u8]>, + ) -> Result { + let input_data = pack_hi_lo(input.len() as _, input.as_ptr() as _); + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let output_data = pack_hi_lo(&mut output_len as *mut _ as _, output_ptr as _); + + let ret_code = unsafe { + sys::call_evm( + flags.bits(), + callee.as_ptr() as _, + value_ptr.as_ptr() as _, + gas, + input_data, + output_data, + ) + }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + fn delegate_call( flags: CallFlags, address: &[u8; 20], @@ -261,6 +306,34 @@ impl HostFn for HostFnImpl { ret_code.into() } + fn delegate_call_evm( + flags: CallFlags, + address: &[u8; 20], + gas: u64, + input: &[u8], + mut output: Option<&mut &mut [u8]>, + ) -> Result { + let input_data = pack_hi_lo(input.len() as u32, input.as_ptr() as u32); + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let output_data = pack_hi_lo(&mut output_len as *mut _ as u32, output_ptr as u32); + + let ret_code = unsafe { + sys::delegate_call_evm( + flags.bits(), + address.as_ptr() as _, + gas, + input_data, + output_data, + ) + }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + fn deposit_event(topics: &[[u8; 32]], data: &[u8]) { unsafe { sys::deposit_event(