ink_e2e 6.0.0-beta.2

[ink!] End-to-end testing framework for smart contracts.
Documentation
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
// Copyright (C) Use Ink (UK) Ltd.
//
// 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.

use std::{
    fmt,
    fmt::Debug,
    marker::PhantomData,
};

use frame_support::pallet_prelude::{
    Decode,
    Encode,
};
use ink::codegen::ContractCallBuilder;
use ink_env::{
    Environment,
    call::{
        FromAddr,
        utils::DecodeMessageResult,
    },
};
use ink_primitives::{
    Address,
    ConstructorResult,
    H256,
    MessageResult,
};
use ink_revive_types::{
    CodeUploadResult,
    ExecReturnValue,
    InstantiateReturnValue,
    StorageDeposit,
    evm::CallTrace,
};
use sp_runtime::{
    DispatchError,
    Weight,
};

/// Alias for the contract instantiate result.
pub type ContractInstantiateResultFor<E> =
    ContractResult<InstantiateReturnValue, <E as Environment>::Balance>;

// todo use the obj one from `pallet-revive` instead
/// Result type of a `bare_call`, `bare_instantiate`, `ReviveApi::call`, and
/// `ReviveApi::instantiate`.
///
/// It contains the execution result together with some auxiliary information.
///
/// # Note
///
/// It has been extended to include `events` at the end of the struct while not bumping
/// the `ReviveApi` version. Therefore when SCALE decoding a `ContractResult` its
/// trailing data should be ignored to avoid any potential compatibility issues.
#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)]
pub struct ContractResult<R, Balance> {
    /// How much weight was consumed during execution.
    pub weight_consumed: Weight,
    /// How much weight is required as gas limit in order to execute this call.
    ///
    /// This value should be used to determine the weight limit for on-chain execution.
    ///
    /// # Note
    ///
    /// This can only different from [`Self::weight_consumed`] when weight pre-charging
    /// is used. Currently, only `seal_call_runtime` makes use of pre-charging.
    /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
    /// when a non-zero `gas_limit` argument is supplied.
    pub weight_required: Weight,
    /// How much balance was paid by the origin into the contract's deposit account in
    /// order to pay for storage.
    ///
    /// The storage deposit is never actually charged from the origin in case of
    /// [`Self::result`] is `Err`. This is because on error all storage changes are
    /// rolled back including the payment of the deposit.
    pub storage_deposit: StorageDeposit<Balance>,
    /// The maximal storage deposit amount that occured at any time during the execution.
    /// This can be higher than the final storage_deposit due to refunds
    /// This is always a StorageDeposit::Charge(..)
    pub max_storage_deposit: StorageDeposit<Balance>,
    /// The amount of Ethereum gas that has been consumed during execution.
    pub gas_consumed: Balance,
    /// The execution result of the code.
    pub result: Result<R, DispatchError>,
}

/// Alias for the contract exec result.
pub type ContractExecResultFor<E> =
    ContractResult<ExecReturnValue, <E as Environment>::Balance>;

/// Result of a contract instantiation using bare call.
pub struct BareInstantiationResult<E: Environment, EventLog> {
    // The address at which the contract was instantiated.
    pub addr: Address,
    // The account id at which the contract was instantiated.
    pub account_id: E::AccountId,
    /// Events that happened with the contract instantiation.
    pub events: EventLog,
    /// Trace of the instantiated contract.
    pub trace: Option<CallTrace>,
    /// Code hash of the instantiated contract.
    pub code_hash: H256,
}

/// We implement a custom `Debug` here, as to avoid requiring the trait bound
/// `Debug` for `E`.
impl<E: Environment, EventLog> Debug for BareInstantiationResult<E, EventLog>
where
    EventLog: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        f.debug_struct("BareInstantiationResult")
            .field("addr", &self.addr)
            .field("account_id", &self.account_id.encode())
            .field("events", &self.events)
            .field("trace", &self.trace)
            .field("code_hash", &self.code_hash)
            .finish()
    }
}

/// Result of a contract instantiation.
pub struct InstantiationResult<E: Environment, EventLog, Abi> {
    /// The address at which the contract was instantiated.
    pub addr: Address,
    /// The account id at which the contract was instantiated.
    pub account_id: E::AccountId,
    /// The result of the dry run, contains debug messages
    /// if there were any.
    pub dry_run: InstantiateDryRunResult<E, Abi>,
    /// Events that happened with the contract instantiation.
    pub events: EventLog,
    /// todo
    pub trace: Option<CallTrace>,
    /// todo
    pub code_hash: H256,
}

impl<E: Environment, EventLog, Abi> InstantiationResult<E, EventLog, Abi> {
    /// Returns a call builder for the contract which was instantiated.
    ///
    /// # Note
    ///
    /// This uses the ABI used for the contract instantiation call.
    pub fn call_builder<Contract>(&self) -> <Contract as ContractCallBuilder>::Type<Abi>
    where
        Contract: ContractCallBuilder,
        <Contract as ContractCallBuilder>::Type<Abi>: FromAddr,
    {
        <<Contract as ContractCallBuilder>::Type<Abi> as FromAddr>::from_addr(self.addr)
    }

