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    call::utils::DecodeMessageResult,
19    Environment,
20};
21use ink_primitives::{
22    abi::AbiEncodeWith,
23    DepositLimit,
24};
25use sp_weights::Weight;
26
27use super::{
28    balance_to_deposit_limit,
29    InstantiateDryRunResult,
30    Keypair,
31};
32use crate::{
33    backend::BuilderClient,
34    builders::CreateBuilderPartial,
35    CallBuilderFinal,
36    CallDryRunResult,
37    CallResult,
38    ContractsBackend,
39    InstantiationResult,
40    UploadResult,
41    H256,
42};
43
44/// Allows to build an end-to-end call using a builder pattern.
45pub struct CallBuilder<'a, E, Args, RetType, B, Abi>
46where
47    E: Environment,
48    Args: AbiEncodeWith<Abi> + Clone,
49    RetType: Send + DecodeMessageResult<Abi>,
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 + DecodeMessageResult<Abi>,
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, Abi>, 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, Abi>, 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    B: ContractsBackend<E>,
208{
209    client: &'a mut B,
210    caller: &'a Keypair,
211    contract_name: &'a str,
212    constructor: &'a mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
213    value: E::Balance,
214    extra_gas_portion: Option<u64>,
215    gas_limit: Option<Weight>,
216    storage_deposit_limit: DepositLimit<E::Balance>,
217}
218
219impl<'a, E, Contract, Args, R, B, Abi>
220    InstantiateBuilder<'a, E, Contract, Args, R, B, Abi>
221where
222    E: Environment,
223    Args: AbiEncodeWith<Abi> + Clone + Send + Sync,
224    Contract: Clone,
225    B: BuilderClient<E>,
226    Abi: Send + Sync + Clone,
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, Abi>,
234    ) -> InstantiateBuilder<'a, E, Contract, Args, R, B, Abi>
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::UnsafeOnlyForDryRun,
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 from 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, Abi>, 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, Abi>, 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}