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 super::{
16    balance_to_deposit_limit,
17    InstantiateDryRunResult,
18    Keypair,
19};
20use crate::{
21    backend::BuilderClient,
22    builders::CreateBuilderPartial,
23    CallBuilderFinal,
24    CallDryRunResult,
25    CallResult,
26    ContractsBackend,
27    InstantiationResult,
28    UploadResult,
29    H256,
30};
31use ink_env::Environment;
32use ink_primitives::{
33    reflect::{
34        AbiDecodeWith,
35        AbiEncodeWith,
36        ScaleEncoding,
37    },
38    DepositLimit,
39};
40use sp_weights::Weight;
41use std::marker::PhantomData;
42
43/// Allows to build an end-to-end call using a builder pattern.
44pub struct CallBuilder<'a, E, Args, RetType, B, Abi>
45where
46    E: Environment,
47    Args: AbiEncodeWith<Abi> + Clone,
48    RetType: Send + AbiDecodeWith<Abi>,
49
50    B: BuilderClient<E>,
51    Abi: Clone,
52{
53    client: &'a mut B,
54    caller: &'a Keypair,
55    message: &'a CallBuilderFinal<E, Args, RetType, Abi>,
56    value: E::Balance,
57    extra_gas_portion: Option<u64>,
58    gas_limit: Option<Weight>,
59    storage_deposit_limit: E::Balance,
60}
61
62impl<'a, E, Args, RetType, B, Abi> CallBuilder<'a, E, Args, RetType, B, Abi>
63where
64    E: Environment,
65    Args: Sync + AbiEncodeWith<Abi> + Clone,
66    RetType: Send + AbiDecodeWith<Abi>,
67
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: 0u32.into(),
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 = 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>, 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::<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>, 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::<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>
204where
205    E: Environment,
206    Args: AbiEncodeWith<ScaleEncoding> + Clone,
207    Contract: Clone,
208
209    B: ContractsBackend<E>,
210{
211    client: &'a mut B,
212    caller: &'a Keypair,
213    contract_name: &'a str,
214    constructor: &'a mut CreateBuilderPartial<E, Contract, Args, R>,
215    value: E::Balance,
216    extra_gas_portion: Option<u64>,
217    gas_limit: Option<Weight>,
218    storage_deposit_limit: DepositLimit<E::Balance>,
219}
220
221impl<'a, E, Contract, Args, R, B> InstantiateBuilder<'a, E, Contract, Args, R, B>
222where
223    E: Environment,
224    Args: AbiEncodeWith<ScaleEncoding> + Clone + Send + Sync,
225    Contract: Clone,
226    B: BuilderClient<E>,
227{
228    /// Initialize a call builder with essential values.
229    pub fn new(
230        client: &'a mut B,
231        caller: &'a Keypair,
232        contract_name: &'a str,
233        constructor: &'a mut CreateBuilderPartial<E, Contract, Args, R>,
234    ) -> InstantiateBuilder<'a, E, Contract, Args, R, B>
235    where
236        E::Balance: From<u32>,
237    {
238        Self {
239            client,
240            caller,
241            contract_name,
242            constructor,
243            value: 0u32.into(),
244            extra_gas_portion: None,
245            gas_limit: None,
246            storage_deposit_limit: DepositLimit::Unchecked,
247        }
248    }
249
250    /// Provide value with a call
251    pub fn value(&mut self, value: E::Balance) -> &mut Self {
252        self.value = value;
253        self
254    }
255
256    /// Increases the gas limit marginally by a specified percent.
257    /// Useful when the message's gas usage depends on the runtime state
258    /// and the dry run does not produce an accurate gas estimate.
259    ///
260    /// # Example
261    ///
262    /// With dry run gas estimate of `100` units and `5`% extra gas portion specified,
263    /// the set gas limit becomes `105` units
264    pub fn extra_gas_portion(&mut self, per_cent: u64) -> &mut Self {
265        if per_cent == 0 {
266            self.extra_gas_portion = None
267        } else {
268            self.extra_gas_portion = Some(per_cent)
269        }
270        self
271    }
272
273    /// Specifies the raw gas limit as part of the call.
274    ///
275    /// # Notes
276    ///
277    /// Overwrites any values specified for `extra_gas_portion`.
278    /// The gas estimate fro dry-run will be ignored.
279    pub fn gas_limit(&mut self, limit: Weight) -> &mut Self {
280        if limit == Weight::from_parts(0, 0) {
281            self.gas_limit = None
282        } else {
283            self.gas_limit = Some(limit)
284        }
285        self
286    }
287
288    /// Specify the max amount of funds that can be charged for storage.
289    pub fn storage_deposit_limit(
290        &mut self,
291        storage_deposit_limit: DepositLimit<E::Balance>,
292    ) -> &mut Self {
293        self.storage_deposit_limit = storage_deposit_limit;
294        self
295    }
296
297    /// Submit the instantiate call for the on-chain execution.
298    ///
299    /// This will automatically run a dry-run call, and use `extra_gas_portion`
300    /// to add a margin to the gas limit.
301    pub async fn submit(
302        &mut self,
303    ) -> Result<InstantiationResult<E, B::EventLog>, B::Error> {
304        // we have to make sure the account was mapped
305        let _map = B::map_account(self.client, self.caller).await; // todo will fail if instantiation happened before
306
307        let dry_run = B::bare_instantiate_dry_run(
308            self.client,
309            self.contract_name,
310            self.caller,
311            self.constructor,
312            self.value,
313            self.storage_deposit_limit.clone(),
314        )
315        .await?;
316
317        let gas_limit = if let Some(limit) = self.gas_limit {
318            limit
319        } else {
320            let gas_required = dry_run.contract_result.gas_required;
321            let proof_size = gas_required.proof_size();
322            let ref_time = gas_required.ref_time();
323            calculate_weight(proof_size, ref_time, self.extra_gas_portion)
324        };
325
326        let instantiate_result = B::bare_instantiate(
327            self.client,
328            self.contract_name,
329            self.caller,
330            self.constructor,
331            self.value,
332            gas_limit,
333            balance_to_deposit_limit::<E>(
334                dry_run.contract_result.storage_deposit.charge_or_zero(),
335            ),
336        )
337        .await?;
338
339        Ok(InstantiationResult {
340            addr: instantiate_result.addr,
341            dry_run,
342            events: instantiate_result.events,
343            trace: instantiate_result.trace,
344            code_hash: instantiate_result.code_hash,
345        })
346    }
347
348    /// Dry run the instantiate call.
349    pub async fn dry_run(&mut self) -> Result<InstantiateDryRunResult<E>, B::Error> {
350        B::bare_instantiate_dry_run(
351            self.client,
352            self.contract_name,
353            self.caller,
354            self.constructor,
355            self.value,
356            self.storage_deposit_limit.clone(),
357        )
358        .await
359    }
360}
361
362/// Allows to build an end-to-end upload call using a builder pattern.
363pub struct UploadBuilder<'a, E, B>
364where
365    E: Environment,
366    B: BuilderClient<E>,
367{
368    client: &'a mut B,
369    contract_name: &'a str,
370    caller: &'a Keypair,
371    storage_deposit_limit: E::Balance,
372}
373
374impl<'a, E, B> UploadBuilder<'a, E, B>
375where
376    E: Environment,
377    B: BuilderClient<E>,
378{
379    /// Initialize an upload builder with essential values.
380    pub fn new(client: &'a mut B, contract_name: &'a str, caller: &'a Keypair) -> Self {
381        Self {
382            client,
383            contract_name,
384            caller,
385            storage_deposit_limit: 0u32.into(),
386        }
387    }
388
389    /// Specify the max amount of funds that can be charged for storage.
390    pub fn storage_deposit_limit(
391        &mut self,
392        storage_deposit_limit: E::Balance,
393    ) -> &mut Self {
394        self.storage_deposit_limit = storage_deposit_limit;
395        self
396    }
397
398    /// Execute the upload.
399    pub async fn submit(&mut self) -> Result<UploadResult<E, B::EventLog>, B::Error> {
400        B::bare_upload(
401            self.client,
402            self.contract_name,
403            self.caller,
404            self.storage_deposit_limit,
405        )
406        .await
407    }
408}
409
410/// Allows to build an end-to-end remove code call using a builder pattern.
411pub struct RemoveCodeBuilder<'a, E, B>
412where
413    E: Environment,
414    B: BuilderClient<E>,
415{
416    client: &'a mut B,
417    caller: &'a Keypair,
418    code_hash: crate::H256,
419    _phantom: PhantomData<fn() -> E>,
420}
421
422impl<'a, E, B> RemoveCodeBuilder<'a, E, B>
423where
424    E: Environment,
425    B: BuilderClient<E>,
426{
427    /// Initialize a remove code builder with essential values.
428    pub fn new(client: &'a mut B, caller: &'a Keypair, code_hash: H256) -> Self {
429        Self {
430            client,
431            caller,
432            code_hash,
433            _phantom: Default::default(),
434        }
435    }
436
437    /// Submit the remove code extrinsic.
438    pub async fn submit(&mut self) -> Result<B::EventLog, B::Error> {
439        B::bare_remove_code(self.client, self.caller, self.code_hash).await
440    }
441}
442
443fn calculate_weight(
444    mut proof_size: u64,
445    mut ref_time: u64,
446    portion: Option<u64>,
447) -> Weight {
448    if let Some(m) = portion {
449        ref_time += ref_time / 100 * m;
450        proof_size += proof_size / 100 * m;
451    }
452    Weight::from_parts(ref_time, proof_size)
453}