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}