    /// Returns a call builder for the specified ABI for the contract which was
    /// instantiated.
    ///
    /// # Note
    ///
    /// This is useful for contracts that support multiple ABIs.
    pub fn call_builder_abi<Contract, CallAbi>(
        &self,
    ) -> <Contract as ContractCallBuilder>::Type<CallAbi>
    where
        Contract: ContractCallBuilder,
        <Contract as ContractCallBuilder>::Type<CallAbi>: FromAddr,
    {
        <<Contract as ContractCallBuilder>::Type<CallAbi> as FromAddr>::from_addr(
            self.addr,
        )
    }
}

/// We implement a custom `Debug` here, as to avoid requiring the trait bound `Debug` for
/// `E`.
impl<E: Environment, EventLog, Abi> Debug for InstantiationResult<E, EventLog, Abi>
where
    E::AccountId: Debug,
    E::Balance: Debug,
    E::EventRecord: Debug,
    EventLog: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        // todo add missing fields
        f.debug_struct("InstantiationResult")
            .field("addr", &self.addr)
            .field("dry_run", &self.dry_run)
            .field("events", &self.events)
            .finish()
    }
}

/// Result of a contract upload.
pub struct UploadResult<E: Environment, EventLog> {
    /// The hash with which the contract can be instantiated.
    pub code_hash: H256,
    /// The result of the dry run, contains debug messages if there were any.
    pub dry_run: CodeUploadResult<E::Balance>,
    /// Events that happened with the contract instantiation.
    pub events: EventLog,
}

/// We implement a custom `Debug` here, to avoid requiring the trait bound `Debug` for
/// `E`.
impl<E: Environment, EventLog> Debug for UploadResult<E, EventLog>
where
    E::Balance: Debug,
    H256: Debug,
    EventLog: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        f.debug_struct("UploadResult")
            .field("code_hash", &self.code_hash)
            .field("dry_run", &self.dry_run)
            .field("events", &self.events)
            .finish()
    }
}

/// Result of a contract call.
pub struct CallResult<E: Environment, V, EventLog, Abi> {
    /// The result of the dry run, contains debug messages if there were any.
    pub dry_run: CallDryRunResult<E, V, Abi>,
    /// Events that happened with the contract instantiation.
    pub events: EventLog,
    /// todo
    pub trace: Option<CallTrace>,
}

impl<E: Environment, V: DecodeMessageResult<Abi>, EventLog, Abi>
    CallResult<E, V, EventLog, Abi>
{
    /// Returns the [`MessageResult`] from the execution of the dry-run message
    /// call.
    ///
    /// # Panics
    /// - if the dry-run message call failed to execute.
    /// - if message result cannot be decoded into the expected return value type.
    pub fn message_result(&self) -> MessageResult<V> {
        self.dry_run.message_result()
    }

    /// Returns the decoded return value of the message from the dry-run.
    ///
    /// Panics if the value could not be decoded. The raw bytes can be accessed
    /// via [`CallResult::return_data`].
    pub fn return_value(self) -> V {
        self.dry_run.return_value()
    }
}

impl<E: Environment, V, EventLog, Abi> CallResult<E, V, EventLog, Abi> {
    /// Returns the return value of the message dry-run as raw bytes.
    ///
    /// Panics if the dry-run message call failed to execute.
    pub fn return_data(&self) -> &[u8] {
        &self.dry_run.exec_return_value().data
    }

    /// Returns the error from nested contract calls (e.g., precompile errors)
    /// if available in the trace, otherwise returns the raw error data.
    pub fn extract_error(&self) -> Option<String> {
        if !self.dry_run.did_revert() {
            return None;
        }

        // Check trace for error information
        if let Some(trace) = &self.trace {
            // // Check nested calls first (more specific errors)
            for call in &trace.calls {
                if let Some(error) = &call.error {
                    return Some(error.clone());
                }
            }

            // Then check top-level error
            if let Some(error) = &trace.error {
                return Some(error.clone());
            }
        }
        // Fallback to raw data
        Some(format!("{:?}", self.return_data()))
    }
}

// TODO(#xxx) Improve the `Debug` implementation.
impl<E: Environment, V, EventLog, Abi> Debug for CallResult<E, V, EventLog, Abi>
where
    E: Debug,
    E::Balance: Debug,
    E::EventRecord: Debug,
    V: Debug,
    EventLog: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        f.debug_struct("CallResult")
            .field("dry_run", &self.dry_run)
            .field("events", &self.events)
            .field("trace", &self.trace)
            .finish()
    }
}

/// Result of the dry run of a contract call.
pub struct CallDryRunResult<E: Environment, V, Abi> {
    /// The result of the dry run, contains debug messages if there were any.
    pub exec_result: ContractExecResultFor<E>,
    /// The execution trace (if any).
    pub trace: Option<CallTrace>,
    /// Phantom data for return type and its ABI encoding.
    pub _marker: PhantomData<(V, Abi)>,
}

