diff --git a/crates/evm/src/context.cairo b/crates/evm/src/context.cairo index b9b442c29..52cf14460 100644 --- a/crates/evm/src/context.cairo +++ b/crates/evm/src/context.cairo @@ -111,8 +111,14 @@ impl CallContextImpl of CallContextTrait { impl DefaultBoxCallContext of Default> { fn default() -> Box { - let call_context: CallContext = Default::default(); - BoxTrait::new(call_context) + let call_ctx: CallContext = Default::default(); + BoxTrait::new(call_ctx) + } +} + +impl DefaultOptionSpanU8 of Default>> { + fn default() -> Option> { + Option::None } } @@ -130,13 +136,13 @@ struct ExecutionContext { starknet_address: ContractAddress, program_counter: u32, status: Status, - call_context: Box, + call_ctx: Box, destroyed_contracts: Array, events: Array, create_addresses: Array, return_data: Array, - parent_context: Nullable, - child_context: Nullable, + parent_ctx: Nullable, + child_return_data: Option>, } impl DefaultBoxExecutionContext of Default> { @@ -157,9 +163,9 @@ impl ExecutionContextImpl of ExecutionContextTrait { id: usize, evm_address: EthAddress, starknet_address: ContractAddress, - call_context: CallContext, - parent_context: Nullable, - child_context: Nullable, + call_ctx: CallContext, + parent_ctx: Nullable, + child_return_data: Option>, return_data: Array, ) -> ExecutionContext { ExecutionContext { @@ -168,23 +174,13 @@ impl ExecutionContextImpl of ExecutionContextTrait { starknet_address, program_counter: Default::default(), status: Status::Active, - call_context: BoxTrait::new( - CallContextTrait::new( - call_context.caller, - call_context.bytecode, - call_context.calldata, - call_context.value, - call_context.read_only, - call_context.gas_limit, - call_context.gas_price, - ) - ), + call_ctx: BoxTrait::new(call_ctx), destroyed_contracts: Default::default(), events: Default::default(), create_addresses: Default::default(), return_data, - parent_context, - child_context, + parent_ctx, + child_return_data, } } @@ -210,8 +206,8 @@ impl ExecutionContextImpl of ExecutionContextTrait { } #[inline(always)] - fn call_context(self: @ExecutionContext) -> CallContext { - (*self.call_context).unbox() + fn call_ctx(self: @ExecutionContext) -> CallContext { + (*self.call_ctx).unbox() } #[inline(always)] @@ -280,7 +276,7 @@ impl ExecutionContextImpl of ExecutionContextTrait { #[inline(always)] fn read_code(self: @ExecutionContext, len: usize) -> Span { // Copy code slice from [pc, pc+len] - let code = (*self.call_context).unbox().bytecode().slice(self.pc(), len); + let code = (*self.call_ctx).unbox().bytecode().slice(self.pc(), len); code } @@ -314,4 +310,9 @@ impl ExecutionContextImpl of ExecutionContextTrait { fn pc(self: @ExecutionContext) -> u32 { *self.program_counter } + + #[inline(always)] + fn child_return_data(self: @ExecutionContext) -> Option> { + *self.child_return_data + } } diff --git a/crates/evm/src/interpreter.cairo b/crates/evm/src/interpreter.cairo index c08c1ccaa..447df2394 100644 --- a/crates/evm/src/interpreter.cairo +++ b/crates/evm/src/interpreter.cairo @@ -58,7 +58,7 @@ impl EVMInterpreterImpl of EVMInterpreterTrait { fn decode_and_execute(ref self: EVMInterpreter, ref machine: Machine) -> Result<(), EVMError> { // Retrieve the current program counter. let pc = machine.pc(); - let bytecode = machine.call_context().bytecode(); + let bytecode = machine.call_ctx().bytecode(); let bytecode_len = bytecode.len(); // Check if PC is not out of bounds. diff --git a/crates/evm/src/machine.cairo b/crates/evm/src/machine.cairo index b1a1014c7..68ecbb385 100644 --- a/crates/evm/src/machine.cairo +++ b/crates/evm/src/machine.cairo @@ -23,7 +23,7 @@ struct Journal { #[derive(Destruct)] struct Machine { - current_context: Box, + current_ctx: Box, ctx_count: usize, stack: Stack, memory: Memory, @@ -33,7 +33,7 @@ struct Machine { impl DefaultMachine of Default { fn default() -> Machine { Machine { - current_context: Default::default(), + current_ctx: Default::default(), ctx_count: 1, stack: Default::default(), memory: Default::default(), @@ -43,7 +43,7 @@ impl DefaultMachine of Default { } /// A set of getters and setters for the current context -/// Since current_context is a pointer to the current context being executed by the machine we're forced into the following pattern: +/// Since current_ctx is a pointer to the current context being executed by the machine we're forced into the following pattern: /// /// For getters: /// Unbox the current ExecutionContext @@ -67,155 +67,155 @@ impl MachineCurrentContextImpl of MachineCurrentContextTrait { /// to divide a unique Stack/Memory simulated by a dict into /// multiple sub-structures relative to a single context. #[inline(always)] - fn set_current_context(ref self: Machine, ctx: ExecutionContext) { + fn set_current_ctx(ref self: Machine, ctx: ExecutionContext) { self.memory.set_active_segment(ctx.id); self.stack.set_active_segment(ctx.id); - self.current_context = BoxTrait::new(ctx); + self.current_ctx = BoxTrait::new(ctx); } #[inline(always)] fn pc(ref self: Machine) -> usize { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let pc = current_execution_ctx.pc(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); pc } #[inline(always)] fn set_pc(ref self: Machine, new_pc: u32) { - let mut current_execution_ctx = self.current_context.unbox(); + let mut current_execution_ctx = self.current_ctx.unbox(); current_execution_ctx.program_counter = new_pc; - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); } #[inline(always)] fn revert(ref self: Machine, revert_reason: Span) { - let mut current_execution_ctx = self.current_context.unbox(); + let mut current_execution_ctx = self.current_ctx.unbox(); current_execution_ctx.revert(revert_reason); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); } #[inline(always)] fn reverted(ref self: Machine) -> bool { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let reverted = current_execution_ctx.reverted(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); reverted } #[inline(always)] fn stopped(ref self: Machine) -> bool { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let stopped = current_execution_ctx.stopped(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); stopped } #[inline(always)] - fn call_context(ref self: Machine) -> CallContext { - let current_execution_ctx = self.current_context.unbox(); - let call_context = current_execution_ctx.call_context.unbox(); - self.current_context = BoxTrait::new(current_execution_ctx); - call_context + fn call_ctx(ref self: Machine) -> CallContext { + let current_execution_ctx = self.current_ctx.unbox(); + let call_ctx = current_execution_ctx.call_ctx.unbox(); + self.current_ctx = BoxTrait::new(current_execution_ctx); + call_ctx } #[inline(always)] fn destroyed_contracts(ref self: Machine) -> Span { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let destroyed_contracts = current_execution_ctx.destroyed_contracts.span(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); destroyed_contracts } #[inline(always)] fn events(ref self: Machine) -> Span { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let events = current_execution_ctx.events.span(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); events } #[inline(always)] fn create_addresses(ref self: Machine) -> Span { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let create_addresses = current_execution_ctx.create_addresses.span(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); create_addresses } #[inline(always)] fn return_data(ref self: Machine) -> Span { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let return_data = current_execution_ctx.return_data.span(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); return_data } /// Stops the current execution context. #[inline(always)] fn stop(ref self: Machine) { - let mut current_execution_ctx = self.current_context.unbox(); + let mut current_execution_ctx = self.current_ctx.unbox(); current_execution_ctx.status = Status::Stopped; - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); } #[inline(always)] fn evm_address(ref self: Machine) -> EthAddress { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let evm_address = current_execution_ctx.evm_address(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); evm_address } #[inline(always)] fn starknet_address(ref self: Machine) -> ContractAddress { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let starknet_address = current_execution_ctx.starknet_address(); - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); starknet_address } #[inline(always)] fn caller(ref self: Machine) -> EthAddress { - let current_call_ctx = self.call_context(); + let current_call_ctx = self.call_ctx(); current_call_ctx.caller() } #[inline(always)] fn read_only(ref self: Machine) -> bool { - let current_call_ctx = self.call_context(); + let current_call_ctx = self.call_ctx(); current_call_ctx.read_only() } #[inline(always)] fn gas_limit(ref self: Machine) -> u64 { - let current_call_ctx = self.call_context(); + let current_call_ctx = self.call_ctx(); current_call_ctx.gas_limit() } #[inline(always)] fn gas_price(ref self: Machine) -> u64 { - let current_call_ctx = self.call_context(); + let current_call_ctx = self.call_ctx(); current_call_ctx.gas_price() } #[inline(always)] fn value(ref self: Machine) -> u256 { - let current_call_ctx = self.call_context(); + let current_call_ctx = self.call_ctx(); current_call_ctx.value() } #[inline(always)] fn bytecode(ref self: Machine) -> Span { - let current_call_ctx = self.call_context(); + let current_call_ctx = self.call_ctx(); current_call_ctx.bytecode() } #[inline(always)] fn calldata(ref self: Machine) -> Span { - let current_call_ctx = self.call_context(); + let current_call_ctx = self.call_ctx(); current_call_ctx.calldata() } @@ -240,16 +240,26 @@ impl MachineCurrentContextImpl of MachineCurrentContextTrait { /// The root is always the first context to be executed, and thus has id 0. #[inline(always)] fn is_root(ref self: Machine) -> bool { - let current_execution_ctx = self.current_context.unbox(); + let current_execution_ctx = self.current_ctx.unbox(); let is_root = current_execution_ctx.id == 0; - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); is_root } #[inline(always)] fn set_return_data(ref self: Machine, value: Array) { - let mut current_execution_ctx = self.current_context.unbox(); + let mut current_execution_ctx = self.current_ctx.unbox(); current_execution_ctx.return_data = value; - self.current_context = BoxTrait::new(current_execution_ctx); + self.current_ctx = BoxTrait::new(current_execution_ctx); + } + + /// Getter for the return data of a child context, accessed from its parent context + /// Enabler for RETURNDATASIZE and RETURNDATACOPY opcodes + #[inline(always)] + fn child_return_data(ref self: Machine) -> Option> { + let mut current_execution_ctx = self.current_ctx.unbox(); + let child_return_data = current_execution_ctx.child_return_data(); + self.current_ctx = BoxTrait::new(current_execution_ctx); + child_return_data } } diff --git a/crates/evm/src/tests/test_execution_context.cairo b/crates/evm/src/tests/test_execution_context.cairo index caa4faf89..24797683d 100644 --- a/crates/evm/src/tests/test_execution_context.cairo +++ b/crates/evm/src/tests/test_execution_context.cairo @@ -1,6 +1,8 @@ use core::nullable::{NullableTrait, null}; use debug::PrintTrait; -use evm::context::{CallContext, CallContextTrait, ExecutionContext, ExecutionContextTrait}; +use evm::context::{ + CallContext, CallContextTrait, ExecutionContext, ExecutionContextTrait, DefaultOptionSpanU8 +}; use evm::memory::{Memory, MemoryTrait}; use evm::model::Event; use evm::stack::{Stack, StackTrait}; @@ -47,7 +49,7 @@ fn test_call_context_new() { #[available_gas(500000)] fn test_execution_context_new() { // Given - let call_context = setup_call_context(); + let call_ctx = setup_call_context(); let context_id = 0; let program_counter: u32 = 0; @@ -63,23 +65,22 @@ fn test_execution_context_new() { let reverted: bool = false; let read_only: bool = false; - let parent_context: Nullable = null(); - let child_context: Nullable = null(); + let parent_ctx: Nullable = null(); // When let mut execution_context = ExecutionContextTrait::new( context_id, evm_address, starknet_address, - call_context, - parent_context, - child_context, + call_ctx, + parent_ctx, + Default::default(), return_data ); // Then - let call_context = setup_call_context(); - assert(execution_context.call_context() == call_context, 'wrong call_context'); + let call_ctx = setup_call_context(); + assert(execution_context.call_ctx() == call_ctx, 'wrong call_ctx'); assert(execution_context.program_counter == program_counter, 'wrong program_counter'); assert(execution_context.stopped() == stopped, 'wrong stopped'); assert(execution_context.return_data() == Default::default().span(), 'wrong return_data'); @@ -141,14 +142,27 @@ fn test_execution_context_read_code() { #[test] #[available_gas(300000)] -#[ignore] fn test_is_root() { - // TODO: finish this test once calling_contexts are implemented // Given let mut execution_context = setup_execution_context(); // When let is_root = execution_context.is_root(); -// Then -// assert(is_root == true, 'should not be a leaf'); + + // Then + assert(is_root, 'should not be a leaf'); +} + + +#[test] +#[available_gas(300000)] +fn test_child_return_data() { + // Given + let mut execution_context = setup_execution_context(); + + // When + let child_return_data = execution_context.child_return_data().unwrap(); + + // Then + assert(child_return_data == array![1, 2, 3].span(), 'wrong child_return_data'); } diff --git a/crates/evm/src/tests/test_machine.cairo b/crates/evm/src/tests/test_machine.cairo index 50841f94b..64a234bae 100644 --- a/crates/evm/src/tests/test_machine.cairo +++ b/crates/evm/src/tests/test_machine.cairo @@ -19,13 +19,13 @@ fn test_machine_default() { #[test] #[available_gas(20000000)] -fn test_set_current_context() { +fn test_set_current_ctx() { let mut machine: Machine = Default::default(); - let first_ctx = machine.current_context.unbox(); + let first_ctx = machine.current_ctx.unbox(); assert(first_ctx.id == 0, 'wrong first id'); // We need to re-box the context into the machine, otherwise we have a "Variable Moved" error. - machine.current_context = BoxTrait::new(first_ctx); + machine.current_ctx = BoxTrait::new(first_ctx); assert(machine.stack.active_segment == 0, 'wrong initial stack segment'); assert(machine.memory.active_segment == 0, 'wrong initial memory segment'); @@ -33,10 +33,10 @@ fn test_set_current_context() { let mut second_ctx = setup_execution_context(); second_ctx.id = 1; - machine.set_current_context(second_ctx); + machine.set_current_ctx(second_ctx); assert(machine.stack.active_segment == 1, 'wrong updated stack segment'); assert(machine.memory.active_segment == 1, 'wrong updated stack segment'); - assert(machine.current_context.unbox().id == 1, 'wrong updated id'); + assert(machine.current_ctx.unbox().id == 1, 'wrong updated id'); } #[test] @@ -104,13 +104,13 @@ fn test_call_context_properties() { let bytecode = array![0x01, 0x02, 0x03, 0x04, 0x05].span(); let mut machine = setup_machine_with_bytecode(bytecode); - let call_context = machine.call_context(); - assert(call_context.read_only() == false, 'wrong read_only'); - assert(call_context.gas_limit() == 0xffffff, 'wrong gas_limit'); - assert(call_context.gas_price() == 0xaaaaaa, 'wrong gas_price'); - assert(call_context.value() == 123456789, 'wrong value'); - assert(call_context.bytecode() == bytecode, 'wrong bytecode'); - assert(call_context.calldata() == array![4, 5, 6].span(), 'wrong calldata'); + let call_ctx = machine.call_ctx(); + assert(call_ctx.read_only() == false, 'wrong read_only'); + assert(call_ctx.gas_limit() == 0xffffff, 'wrong gas_limit'); + assert(call_ctx.gas_price() == 0xaaaaaa, 'wrong gas_price'); + assert(call_ctx.value() == 123456789, 'wrong value'); + assert(call_ctx.bytecode() == bytecode, 'wrong bytecode'); + assert(call_ctx.calldata() == array![4, 5, 6].span(), 'wrong calldata'); } #[test] @@ -161,3 +161,13 @@ fn test_return_data() { let return_data = machine.return_data(); assert(return_data.len() == 0, 'wrong length'); } + + +#[test] +#[available_gas(20000000)] +fn test_child_return_data() { + let mut machine: Machine = setup_machine(); + + let return_data = machine.child_return_data().unwrap(); + assert(return_data == array![1, 2, 3].span(), 'wrong child return data'); +} diff --git a/crates/evm/src/tests/test_utils.cairo b/crates/evm/src/tests/test_utils.cairo index c79b432c3..c6f11de7d 100644 --- a/crates/evm/src/tests/test_utils.cairo +++ b/crates/evm/src/tests/test_utils.cairo @@ -1,4 +1,6 @@ -use evm::context::{CallContext, CallContextTrait, ExecutionContext, ExecutionContextTrait,}; +use evm::context::{ + CallContext, CallContextTrait, ExecutionContext, ExecutionContextTrait, DefaultOptionSpanU8 +}; use evm::machine::Machine; use starknet::{contract_address_try_from_felt252, ContractAddress, EthAddress}; @@ -33,18 +35,19 @@ fn setup_call_context() -> CallContext { fn setup_execution_context() -> ExecutionContext { let context_id = 0; - let call_context = setup_call_context(); + let call_ctx = setup_call_context(); let starknet_address: ContractAddress = starknet_address(); let evm_address: EthAddress = evm_address(); let return_data = Default::default(); + let child_return_data = Option::Some(array![1, 2, 3].span()); ExecutionContextTrait::new( context_id, evm_address, starknet_address, - call_context, - Default::default(), + call_ctx, Default::default(), + child_return_data, return_data, ) } @@ -62,7 +65,7 @@ fn setup_call_context_with_bytecode(bytecode: Span) -> CallContext { fn setup_execution_context_with_bytecode(bytecode: Span) -> ExecutionContext { let context_id = 0; - let call_context = setup_call_context_with_bytecode(bytecode); + let call_ctx = setup_call_context_with_bytecode(bytecode); let starknet_address: ContractAddress = starknet_address(); let evm_address: EthAddress = evm_address(); let return_data = Default::default(); @@ -71,7 +74,7 @@ fn setup_execution_context_with_bytecode(bytecode: Span) -> ExecutionContext context_id, evm_address, starknet_address, - call_context, + call_ctx, Default::default(), Default::default(), return_data, @@ -92,7 +95,7 @@ fn setup_call_context_with_calldata(calldata: Span) -> CallContext { fn setup_execution_context_with_calldata(calldata: Span) -> ExecutionContext { let context_id = 0; - let call_context = setup_call_context_with_calldata(calldata); + let call_ctx = setup_call_context_with_calldata(calldata); let starknet_address: ContractAddress = starknet_address(); let evm_address: EthAddress = evm_address(); let return_data = Default::default(); @@ -101,7 +104,7 @@ fn setup_execution_context_with_calldata(calldata: Span) -> ExecutionContext context_id, evm_address, starknet_address, - call_context, + call_ctx, Default::default(), Default::default(), return_data, @@ -119,7 +122,7 @@ impl CallContextPartialEq of PartialEq { fn setup_machine() -> Machine { Machine { - current_context: BoxTrait::new(setup_execution_context()), + current_ctx: BoxTrait::new(setup_execution_context()), ctx_count: 1, stack: Default::default(), memory: Default::default(), @@ -128,9 +131,9 @@ fn setup_machine() -> Machine { } fn setup_machine_with_bytecode(bytecode: Span) -> Machine { - let current_context = BoxTrait::new(setup_execution_context_with_bytecode(bytecode)); + let current_ctx = BoxTrait::new(setup_execution_context_with_bytecode(bytecode)); Machine { - current_context, + current_ctx, ctx_count: 1, stack: Default::default(), memory: Default::default(), @@ -139,9 +142,9 @@ fn setup_machine_with_bytecode(bytecode: Span) -> Machine { } fn setup_machine_with_calldata(calldata: Span) -> Machine { - let current_context = BoxTrait::new(setup_execution_context_with_calldata(calldata)); + let current_ctx = BoxTrait::new(setup_execution_context_with_calldata(calldata)); Machine { - current_context, + current_ctx, ctx_count: 1, stack: Default::default(), memory: Default::default(), diff --git a/docs/general/execution_context.md b/docs/general/execution_context.md index 83ee7d92e..13dd1a63c 100644 --- a/docs/general/execution_context.md +++ b/docs/general/execution_context.md @@ -21,13 +21,13 @@ classDiagram starknet_address: ContractAddress, program_counter: u32, status: Status, - call_context: CallContext, + call_ctx: CallContext, destroyed_contracts: Array~EthAddress~, events: Array~Event~, create_addresses: Array~EthAddress~, return_data: Array~u32~, - parent_context: Nullable~ExecutionContext~, - child_context: Nullable~ExecutionContext~, + parent_ctx: Nullable~ExecutionContext~, + child_return_data: Option~Span~u8~~ } class CallContext{ @@ -57,7 +57,7 @@ classDiagram ExecutionContext *-- Status ``` -When submitting a transaction to the EVM, the `call_context` field of the +When submitting a transaction to the EVM, the `call_ctx` field of the `ExecutionContext` is initialized with the bytecode of the contract to execute, the call data sent in the transaction, and the value of the transaction. The `ExecutionContext` could also hold the `Stack` and `Memory` data structures diff --git a/docs/general/machine.md b/docs/general/machine.md index 4fdcdc36a..71678c0cc 100644 --- a/docs/general/machine.md +++ b/docs/general/machine.md @@ -26,10 +26,12 @@ To overcome the problem stated above, we have come up with the following design: between the different execution contexts. - Each execution context has its own identifier `id`, which uniquely identifies it. -- Each execution context has a `parent_context` field, which value is either a +- Each execution context has a `parent_ctx` field, which value is either a pointer to its parent execution context or `null`. - - Each execution context has a `child_context` field, which value is either a - pointer to its child execution context or `null`. +- Each execution context has a `child_return_data` field, which value is either + nothing or the return data from the child context. This is meant to enable + opcodes `RETURNDATASIZE` and `RETURNDATACOPY`. These two opcodes are the only + ones enabling a current context to access its child context's return data. - The execution context tree is a directed acyclic graph, where each execution context has at most one parent, and at most one child. - A specific execution context is accessible by traversing the execution context @@ -69,13 +71,13 @@ classDiagram starknet_address: ContractAddress, program_counter: u32, status: Status, - call_context: CallContext, + call_ctx: CallContext, destroyed_contracts: Array~EthAddress~, events: Array~Event~, create_addresses: Array~EthAddress~, return_data: Array~u8~, - parent_context: Nullable~ExecutionContext~, - child_context: Nullable~ExecutionContext~, + parent_ctx: Nullable~ExecutionContext~, + child_return_data: Option~Span~u8~~ }