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