-
Notifications
You must be signed in to change notification settings - Fork 109
Expand file tree
/
Copy pathsubcall.rs
More file actions
148 lines (139 loc) · 6.85 KB
/
Copy pathsubcall.rs
File metadata and controls
148 lines (139 loc) · 6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2026 Circle Internet Group, Inc. All rights reserved.
//
// 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.
//! Subcall precompile trait and types.
//!
//! Defines the two-phase execution interface for precompiles that need to spawn
//! child EVM call frames. Implementations live alongside this module (e.g.,
//! [`crate::call_from::CallFromPrecompile`]). The registry and continuation storage
//! that drives the execution loop live in `arc-evm`.
use alloy_primitives::Bytes;
use revm::handler::FrameResult;
use revm::interpreter::interpreter_action::CallInputs;
use std::any::Any;
/// Trait for precompiles that spawn child EVM call frames.
///
/// Implementors define a two-phase execution model:
/// - [`init_subcall`](SubcallPrecompile::init_subcall): Decodes the precompile input and returns a
/// child call request plus any continuation state needed for completion.
/// - [`complete_subcall`](SubcallPrecompile::complete_subcall): Receives the child call result and
/// continuation state, producing the final precompile output.
///
/// # Constraint: child target must not be a subcall precompile
///
/// The child frame returned by `init_subcall` is dispatched via revm's standard
/// `init_with_context`, which does **not** consult the subcall registry. If
/// `child_inputs.target_address` is another subcall precompile address, the call will hit the
/// stub bytecode at that genesis address (typically `0x01` / `ADD`) and silently revert, rather
/// than triggering the target's two-phase execution.
///
/// Indirect calls work fine: if the child targets a regular contract that then CALLs a subcall
/// precompile, the CALL opcode triggers a new `frame_init` cycle through `ArcEvm`, which
/// consults the subcall registry as expected.
///
/// # Checkpoint and revert semantics
///
/// The subcall framework takes a journal checkpoint **before** dispatching the child frame.
/// If `complete_subcall` returns `success: false` or `Err` when the child succeeded, the
/// framework reverts the child's committed state changes using that checkpoint.
///
/// Returning `success: true` when the child failed is fine (e.g., `CallFromPrecompile`
/// always succeeds and encodes the child's outcome in its output bytes). The child's
/// checkpoint was already reverted, so no state leaks.
pub trait SubcallPrecompile: Send + Sync {
/// Decode precompile input and produce a child call request.
///
/// Called during `frame_init` when the EVM encounters a call to this precompile's address.
/// Returns either a subcall request (with continuation data for `complete_subcall`) or an
/// error.
///
/// # Sender constraint
///
/// The framework enforces that `child_inputs.caller` must equal `call_inputs.caller`
/// (the address that called this precompile) or `tx.origin` (the signing EOA).
/// Setting `child_inputs.caller` to any other address will cause the framework to
/// revert with "sender spoofing requires tx.origin as sender".
fn init_subcall(&self, inputs: &CallInputs) -> Result<SubcallInitResult, SubcallError>;
/// Finalize the precompile result after the child call completes.
///
/// Called during `frame_return_result` when the child frame finishes execution.
/// Receives the continuation data from `init_subcall` and the child's frame result.
///
/// # Note on checkpoint semantics
///
/// Returning `success: false` or `Err` when the child succeeded will cause the
/// framework to revert the child's committed state changes. See the trait-level docs.
fn complete_subcall(
&self,
continuation_data: SubcallContinuationData,
child_result: &FrameResult,
) -> Result<SubcallCompletionResult, SubcallError>;
/// Construct the child [`CallInputs`] for tracing purposes.
///
/// Called by `ArcEvm::inspect_frame_init` *before* `init_subcall` to obtain the
/// child call's identity (caller, target, gas limit, calldata). The returned
/// inputs are passed to the inspector's `frame_start`, making the precompile
/// transparent in `debug_traceTransaction` output — the trace shows the logical
/// child call instead of the precompile address.
///
/// The returned `CallInputs` should match what `init_subcall` would produce
/// (same caller, target, gas limit, calldata). Implementations should share
/// the decoding logic with `init_subcall` to keep them in sync.
///
/// Returns `None` if the input cannot be decoded (e.g., malformed calldata).
/// In that case the trace falls back to the precompile's own address, so the
/// debug trace for error cases will differ from the successful case (showing
/// the precompile call instead of the logical child call).
fn trace_child_call(&self, _inputs: &CallInputs) -> Option<CallInputs> {
None
}
}
/// Result of a successful [`SubcallPrecompile::init_subcall`] execution.
pub struct SubcallInitResult {
/// The child call inputs to execute.
pub child_inputs: Box<CallInputs>,
/// Opaque state carried from `init_subcall` to `complete_subcall`.
pub continuation_data: SubcallContinuationData,
/// Gas consumed by the precompile itself (ABI decoding, validation).
/// Deducted from the caller's gas budget before the 63/64ths split.
pub gas_overhead: u64,
}
/// Opaque state blob carried between `init_subcall` and `complete_subcall`.
pub struct SubcallContinuationData {
/// Precompile-specific state (e.g., memo bytes, decoded parameters).
pub state: Box<dyn Any + Send>,
}
/// Result of a successful [`SubcallPrecompile::complete_subcall`] execution.
pub struct SubcallCompletionResult {
/// The output bytes to return to the caller.
pub output: Bytes,
/// Whether the precompile considers the call successful.
pub success: bool,
/// Gas consumed by the completion phase (e.g., ABI encoding the return data).
/// Added to the total gas_used for the precompile invocation.
pub gas_overhead: u64,
}
/// Errors that can occur during subcall precompile execution.
#[derive(Debug, thiserror::Error)]
pub enum SubcallError {
#[error("ABI decode error: {0}")]
AbiDecodeError(String),
#[error("unexpected frame result type (expected Call)")]
UnexpectedFrameResult,
#[error("insufficient gas: {0}")]
InsufficientGas(String),
#[error("internal error: {0}")]
InternalError(String),
}