ink_e2e/
backend_calls.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 std::marker::PhantomData;
16
17use ink_env::{
18    Environment,
19    call::utils::DecodeMessageResult,
20};
21use ink_primitives::{
22    DepositLimit,
23    abi::AbiEncodeWith,
24};
25use sp_weights::Weight;
26
27use super::{
28    InstantiateDryRunResult,
29    Keypair,
30    balance_to_deposit_limit,
31    balance_to_deposit_limit_dry_run,
32};
33use crate::{
34    CallBuilderFinal,
35    CallDryRunResult,
36    CallResult,
37    ContractsBackend,
38    H256,
39    InstantiationResult,
40    UploadResult,
41    backend::BuilderClient,
42    builders::CreateBuilderPartial,
43};
44
45/// Allows to build an end-to-end call using a builder pattern.
46pub struct CallBuilder<'a, E, Args, RetType, B, Abi>
47where
48    E: Environment,
49    Args: AbiEncodeWith<Abi> + Clone,
50    RetType: Send + DecodeMessageResult<Abi>,
51    B: BuilderClient<E>,
52    Abi: Clone,
53{
54    client: &'a mut B,
55    caller: &'a Keypair,
56    message: &'a CallBuilderFinal<E, Args, RetType, Abi>,
57    value: E::Balance,
58    extra_gas_portion: Option<u64>,
59    gas_limit: Option<Weight>,
60    storage_deposit_limit: Option<E::Balance>,
61}
62
63impl<'a, E, Args, RetType, B, Abi> CallBuilder<'a, E, Args, RetType, B, Abi>
64where
65    E: Environment,
66    Args: Sync + AbiEncodeWith<Abi> + Clone,
67    RetType: Send + DecodeMessageResult<Abi>,
68    B: BuilderClient<E>,
69    Abi: Sync + Clone,
70{
71    /// Initialize a call builder with defaults values.
72    pub fn new(
73        client: &'a mut B,
74        caller: &'a Keypair,
75        message: &'a CallBuilderFinal<E, Args, RetType, Abi>,
76    ) -> CallBuilder<'a, E, Args, RetType, B, Abi>
77    where
78        E::Balance: From<u32>,
79    {
80        Self {
81            client,
82            caller,
83            message,
84            value: 0u32.into(),
85            extra_gas_portion: None,
86            gas_limit: None,
87            storage_deposit_limit: None,
88        }
89    }
90
91    /// Provide value with a call
92    pub fn value(&mut self, value: E::Balance) -> &mut Self {
93        self.value = value;
94        self
95    }
96
97    /// Increases the gas limit marginally by a specified percent.
98    /// Useful when the message's gas usage depends on the runtime state
99    /// and the dry run does not produce an accurate gas estimate.
100    ///
101    /// # Example
102    ///
103    /// With dry run gas estimate of `100` units and `5`% extra gas portion specified,
104    /// the set gas limit becomes `105` units
105    pub fn extra_gas_portion(&mut self, per_cent: u64) -> &mut Self {
106        if per_cent == 0 {
107            self.extra_gas_portion = None
108        } else {
109            self.extra_gas_portion = Some(per_cent)
110        }
111        self
112    }
113
114    /// Specifies the raw gas limit as part of the call.
115    ///
116    /// # Notes
117    ///
118    /// Overwrites any values specified for `extra_gas_portion`.
119    /// The gas estimate from the dry-run will be ignored.
120    pub fn gas_limit(&mut self, limit: Weight) -> &mut Self {
121        if limit == Weight::from_parts(0, 0) {
122            self.gas_limit = None
123        } else {
124            self.gas_limit = Some(limit)
125        }
126        self
127    }
128
129    /// Specify the max amount of funds that can be charged for storage.
130    pub fn storage_deposit_limit(
131        &mut self,
132        storage_deposit_limit: E::Balance,
133    ) -> &mut Self {
134        self.storage_deposit_limit = Some(storage_deposit_limit);
135        self
136    }
137
138    /// Submit the call for the on-chain execution.
139    ///
140    /// This will automatically run a dry-run call, and use `extra_gas_portion`
141    /// to add a margin to the gas limit.
142    pub async fn submit(
143        &mut self,
144    ) -> Result<CallResult<E, RetType, B::EventLog, Abi>, B::Error>
145    where
146        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
147    {
148        let _map = B::map_account(self.client, self.caller).await; // todo will fail if instantiation happened before
149
150        let dry_run = B::bare_call_dry_run(
151            self.client,
152            self.caller,
153            self.message,
154            self.value,
155            balance_to_deposit_limit_dry_run::<E>(self.storage_deposit_limit),
156        )
157        .await?;
158
159        let gas_limit = if let Some(limit) = self.gas_limit {
160            limit
161        } else {
162            let gas_required = dry_run.exec_result.gas_required;
163            let proof_size = gas_required.proof_size();
164            let ref_time = gas_required.ref_time();
165            calculate_weight(proof_size, ref_time, self.extra_gas_portion)
166        };
167
168        let (events, trace) = B::bare_call(
169            self.client,
170            self.caller,
171            self.message,
172            self.value,
173            gas_limit,
174            // todo: the `bare_call` converts this value back, this is unnecessary work
175            DepositLimit::Balance(dry_run.exec_result.storage_deposit.charge_or_zero()),
176        )
177        .await?;
178
179        Ok(CallResult {
180            dry_run,
181            events,
182            trace,
183        })
184    }
185
186    /// Dry run the call.
187    pub async fn dry_run(&mut self) -> Result<CallDryRunResult<E, RetType, Abi>, B::Error>
188    where
189        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
190    {
191        B::bare_call_dry_run(
192            self.client,
193            self.caller,
194            self.message,
195            self.value,
196            balance_to_deposit_limit_dry_run::<E>(self.storage_deposit_limit),
197        )
198        .await
199    }
200}
201
202/// Allows to build an end-to-end instantiation call using a builder pattern.
203pub struct InstantiateBuilder<'a, E, Contract, Args, R, B, Abi>
204where
205    E: Environment,
206    Args: AbiEncodeWith<Abi> + Clone,
207    Contract: Clone,
208    B: ContractsBackend<E>,
209{
210    client: &'a mut B,
211    caller: &'a Keypair,
212    contract_name: &'a str,
213    constructor: &'a mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
214    value: E::Balance,
215    extra_gas_portion: Option<u64>,
216    gas_limit: Option<Weight>,
217    storage_deposit_limit: DepositLimit<E::Balance>,
218}
219
220impl<'a, E, Contract, Args, R, B, Abi>
221    InstantiateBuilder<'a, E, Contract, Args, R, B, Abi>
222where
223    E: Environment,
224    Args: AbiEncodeWith<Abi> + Clone + Send + Sync,
225    Contract: Clone,
226    B: BuilderClient<E>,
227    Abi: Send + Sync + Clone,
228{
229    /// Initialize a call builder with essential values.
230    pub fn new(
231        client: &'a mut B,
232        caller: &'a Keypair,
233        contract_name: &'a str,
234        constructor: &'a mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
235    ) -> InstantiateBuilder<'a, E, Contract, Args, R, B, Abi>
236    where
237        E::Balance: From<u32>,
238    {
239        Self {
240            client,
241            caller,
242            contract_name,
243            constructor,
244            value: 0u32.into(),
245            extra_gas_portion: None,
246            gas_limit: None,
247            storage_deposit_limit: DepositLimit::UnsafeOnlyForDryRun,
248        }
249    }
250
251    /// Provide value with a call
252    pub fn value(&mut self, value: E::Balance) -> &mut Self {
253        self.value = value;
254        self
255    }
256
257    /// Increases the gas limit marginally by a specified percent.
258    /// Useful when the message's gas usage depends on the runtime state
259    /// and the dry run does not produce an accurate gas estimate.
260    ///
261    /// # Example
262    ///
263    /// With dry run gas estimate of `100` units and `5`% extra gas portion specified,
264    /// the set gas limit becomes `105` units
265    pub fn extra_gas_portion(&mut self, per_cent: u64) -> &mut Self {
266        if per_cent == 0 {
267            self.extra_gas_portion = None
268        } else {
269            self.extra_gas_portion = Some(per_cent)
270        }
271        self
272    }
273
274    /// Specifies the raw gas limit as part of the call.
275    ///
276    /// # Notes
277    ///
278    /// Overwrites any values specified for `extra_gas_portion`.
279    /// The gas estimate from dry-run will be ignored.
280    pub fn gas_limit(&mut self, limit: Weight) -> &mut Self {
281        if limit == Weight::from_parts(0, 0) {
282            self.gas_limit = None
283        } else {
284            self.gas_limit = Some(limit)
285        }
286        self
287    }
288
289    /// Specify the max amount of funds that can be charged for storage.
290    pub fn storage_deposit_limit(
291        &mut self,
292        storage_deposit_limit: DepositLimit<E::Balance>,
293    ) -> &mut Self {
294        self.storage_deposit_limit = storage_deposit_limit;
295        self
296    }
297
298    /// Submit the instantiate call for the on-chain execution.
299    ///
300    /// This will automatically run a dry-run call, and use `extra_gas_portion`
301    /// to add a margin to the gas limit.
302    pub async fn submit(
303        &mut self,
304    ) -> Result<InstantiationResult<E, B::EventLog, Abi>, B::Error> {
305        // we have to make sure the account was mapped
306        let _map = B::map_account(self.client, self.caller).await; // todo will fail if instantiation happened before
307
308        let dry_run = B::bare_instantiate_dry_run(
309            self.client,
310            self.contract_name,
311            self.caller,
312            self.constructor,
313            self.value,
314            self.storage_deposit_limit.clone(),
315        )
316        .await?;
317
318        let gas_limit = if let Some(limit) = self.gas_limit {
319            limit
320        } else {
321            let gas_required = dry_run.contract_result.gas_required;
322            let proof_size = gas_required.proof_size();
323            let ref_time = gas_required.ref_time();
324            calculate_weight(proof_size, ref_time, self.extra_gas_portion)
325        };
326
327        let instantiate_result = B::bare_instantiate(
328            self.client,
329            self.contract_name,
330            self.caller,
331            self.constructor,
332            self.value,
333            gas_limit,
334            balance_to_deposit_limit::<E>(Some(
335                dry_run.contract_result.storage_deposit.charge_or_zero(),
336            )),
337        )
338        .await?;
339
340        Ok(InstantiationResult {
341            addr: instantiate_result.addr,
342            dry_run,
343            events: instantiate_result.events,
344            trace: instantiate_result.trace,
345            code_hash: instantiate_result.code_hash,
346        })
347    }
348
349    /// Dry run the instantiate call.
350    pub async fn dry_run(&mut self) -> Result<InstantiateDryRunResult<E, Abi>, B::Error> {
351        B::bare_instantiate_dry_run(
352            self.client,
353            self.contract_name,
354            self.caller,
355            self.constructor,
356            self.value,
357            self.storage_deposit_limit.clone(),
358        )
359        .await
360    }
361}
362
363/// Allows to build an end-to-end upload call using a builder pattern.
364pub struct UploadBuilder<'a, E, B>
365where
366    E: Environment,
367    B: BuilderClient<E>,
368{
369    client: &'a mut B,
370    contract_name: &'a str,
371    caller: &'a Keypair,
372    storage_deposit_limit: E::Balance,
373}
374
375impl<'a, E, B> UploadBuilder<'a, E, B>
376where
377    E: Environment,
378    B: BuilderClient<E>,
379{
380    /// Initialize an upload builder with essential values.
381    pub fn new(client: &'a mut B, contract_name: &'a str, caller: &'a Keypair) -> Self {
382        Self {
383            client,
384            contract_name,
385            caller,
386            storage_deposit_limit: 0u32.into(),
387        }
388    }
389
390    /// Specify the max amount of funds that can be charged for storage.
391    pub fn storage_deposit_limit(
392        &mut self,
393        storage_deposit_limit: E::Balance,
394    ) -> &mut Self {
395        self.storage_deposit_limit = storage_deposit_limit;
396        self
397    }
398
399    /// Execute the upload.
400    pub async fn submit(&mut self) -> Result<UploadResult<E, B::EventLog>, B::Error> {
401        B::bare_upload(
402            self.client,
403            self.contract_name,
404            self.caller,
405            self.storage_deposit_limit,
406        )
407        .await
408    }
409}
410
411/// Allows to build an end-to-end remove code call using a builder pattern.
412pub struct RemoveCodeBuilder<'a, E, B>
413where
414    E: Environment,
415    B: BuilderClient<E>,
416{
417    client: &'a mut B,
418    caller: &'a Keypair,
419    code_hash: crate::H256,
420    _phantom: PhantomData<fn() -> E>,
421}
422
423impl<'a, E, B> RemoveCodeBuilder<'a, E, B>
424where
425    E: Environment,
426    B: BuilderClient<E>,
427{
428    /// Initialize a remove code builder with essential values.
429    pub fn new(client: &'a mut B, caller: &'a Keypair, code_hash: H256) -> Self {
430        Self {
431            client,
432            caller,
433            code_hash,
434            _phantom: Default::default(),
435        }
436    }
437
438    /// Submit the remove code extrinsic.
439    pub async fn submit(&mut self) -> Result<B::EventLog, B::Error> {
440        B::bare_remove_code(self.client, self.caller, self.code_hash).await
441    }
442}
443
444fn calculate_weight(
445    mut proof_size: u64,
446    mut ref_time: u64,
447    portion: Option<u64>,
448) -> Weight {
449    if let Some(m) = portion {
450        ref_time += ref_time / 100 * m;
451        proof_size += proof_size / 100 * m;
452    }
453    Weight::from_parts(ref_time, proof_size)
454}