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}