/// We implement a custom `Debug` here, as to avoid requiring the trait bound `Debug` for
/// `E`.
impl<E: Environment, V, Abi> Debug for CallDryRunResult<E, V, Abi>
where
    E::Balance: Debug,
    E::EventRecord: Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        f.debug_struct("CallDryRunResult")
            .field("exec_result", &self.exec_result)
            .field("trace", &self.trace)
            .finish()
    }
}

impl<E: Environment, V, Abi> CallDryRunResult<E, V, Abi> {
    /// Returns true if the dry-run execution resulted in an error.
    pub fn is_err(&self) -> bool {
        self.exec_result.result.is_err() || self.did_revert()
    }

    /// Returns the [`ExecReturnValue`] resulting from the dry-run message call.
    ///
    /// Panics if the dry-run message call failed to execute.
    pub fn exec_return_value(&self) -> &ExecReturnValue {
        self.exec_result
            .result
            .as_ref()
            .unwrap_or_else(|call_err| panic!("Call dry-run failed: {call_err:?}"))
    }

    /// Returns true if the message call reverted.
    pub fn did_revert(&self) -> bool {
        let res = self.exec_result.result.clone().expect("no result found");
        res.did_revert()
    }

    /// Returns the return value as raw bytes of the message from the dry-run.
    ///
    /// Panics if the dry-run message call failed to execute.
    pub fn return_data(&self) -> &[u8] {
        &self.exec_return_value().data
    }
}

impl<E: Environment, V: DecodeMessageResult<Abi>, Abi> CallDryRunResult<E, V, Abi> {
    /// Returns the [`MessageResult`] from the execution of the dry-run message call.
    ///
    /// # Panics
    /// - if the dry-run message call failed to execute.
    /// - if message result cannot be decoded into the expected return value type.
    pub fn message_result(&self) -> MessageResult<V> {
        let data = &self.exec_return_value().data;
        DecodeMessageResult::decode_output(data.as_ref(), self.did_revert()).unwrap_or_else(|env_err| {
            panic!(
                "Decoding dry run result to ink! message return type failed: {env_err:?} {:?}\n\n\
                Attempt to stringify returned data: {:?}",
                self.exec_return_value(),
                String::from_utf8_lossy(&self.exec_return_value().data[..])
            )
        })
    }

    /// Returns the decoded return value of the message from the dry-run.
    ///
    /// Panics if the value could not be decoded. The raw bytes can be accessed via
    /// [`CallResult::return_data`].
    pub fn return_value(&self) -> V {
        self.message_result()
            .unwrap_or_else(|lang_err| {
                panic!(
                    "Encountered a `LangError` while decoding dry run result to ink! message: {lang_err:?}"
                )
            })
    }
}

/// Result of the dry run of a contract call.
#[derive(Clone)]
pub struct InstantiateDryRunResult<E: Environment, Abi> {
    /// The result of the dry run, contains debug messages if there were any.
    pub contract_result: ContractInstantiateResultFor<E>,
    /// Phantom data for return type and its ABI encoding.
    pub _marker: PhantomData<Abi>,
}

impl<E: Environment, Abi> From<ContractInstantiateResultFor<E>>
    for InstantiateDryRunResult<E, Abi>
{
    fn from(contract_result: ContractInstantiateResultFor<E>) -> Self {
        Self {
            contract_result,
            _marker: PhantomData,
        }
    }
}

impl<E: Environment, Abi> InstantiateDryRunResult<E, Abi> {
    /// Returns true if the dry-run execution resulted in an error.
    pub fn is_err(&self) -> bool {
        self.contract_result.result.is_err() || self.did_revert()
    }

    /// Returns the [`InstantiateReturnValue`] resulting from the dry-run message call.
    ///
    /// Panics if the dry-run message call failed to execute.
    pub fn instantiate_return_value(&self) -> &InstantiateReturnValue {
        self.contract_result
            .result
            .as_ref()
            .unwrap_or_else(|call_err| panic!("Instantiate dry-run failed: {call_err:?}"))
    }

    /// Returns the encoded return value from the constructor.
    ///
    /// # Panics
    /// - if the dry-run message instantiate failed to execute.
    /// - if message result cannot be decoded into the expected return value type.
    pub fn constructor_result<V: DecodeMessageResult<Abi>>(
        &self,
    ) -> ConstructorResult<V> {
        let data = &self.instantiate_return_value().result.data;
        DecodeMessageResult::decode_output(data.as_ref(), self.did_revert()).unwrap_or_else(|env_err| {
            panic!("Decoding dry run result to constructor return type failed: {env_err:?}")
        })
    }

    /// Returns the return value of the instantiation dry-run as raw bytes.
    ///
    /// Panics if the dry-run message call failed to execute.
    pub fn return_data(&self) -> &[u8] {
        &self.instantiate_return_value().result.data
    }

    /// Returns true if the instantiation dry-run reverted.
    pub fn did_revert(&self) -> bool {
        let res = self.instantiate_return_value().clone().result;
        res.did_revert()
    }
}

impl<E, Abi> Debug for InstantiateDryRunResult<E, Abi>
where
    E: Environment,
    E::AccountId: Debug,
    E::Balance: Debug,
    E::EventRecord: Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("InstantiateDryRunResult")
            .field("contract_result", &self.contract_result)
            .finish()
    }
}