ink_e2e/
backend.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 ink_env::{
16    Environment,
17    call::utils::{
18        DecodeMessageResult,
19        EncodeArgsWith,
20    },
21};
22use ink_primitives::H160;
23use ink_revive_types::evm::CallTrace;
24use jsonrpsee::core::async_trait;
25use sp_weights::Weight;
26use subxt::dynamic::Value;
27
28use super::{
29    H256,
30    InstantiateDryRunResult,
31    Keypair,
32};
33use crate::{
34    CallBuilder,
35    CallBuilderFinal,
36    CallDryRunResult,
37    UploadResult,
38    backend_calls::{
39        InstantiateBuilder,
40        RemoveCodeBuilder,
41        UploadBuilder,
42    },
43    builders::CreateBuilderPartial,
44    contract_results::BareInstantiationResult,
45};
46
47/// Full E2E testing backend: combines general chain API and contract-specific operations.
48#[async_trait]
49pub trait E2EBackend<E: Environment>: ChainBackend + BuilderClient<E> {}
50
51/// General chain operations useful in contract testing.
52#[async_trait]
53pub trait ChainBackend {
54    /// Account type.
55    type AccountId;
56    /// Balance type.
57    type Balance: Send + From<u32> + std::fmt::Debug;
58    /// Error type.
59    type Error;
60    /// Event log type.
61    type EventLog;
62
63    /// Generate a new account and fund it with the given `amount` of tokens from the
64    /// `origin`.
65    async fn create_and_fund_account(
66        &mut self,
67        origin: &Keypair,
68        amount: Self::Balance,
69    ) -> Keypair;
70
71    /// Returns the free balance of `account`.
72    async fn free_balance(
73        &mut self,
74        account: Self::AccountId,
75    ) -> Result<Self::Balance, Self::Error>;
76
77    /// Executes a runtime call `call_name` for the `pallet_name`.
78    /// The `call_data` is a `Vec<Value>`.
79    ///
80    /// Note:
81    /// - `pallet_name` must be in camel case, for example `Balances`.
82    /// - `call_name` must be snake case, for example `force_transfer`.
83    /// - `call_data` is a `Vec<subxt::dynamic::Value>` that holds a representation of
84    ///   some value.
85    ///
86    /// Returns when the transaction is included in a block. The return value contains all
87    /// events that are associated with this transaction.
88    ///
89    /// Since we might run node with an arbitrary runtime, this method inherently must
90    /// support dynamic calls.
91    async fn runtime_call<'a>(
92        &mut self,
93        origin: &Keypair,
94        pallet_name: &'a str,
95        call_name: &'a str,
96        call_data: Vec<Value>,
97    ) -> Result<Self::EventLog, Self::Error>;
98
99    /// Attempt to transfer the `value` from `origin` to `dest`.
100    ///
101    /// Returns `Ok` on success, and a [`subxt::Error`] if the extrinsic is
102    /// invalid (e.g. out of date nonce)
103    async fn transfer_allow_death(
104        &mut self,
105        origin: &Keypair,
106        dest: Self::AccountId,
107        value: Self::Balance,
108    ) -> Result<(), Self::Error>;
109}
110
111/// Contract-specific operations.
112#[async_trait]
113pub trait ContractsBackend<E: Environment> {
114    /// Error type.
115    type Error;
116    /// Event log type.
117    type EventLog;
118
119    /// Start building an instantiate call using a builder pattern.
120    ///
121    /// # Example
122    ///
123    /// ```ignore
124    /// // Constructor method
125    /// let mut constructor = FlipperRef::new(false);
126    /// let contract = client
127    ///     .instantiate("flipper", &ink_e2e::alice(), &mut constructor)
128    ///     // Optional arguments
129    ///     // Send 100 units with the call.
130    ///     .value(100)
131    ///     // Add 10% margin to the gas limit
132    ///     .extra_gas_portion(10)
133    ///     .storage_deposit_limit(100)
134    ///     // Submit the call for on-chain execution.
135    ///     .submit()
136    ///     .await
137    ///     .expect("instantiate failed");
138    /// ```
139    fn instantiate<
140        'a,
141        Contract: Clone,
142        Args: Send + Clone + EncodeArgsWith<Abi> + Sync,
143        R,
144        Abi: Send + Sync + Clone,
145    >(
146        &'a mut self,
147        contract_name: &'a str,
148        caller: &'a Keypair,
149        constructor: &'a mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
150    ) -> InstantiateBuilder<'a, E, Contract, Args, R, Self, Abi>
151    where
152        Self: Sized + BuilderClient<E>,
153    {
154        InstantiateBuilder::new(self, caller, contract_name, constructor)
155    }
156
157    /// Start building an upload call.
158    ///
159    /// # Example
160    ///
161    /// ```ignore
162    /// let contract = client
163    ///     .upload("flipper", &ink_e2e::alice())
164    ///     // Optional arguments
165    ///     .storage_deposit_limit(100)
166    ///     // Submit the call for on-chain execution.
167    ///     .submit()
168    ///     .await
169    ///     .expect("upload failed");
170    /// ```
171    fn upload<'a>(
172        &'a mut self,
173        contract_name: &'a str,
174        caller: &'a Keypair,
175    ) -> UploadBuilder<'a, E, Self>
176    where
177        Self: Sized + BuilderClient<E>,
178    {
179        UploadBuilder::new(self, contract_name, caller)
180    }
181
182    /// Start building a remove code call.
183    ///
184    /// # Example
185    ///
186    /// ```ignore
187    /// let contract = client
188    ///     .remove_code(&ink_e2e::alice(), code_hash)
189    ///     // Submit the call for on-chain execution.
190    ///     .submit()
191    ///     .await
192    ///     .expect("remove failed");
193    /// ```
194    fn remove_code<'a>(
195        &'a mut self,
196        caller: &'a Keypair,
197        code_hash: H256,
198    ) -> RemoveCodeBuilder<'a, E, Self>
199    where
200        Self: Sized + BuilderClient<E>,
201    {
202        RemoveCodeBuilder::new(self, caller, code_hash)
203    }
204
205    /// Start building a call using a builder pattern.
206    ///
207    /// # Example
208    ///
209    /// ```ignore
210    /// // Message method
211    /// let get = call_builder.get();
212    /// let get_res = client
213    ///    .call(&ink_e2e::bob(), &get)
214    ///     // Optional arguments
215    ///     // Send 100 units with the call.
216    ///     .value(100)
217    ///     // Add 10% margin to the gas limit
218    ///     .extra_gas_portion(10)
219    ///     .storage_deposit_limit(100)
220    ///     // Submit the call for on-chain execution.
221    ///     .submit()
222    ///     .await
223    ///     .expect("instantiate failed");
224    /// ```
225    fn call<
226        'a,
227        Args: Sync + EncodeArgsWith<Abi> + Clone,
228        RetType: Send + DecodeMessageResult<Abi>,
229        Abi: Sync + Clone,
230    >(
231        &'a mut self,
232        caller: &'a Keypair,
233        message: &'a CallBuilderFinal<E, Args, RetType, Abi>,
234    ) -> CallBuilder<'a, E, Args, RetType, Self, Abi>
235    where
236        Self: Sized + BuilderClient<E>,
237    {
238        CallBuilder::new(self, caller, message)
239    }
240}
241
242#[async_trait]
243pub trait BuilderClient<E: Environment>: ContractsBackend<E> {
244    /// Executes a bare `call` for the contract at `account_id`. This function does
245    /// _not_ perform a dry-run, and the user is expected to provide the gas limit.
246    ///
247    /// Use it when you want to have a more precise control over submitting extrinsic.
248    ///
249    /// Returns when the transaction is included in a block. The return value
250    /// contains all events that are associated with this transaction.
251    async fn bare_call<
252        Args: Sync + EncodeArgsWith<Abi> + Clone,
253        RetType: Send + DecodeMessageResult<Abi>,
254        Abi: Sync + Clone,
255    >(
256        &mut self,
257        caller: &Keypair,
258        message: &CallBuilderFinal<E, Args, RetType, Abi>,
259        value: E::Balance,
260        gas_limit: Weight,
261        storage_deposit_limit: E::Balance,
262    ) -> Result<(Self::EventLog, Option<CallTrace>), Self::Error>
263    where
264        CallBuilderFinal<E, Args, RetType, Abi>: Clone;
265
266    /// Executes a dry-run `call`.
267    ///
268    /// Returns the result of the dry run, together with the decoded return value of the
269    /// invoked message.
270    ///
271    /// Important: For an uncomplicated UX of the E2E testing environment we
272    /// decided to automatically map the account in `pallet-revive`, if not
273    /// yet mapped. This is a side effect, as a transaction is then issued
274    /// on-chain and the user incurs costs!
275    async fn bare_call_dry_run<
276        Args: Sync + EncodeArgsWith<Abi> + Clone,
277        RetType: Send + DecodeMessageResult<Abi>,
278        Abi: Sync + Clone,
279    >(
280        &mut self,
281        caller: &Keypair,
282        message: &CallBuilderFinal<E, Args, RetType, Abi>,
283        value: E::Balance,
284        storage_deposit_limit: Option<E::Balance>,
285    ) -> Result<CallDryRunResult<E, RetType, Abi>, Self::Error>
286    where
287        CallBuilderFinal<E, Args, RetType, Abi>: Clone;
288
289    /// Executes a dry-run `call`.
290    ///
291    /// Returns the result of the dry run, together with the decoded return value of the
292    /// invoked message.
293    ///
294    /// Important: For an uncomplicated UX of the E2E testing environment we
295    /// decided to automatically map the account in `pallet-revive`, if not
296    /// yet mapped. This is a side effect, as a transaction is then issued
297    /// on-chain and the user incurs costs!
298    async fn raw_call_dry_run<
299        RetType: Send + DecodeMessageResult<Abi>,
300        Abi: Sync + Clone,
301    >(
302        &mut self,
303        dest: H160,
304        input_data: Vec<u8>,
305        value: E::Balance,
306        storage_deposit_limit: Option<E::Balance>,
307        signer: &Keypair,
308    ) -> Result<CallDryRunResult<E, RetType, Abi>, Self::Error>;
309
310    /// Executes a dry-run `call`.
311    ///
312    /// Returns the result of the dry run, together with the decoded return value of the
313    /// invoked message.
314    async fn raw_call(
315        &mut self,
316        dest: H160,
317        input_data: Vec<u8>,
318        value: E::Balance,
319        gas_limit: Weight,
320        storage_deposit_limit: E::Balance,
321        signer: &Keypair,
322    ) -> Result<(Self::EventLog, Option<CallTrace>), Self::Error>;
323
324    /// Uploads the contract call.
325    ///
326    /// This function extracts the binary of the contract for the specified contract.
327    ///
328    /// Calling this function multiple times should be idempotent, the contract is
329    /// newly instantiated each time using a unique salt. No existing contract
330    /// instance is reused!
331    async fn bare_upload(
332        &mut self,
333        contract_name: &str,
334        caller: &Keypair,
335        storage_deposit_limit: Option<E::Balance>,
336    ) -> Result<UploadResult<E, Self::EventLog>, Self::Error>;
337
338    /// Removes the code of the contract at `code_hash`.
339    async fn bare_remove_code(
340        &mut self,
341        caller: &Keypair,
342        code_hash: crate::H256,
343    ) -> Result<Self::EventLog, Self::Error>;
344
345    /// Bare instantiate call. This function does not perform a dry-run,
346    /// and user is expected to provide the gas limit.
347    ///
348    /// Use it when you want to have a more precise control over submitting extrinsic.
349    ///
350    /// The function subsequently uploads and instantiates an instance of the contract.
351    ///
352    /// This function extracts the metadata of the contract at the file path
353    /// `target/ink/$contract_name.contract`.
354    ///
355    /// Calling this function multiple times should be idempotent, the contract is
356    /// newly instantiated each time using a unique salt. No existing contract
357    /// instance is reused!
358    async fn bare_instantiate<
359        Contract: Clone,
360        Args: Send + Sync + EncodeArgsWith<Abi> + Clone,
361        R,
362        Abi: Send + Sync + Clone,
363    >(
364        &mut self,
365        code: Vec<u8>,
366        caller: &Keypair,
367        constructor: &mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
368        value: E::Balance,
369        gas_limit: Weight,
370        storage_deposit_limit: E::Balance,
371    ) -> Result<BareInstantiationResult<E, Self::EventLog>, Self::Error>;
372
373    async fn raw_instantiate(
374        &mut self,
375        code: Vec<u8>,
376        caller: &Keypair,
377        constructor: Vec<u8>,
378        value: E::Balance,
379        gas_limit: Weight,
380        storage_deposit_limit: E::Balance,
381    ) -> Result<BareInstantiationResult<E, Self::EventLog>, Self::Error>;
382
383    async fn raw_instantiate_dry_run<Abi: Sync + Clone>(
384        &mut self,
385        code: Vec<u8>,
386        caller: &Keypair,
387        constructor: Vec<u8>,
388        value: E::Balance,
389        storage_deposit_limit: Option<E::Balance>,
390    ) -> Result<InstantiateDryRunResult<E, Abi>, Self::Error>;
391
392    async fn exec_instantiate(
393        &mut self,
394        signer: &Keypair,
395        contract_name: &str,
396        data: Vec<u8>,
397        value: E::Balance,
398        gas_limit: Weight,
399        storage_deposit_limit: E::Balance,
400    ) -> Result<BareInstantiationResult<E, Self::EventLog>, Self::Error>;
401
402    /// Dry run contract instantiation.
403    ///
404    /// Important: For an uncomplicated UX of the E2E testing environment we
405    /// decided to automatically map the account in `pallet-revive`, if not
406    /// yet mapped. This is a side effect, as a transaction is then issued
407    /// on-chain and the user incurs costs!
408    async fn bare_instantiate_dry_run<
409        Contract: Clone,
410        Args: Send + Sync + EncodeArgsWith<Abi> + Clone,
411        R,
412        Abi: Send + Sync + Clone,
413    >(
414        &mut self,
415        contract_name: &str,
416        caller: &Keypair,
417        constructor: &mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
418        value: E::Balance,
419        storage_deposit_limit: Option<E::Balance>,
420    ) -> Result<InstantiateDryRunResult<E, Abi>, Self::Error>;
421
422    /// Checks if `caller` was already mapped in `pallet-revive`. If not, it will do so
423    /// and return the events associated with that transaction.
424    async fn map_account(
425        &mut self,
426        caller: &Keypair,
427    ) -> Result<Option<Self::EventLog>, Self::Error>;
428
429    /// Returns the `Environment::AccountId` for an `H160` address.
430    async fn to_account_id(&mut self, addr: &H160) -> Result<E::AccountId, Self::Error>;
431
432    fn load_code(&self, contract_name: &str) -> Vec<u8>;
433}