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    },
37    DepositLimit,
38};
39use sp_weights::Weight;
40use std::marker::PhantomData;
41
42/// Allows to build an end-to-end call using a builder pattern.
43pub struct CallBuilder<'a, E, Args, RetType, B, Abi>
44where
45    E: Environment,
46    Args: AbiEncodeWith<Abi> + Clone,
47    RetType: Send + AbiDecodeWith<Abi>,
48
49    B: BuilderClient<E>,
50    Abi: Clone,
51{
52    client: &'a mut B,
53    caller: &'a Keypair,
54    message: &'a CallBuilderFinal<E, Args, RetType, Abi>,
55    value: E::Balance,
56    extra_gas_portion: Option<u64>,
57    gas_limit: Option<Weight>,
58    storage_deposit_limit: E::Balance,
59}
60
61impl<'a, E, Args, RetType, B, Abi> CallBuilder<'a, E, Args, RetType, B, Abi>
62where
63    E: Environment,
64    Args: Sync + AbiEncodeWith<Abi> + Clone,
65    RetType: Send + AbiDecodeWith<Abi>,
66
67    B: BuilderClient<E>,
68    Abi: Sync + Clone,
69{
70    /// Initialize a call builder with defaults values.
71    pub fn new(
72        client: &'a mut B,
73        caller: &'a Keypair,
74        message: &'a CallBuilderFinal<E, Args, RetType, Abi>,
75    ) -> CallBuilder<'a, E, Args, RetType, B, Abi>
76    where
77        E::Balance: From<u32>,
78    {
79        Self {
80            client,
81            caller,
82            message,
83            value: 0u32.into(),
84            extra_gas_portion: None,
85            gas_limit: None,
86            storage_deposit_limit: 0u32.into(),
87        }
88    }
89
90    /// Provide value with a call
91    pub fn value(&mut self, value: E::Balance) -> &mut Self {
92        self.value = value;
93        self
94    }
95
96    /// Increases the gas limit marginally by a specified percent.
97    /// Useful when the message's gas usage depends on the runtime state
98    /// and the dry run does not produce an accurate gas estimate.
99    ///
100    /// # Example
101    ///
102    /// With dry run gas estimate of `100` units and `5`% extra gas portion specified,
103    /// the set gas limit becomes `105` units
104    pub fn extra_gas_portion(&mut self, per_cent: u64) -> &mut Self {
105        if per_cent == 0 {
106            self.extra_gas_portion = None
107        } else {
108            self.extra_gas_portion = Some(per_cent)
109        }
110        self
111    }
112
113    /// Specifies the raw gas limit as part of the call.
114    ///
115    /// # Notes
116    ///
117    /// Overwrites any values specified for `extra_gas_portion`.
118    /// The gas estimate from the dry-run will be ignored.
119    pub fn gas_limit(&mut self, limit: Weight) -> &mut Self {
120        if limit == Weight::from_parts(0, 0) {
121            self.gas_limit = None
122        } else {
123            self.gas_limit = Some(limit)
124        }
125        self
126    }
127
128    /// Specify the max amount of funds that can be charged for storage.
129    pub fn storage_deposit_limit(
130        &mut self,
131        storage_deposit_limit: E::Balance,
132    ) -> &mut Self {
133        self.storage_deposit_limit = storage_deposit_limit;
134        self
135    }
136
137    /// Submit the call for the on-chain execution.
138    ///
139    /// This will automatically run a dry-run call, and use `extra_gas_portion`
140    /// to add a margin to the gas limit.
141    pub async fn submit(
142        &mut self,
143    ) -> Result<CallResult<E, RetType, B::EventLog>, B::Error>
144    where
145        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
146    {
147        let _map = B::map_account(self.client, self.caller).await; // todo will fail if instantiation happened before
148
149        let dry_run = B::bare_call_dry_run(
150            self.client,
151            self.caller,
152            self.message,
153            self.value,
154            balance_to_deposit_limit::<E>(self.storage_deposit_limit),
155        )
156        .await?;
157
158        let gas_limit = if let Some(limit) = self.gas_limit {
159            limit
160        } else {
161            let gas_required = dry_run.exec_result.gas_required;
162            let proof_size = gas_required.proof_size();
163            let ref_time = gas_required.ref_time();
164            calculate_weight(proof_size, ref_time, self.extra_gas_portion)
165        };
166
167        let (events, trace) = B::bare_call(
168            self.client,
169            self.caller,
170            self.message,
171            self.value,
172            gas_limit,
173            // todo: the `bare_call` converts this value back, this is unnecessary work
174            DepositLimit::Balance(dry_run.exec_result.storage_deposit.charge_or_zero()),
175        )
176        .await?;
177
178        Ok(CallResult {
179            dry_run,
180            events,
181            trace,
182        })
183    }
184
185    /// Dry run the call.
186    pub async fn dry_run(&mut self) -> Result<CallDryRunResult<E, RetType>, B::Error>
187    where
188        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
189    {
190        B::bare_call_dry_run(
191            self.client,
192            self.caller,
193            self.message,
194            self.value,
195            balance_to_deposit_limit::<E>(self.storage_deposit_limit),
196        )
197        .await
198    }
199}
200
201/// Allows to build an end-to-end instantiation call using a builder pattern.
202pub struct InstantiateBuilder<'a, E, Contract, Args, R, B, Abi>
203where
204    E: Environment,
205    Args: AbiEncodeWith<Abi> + Clone,
206    Contract: Clone,
207
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::Unchecked,
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 fro 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>, 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>(
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        })
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}