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}