ink_e2e/
contract_results.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    fmt,
17    fmt::Debug,
18    marker::PhantomData,
19};
20
21use frame_support::pallet_prelude::{
22    Decode,
23    Encode,
24};
25use ink::codegen::ContractCallBuilder;
26use ink_env::{
27    Environment,
28    call::{
29        FromAddr,
30        utils::DecodeMessageResult,
31    },
32};
33use ink_primitives::{
34    Address,
35    ConstructorResult,
36    H256,
37    MessageResult,
38};
39use ink_revive_types::{
40    CodeUploadResult,
41    ExecReturnValue,
42    InstantiateReturnValue,
43    StorageDeposit,
44    evm::CallTrace,
45};
46use sp_runtime::{
47    DispatchError,
48    Weight,
49};
50
51/// Alias for the contract instantiate result.
52pub type ContractInstantiateResultFor<E> =
53    ContractResult<InstantiateReturnValue, <E as Environment>::Balance>;
54
55// todo use the obj one from `pallet-revive` instead
56/// Result type of a `bare_call`, `bare_instantiate`, `ReviveApi::call`, and
57/// `ReviveApi::instantiate`.
58///
59/// It contains the execution result together with some auxiliary information.
60///
61/// # Note
62///
63/// It has been extended to include `events` at the end of the struct while not bumping
64/// the `ReviveApi` version. Therefore when SCALE decoding a `ContractResult` its
65/// trailing data should be ignored to avoid any potential compatibility issues.
66#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)]
67pub struct ContractResult<R, Balance> {
68    /// How much weight was consumed during execution.
69    pub weight_consumed: Weight,
70    /// How much weight is required as gas limit in order to execute this call.
71    ///
72    /// This value should be used to determine the weight limit for on-chain execution.
73    ///
74    /// # Note
75    ///
76    /// This can only different from [`Self::weight_consumed`] when weight pre-charging
77    /// is used. Currently, only `seal_call_runtime` makes use of pre-charging.
78    /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
79    /// when a non-zero `gas_limit` argument is supplied.
80    pub weight_required: Weight,
81    /// How much balance was paid by the origin into the contract's deposit account in
82    /// order to pay for storage.
83    ///
84    /// The storage deposit is never actually charged from the origin in case of
85    /// [`Self::result`] is `Err`. This is because on error all storage changes are
86    /// rolled back including the payment of the deposit.
87    pub storage_deposit: StorageDeposit<Balance>,
88    /// The maximal storage deposit amount that occured at any time during the execution.
89    /// This can be higher than the final storage_deposit due to refunds
90    /// This is always a StorageDeposit::Charge(..)
91    pub max_storage_deposit: StorageDeposit<Balance>,
92    /// The amount of Ethereum gas that has been consumed during execution.
93    pub gas_consumed: Balance,
94    /// The execution result of the code.
95    pub result: Result<R, DispatchError>,
96}
97
98/// Alias for the contract exec result.
99pub type ContractExecResultFor<E> =
100    ContractResult<ExecReturnValue, <E as Environment>::Balance>;
101
102/// Result of a contract instantiation using bare call.
103pub struct BareInstantiationResult<E: Environment, EventLog> {
104    // The address at which the contract was instantiated.
105    pub addr: Address,
106    // The account id at which the contract was instantiated.
107    pub account_id: E::AccountId,
108    /// Events that happened with the contract instantiation.
109    pub events: EventLog,
110    /// Trace of the instantiated contract.
111    pub trace: Option<CallTrace>,
112    /// Code hash of the instantiated contract.
113    pub code_hash: H256,
114}
115
116/// We implement a custom `Debug` here, as to avoid requiring the trait bound
117/// `Debug` for `E`.
118impl<E: Environment, EventLog> Debug for BareInstantiationResult<E, EventLog>
119where
120    EventLog: Debug,
121{
122    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
123        f.debug_struct("BareInstantiationResult")
124            .field("addr", &self.addr)
125            .field("account_id", &self.account_id.encode())
126            .field("events", &self.events)
127            .field("trace", &self.trace)
128            .field("code_hash", &self.code_hash)
129            .finish()
130    }
131}
132
133/// Result of a contract instantiation.
134pub struct InstantiationResult<E: Environment, EventLog, Abi> {
135    /// The address at which the contract was instantiated.
136    pub addr: Address,
137    /// The account id at which the contract was instantiated.
138    pub account_id: E::AccountId,
139    /// The result of the dry run, contains debug messages
140    /// if there were any.
141    pub dry_run: InstantiateDryRunResult<E, Abi>,
142    /// Events that happened with the contract instantiation.
143    pub events: EventLog,
144    /// todo
145    pub trace: Option<CallTrace>,
146    /// todo
147    pub code_hash: H256,
148}
149
150impl<E: Environment, EventLog, Abi> InstantiationResult<E, EventLog, Abi> {
151    /// Returns a call builder for the contract which was instantiated.
152    ///
153    /// # Note
154    ///
155    /// This uses the ABI used for the contract instantiation call.
156    pub fn call_builder<Contract>(&self) -> <Contract as ContractCallBuilder>::Type<Abi>
157    where
158        Contract: ContractCallBuilder,
159        <Contract as ContractCallBuilder>::Type<Abi>: FromAddr,
160    {
161        <<Contract as ContractCallBuilder>::Type<Abi> as FromAddr>::from_addr(self.addr)
162    }
163
164    /// Returns a call builder for the specified ABI for the contract which was
165    /// instantiated.
166    ///
167    /// # Note
168    ///
169    /// This is useful for contracts that support multiple ABIs.
170    pub fn call_builder_abi<Contract, CallAbi>(
171        &self,
172    ) -> <Contract as ContractCallBuilder>::Type<CallAbi>
173    where
174        Contract: ContractCallBuilder,
175        <Contract as ContractCallBuilder>::Type<CallAbi>: FromAddr,
176    {
177        <<Contract as ContractCallBuilder>::Type<CallAbi> as FromAddr>::from_addr(
178            self.addr,
179        )
180    }
181}
182
183/// We implement a custom `Debug` here, as to avoid requiring the trait bound `Debug` for
184/// `E`.
185impl<E: Environment, EventLog, Abi> Debug for InstantiationResult<E, EventLog, Abi>
186where
187    E::AccountId: Debug,
188    E::Balance: Debug,
189    E::EventRecord: Debug,
190    EventLog: Debug,
191{
192    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
193        // todo add missing fields
194        f.debug_struct("InstantiationResult")
195            .field("addr", &self.addr)
196            .field("dry_run", &self.dry_run)
197            .field("events", &self.events)
198            .finish()
199    }
200}
201
202/// Result of a contract upload.
203pub struct UploadResult<E: Environment, EventLog> {
204    /// The hash with which the contract can be instantiated.
205    pub code_hash: H256,
206    /// The result of the dry run, contains debug messages if there were any.
207    pub dry_run: CodeUploadResult<E::Balance>,
208    /// Events that happened with the contract instantiation.
209    pub events: EventLog,
210}
211
212/// We implement a custom `Debug` here, to avoid requiring the trait bound `Debug` for
213/// `E`.
214impl<E: Environment, EventLog> Debug for UploadResult<E, EventLog>
215where
216    E::Balance: Debug,
217    H256: Debug,
218    EventLog: Debug,
219{
220    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
221        f.debug_struct("UploadResult")
222            .field("code_hash", &self.code_hash)
223            .field("dry_run", &self.dry_run)
224            .field("events", &self.events)
225            .finish()
226    }
227}
228
229/// Result of a contract call.
230pub struct CallResult<E: Environment, V, EventLog, Abi> {
231    /// The result of the dry run, contains debug messages if there were any.
232    pub dry_run: CallDryRunResult<E, V, Abi>,
233    /// Events that happened with the contract instantiation.
234    pub events: EventLog,
235    /// todo
236    pub trace: Option<CallTrace>,
237}
238
239impl<E: Environment, V: DecodeMessageResult<Abi>, EventLog, Abi>
240    CallResult<E, V, EventLog, Abi>
241{
242    /// Returns the [`MessageResult`] from the execution of the dry-run message
243    /// call.
244    ///
245    /// # Panics
246    /// - if the dry-run message call failed to execute.
247    /// - if message result cannot be decoded into the expected return value type.
248    pub fn message_result(&self) -> MessageResult<V> {
249        self.dry_run.message_result()
250    }
251
252    /// Returns the decoded return value of the message from the dry-run.
253    ///
254    /// Panics if the value could not be decoded. The raw bytes can be accessed
255    /// via [`CallResult::return_data`].
256    pub fn return_value(self) -> V {
257        self.dry_run.return_value()
258    }
259}
260
261impl<E: Environment, V, EventLog, Abi> CallResult<E, V, EventLog, Abi> {
262    /// Returns the return value of the message dry-run as raw bytes.
263    ///
264    /// Panics if the dry-run message call failed to execute.
265    pub fn return_data(&self) -> &[u8] {
266        &self.dry_run.exec_return_value().data
267    }
268
269    /// Returns the error from nested contract calls (e.g., precompile errors)
270    /// if available in the trace, otherwise returns the raw error data.
271    pub fn extract_error(&self) -> Option<String> {
272        if !self.dry_run.did_revert() {
273            return None;
274        }
275
276        // Check trace for error information
277        if let Some(trace) = &self.trace {
278            // // Check nested calls first (more specific errors)
279            for call in &trace.calls {
280                if let Some(error) = &call.error {
281                    return Some(error.clone());
282                }
283            }
284
285            // Then check top-level error
286            if let Some(error) = &trace.error {
287                return Some(error.clone());
288            }
289        }
290        // Fallback to raw data
291        Some(format!("{:?}", self.return_data()))
292    }
293}
294
295// TODO(#xxx) Improve the `Debug` implementation.
296impl<E: Environment, V, EventLog, Abi> Debug for CallResult<E, V, EventLog, Abi>
297where
298    E: Debug,
299    E::Balance: Debug,
300    E::EventRecord: Debug,
301    V: Debug,
302    EventLog: Debug,
303{
304    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
305        f.debug_struct("CallResult")
306            .field("dry_run", &self.dry_run)
307            .field("events", &self.events)
308            .field("trace", &self.trace)
309            .finish()
310    }
311}
312
313/// Result of the dry run of a contract call.
314pub struct CallDryRunResult<E: Environment, V, Abi> {
315    /// The result of the dry run, contains debug messages if there were any.
316    pub exec_result: ContractExecResultFor<E>,
317    /// The execution trace (if any).
318    pub trace: Option<CallTrace>,
319    /// Phantom data for return type and its ABI encoding.
320    pub _marker: PhantomData<(V, Abi)>,
321}
322
323/// We implement a custom `Debug` here, as to avoid requiring the trait bound `Debug` for
324/// `E`.
325impl<E: Environment, V, Abi> Debug for CallDryRunResult<E, V, Abi>
326where
327    E::Balance: Debug,
328    E::EventRecord: Debug,
329{
330    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
331        f.debug_struct("CallDryRunResult")
332            .field("exec_result", &self.exec_result)
333            .field("trace", &self.trace)
334            .finish()
335    }
336}
337
338impl<E: Environment, V, Abi> CallDryRunResult<E, V, Abi> {
339    /// Returns true if the dry-run execution resulted in an error.
340    pub fn is_err(&self) -> bool {
341        self.exec_result.result.is_err() || self.did_revert()
342    }
343
344    /// Returns the [`ExecReturnValue`] resulting from the dry-run message call.
345    ///
346    /// Panics if the dry-run message call failed to execute.
347    pub fn exec_return_value(&self) -> &ExecReturnValue {
348        self.exec_result
349            .result
350            .as_ref()
351            .unwrap_or_else(|call_err| panic!("Call dry-run failed: {call_err:?}"))
352    }
353
354    /// Returns true if the message call reverted.
355    pub fn did_revert(&self) -> bool {
356        let res = self.exec_result.result.clone().expect("no result found");
357        res.did_revert()
358    }
359
360    /// Returns the return value as raw bytes of the message from the dry-run.
361    ///
362    /// Panics if the dry-run message call failed to execute.
363    pub fn return_data(&self) -> &[u8] {
364        &self.exec_return_value().data
365    }
366}
367
368impl<E: Environment, V: DecodeMessageResult<Abi>, Abi> CallDryRunResult<E, V, Abi> {
369    /// Returns the [`MessageResult`] from the execution of the dry-run message call.
370    ///
371    /// # Panics
372    /// - if the dry-run message call failed to execute.
373    /// - if message result cannot be decoded into the expected return value type.
374    pub fn message_result(&self) -> MessageResult<V> {
375        let data = &self.exec_return_value().data;
376        DecodeMessageResult::decode_output(data.as_ref(), self.did_revert()).unwrap_or_else(|env_err| {
377            panic!(
378                "Decoding dry run result to ink! message return type failed: {env_err:?} {:?}\n\n\
379                Attempt to stringify returned data: {:?}",
380                self.exec_return_value(),
381                String::from_utf8_lossy(&self.exec_return_value().data[..])
382            )
383        })
384    }
385
386    /// Returns the decoded return value of the message from the dry-run.
387    ///
388    /// Panics if the value could not be decoded. The raw bytes can be accessed via
389    /// [`CallResult::return_data`].
390    pub fn return_value(&self) -> V {
391        self.message_result()
392            .unwrap_or_else(|lang_err| {
393                panic!(
394                    "Encountered a `LangError` while decoding dry run result to ink! message: {lang_err:?}"
395                )
396            })
397    }
398}
399
400/// Result of the dry run of a contract call.
401#[derive(Clone)]
402pub struct InstantiateDryRunResult<E: Environment, Abi> {
403    /// The result of the dry run, contains debug messages if there were any.
404    pub contract_result: ContractInstantiateResultFor<E>,
405    /// Phantom data for return type and its ABI encoding.
406    pub _marker: PhantomData<Abi>,
407}
408
409impl<E: Environment, Abi> From<ContractInstantiateResultFor<E>>
410    for InstantiateDryRunResult<E, Abi>
411{
412    fn from(contract_result: ContractInstantiateResultFor<E>) -> Self {
413        Self {
414            contract_result,
415            _marker: PhantomData,
416        }
417    }
418}
419
420impl<E: Environment, Abi> InstantiateDryRunResult<E, Abi> {
421    /// Returns true if the dry-run execution resulted in an error.
422    pub fn is_err(&self) -> bool {
423        self.contract_result.result.is_err() || self.did_revert()
424    }
425
426    /// Returns the [`InstantiateReturnValue`] resulting from the dry-run message call.
427    ///
428    /// Panics if the dry-run message call failed to execute.
429    pub fn instantiate_return_value(&self) -> &InstantiateReturnValue {
430        self.contract_result
431            .result
432            .as_ref()
433            .unwrap_or_else(|call_err| panic!("Instantiate dry-run failed: {call_err:?}"))
434    }
435
436    /// Returns the encoded return value from the constructor.
437    ///
438    /// # Panics
439    /// - if the dry-run message instantiate failed to execute.
440    /// - if message result cannot be decoded into the expected return value type.
441    pub fn constructor_result<V: DecodeMessageResult<Abi>>(
442        &self,
443    ) -> ConstructorResult<V> {
444        let data = &self.instantiate_return_value().result.data;
445        DecodeMessageResult::decode_output(data.as_ref(), self.did_revert()).unwrap_or_else(|env_err| {
446            panic!("Decoding dry run result to constructor return type failed: {env_err:?}")
447        })
448    }
449
450    /// Returns the return value of the instantiation dry-run as raw bytes.
451    ///
452    /// Panics if the dry-run message call failed to execute.
453    pub fn return_data(&self) -> &[u8] {
454        &self.instantiate_return_value().result.data
455    }
456
457    /// Returns true if the instantiation dry-run reverted.
458    pub fn did_revert(&self) -> bool {
459        let res = self.instantiate_return_value().clone().result;
460        res.did_revert()
461    }
462}
463
464impl<E, Abi> Debug for InstantiateDryRunResult<E, Abi>
465where
466    E: Environment,
467    E::AccountId: Debug,
468    E::Balance: Debug,
469    E::EventRecord: Debug,
470{
471    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
472        f.debug_struct("InstantiateDryRunResult")
473            .field("contract_result", &self.contract_result)
474            .finish()
475    }
476}