ink_env/call/call_builder/
mod.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
15mod call;
16mod delegate;
17
18pub use call::Call;
19pub use delegate::DelegateCall;
20
21use core::marker::PhantomData;
22
23use ink_primitives::{
24    Address,
25    abi::{
26        Ink,
27        Sol,
28    },
29};
30
31use crate::{
32    call::{
33        Execution,
34        ExecutionInput,
35        utils::{
36            EmptyArgumentList,
37            ReturnType,
38            Set,
39            Unset,
40        },
41    },
42    types::Environment,
43};
44
45/// The final parameters to the cross-contract call.
46#[derive(Debug)]
47pub struct CallParams<E, CallType, Args, R, Abi>
48where
49    E: Environment,
50{
51    /// A marker to indicate which type of call to perform.
52    call_type: CallType,
53    /// The expected return type.
54    _return_type: ReturnType<R>,
55    /// The inputs to the execution which is a selector and encoded arguments.
56    exec_input: ExecutionInput<Args, Abi>,
57    /// `Environment` is used by `CallType` for correct types
58    _phantom: PhantomData<fn() -> E>,
59}
60
61impl<E, CallType, Args, R, Abi> CallParams<E, CallType, Args, R, Abi>
62where
63    E: Environment,
64{
65    /// Returns the execution input.
66    #[inline]
67    pub fn exec_input(&self) -> &ExecutionInput<Args, Abi> {
68        &self.exec_input
69    }
70}
71
72/// Returns a new [`CallBuilder`] to build up the parameters to a cross-contract call
73/// that uses the "default" ABI for calls for the ink! project.
74///
75/// # Note
76///
77/// The "default" ABI for calls is "ink", unless the ABI is set to "sol"
78/// in the ink! project's manifest file (i.e. `Cargo.toml`).
79///
80/// # Example
81///
82/// **Note:** The shown examples panic because there is currently no cross-calling
83///           support in the off-chain testing environment. However, this code
84///           should work fine in on-chain environments.
85///
86/// ## Example 1: No Return Value
87///
88/// The below example shows calling of a message of another contract that does
89/// not return any value back to its caller. The called function:
90///
91/// - has a selector equal to `0xDEADBEEF`
92/// - is provided with 5000 units of gas for its execution
93/// - is provided with 10 units of transferred value for the contract instance
94/// - receives the following arguments in order 1. an `i32` with value `42` 2. a `bool`
95///   with value `true` 3. an array of 32 `u8` with value `0x10`
96///
97/// ```should_panic
98/// # use ::ink_env::{
99/// #     Environment,
100/// #     DefaultEnvironment,
101/// #     call::{build_call, Selector, ExecutionInput}
102/// # };
103/// # use ink_env::call::Call;
104/// # use ink_primitives::Address;
105///
106/// type AccountId = <DefaultEnvironment as Environment>::AccountId;
107/// # type Balance = <DefaultEnvironment as Environment>::Balance;
108/// build_call::<DefaultEnvironment>()
109///     .call(Address::from([0x42; 20]))
110///     .ref_time_limit(5000)
111///     .transferred_value(ink::U256::from(10))
112///     .exec_input(
113///         ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF]))
114///             .push_arg(42u8)
115///             .push_arg(true)
116///             .push_arg(&[0x10u8; 32]),
117///     )
118///     .returns::<()>()
119///     .invoke();
120/// ```
121///
122/// ## Example 2: With Return Value
123///
124/// The below example shows calling of a message of another contract that does
125/// return a `i32` value back to its caller. The called function:
126///
127/// - has a selector equal to `0xDEADBEEF`
128/// - is provided with 5000 units of gas for its execution
129/// - is provided with 10 units of transferred value for the contract instance
130/// - receives the following arguments in order 1. an `i32` with value `42` 2. a `bool`
131///   with value `true` 3. an array of 32 `u8` with value `0x10`
132///
133/// ```should_panic
134/// # use ::ink_env::{
135/// #     Environment,
136/// #     DefaultEnvironment,
137/// #     call::{build_call, Selector, ExecutionInput, Call},
138/// # };
139/// # type AccountId = <DefaultEnvironment as Environment>::AccountId;
140/// let my_return_value: i32 = build_call::<DefaultEnvironment>()
141///     .call_type(Call::new(ink::Address::from([0x42; 20])))
142///     .ref_time_limit(5000)
143///     .transferred_value(ink::U256::from(10))
144///     .exec_input(
145///         ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF]))
146///             .push_arg(42u8)
147///             .push_arg(true)
148///             .push_arg(&[0x10u8; 32]),
149///     )
150///     .returns::<i32>()
151///     .invoke();
152/// ```
153///
154/// ## Example 3: Delegate call
155///
156/// **Note:** The shown example panics because there is currently no delegate calling
157///           support in the off-chain testing environment. However, this code
158///           should work fine in on-chain environments.
159///
160/// ```should_panic
161/// # use ::ink_env::{
162/// #     Environment,
163/// #     DefaultEnvironment,
164/// #     call::{build_call, Selector, ExecutionInput, utils::ReturnType, DelegateCall},
165/// # };
166/// use ink::Address;
167/// # use ink_primitives::Clear;
168/// # type AccountId = <DefaultEnvironment as Environment>::AccountId;
169/// let my_return_value: i32 = build_call::<DefaultEnvironment>()
170///     .delegate(Address::zero())
171///     .exec_input(
172///         ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF]))
173///             .push_arg(42u8)
174///             .push_arg(true)
175///             .push_arg(&[0x10u8; 32])
176///     )
177///     .returns::<i32>()
178///     .invoke();
179/// ```
180///
181/// # Handling `LangError`s
182///
183/// It is also important to note that there are certain types of errors which can happen
184/// during cross-contract calls which can be handled know as
185/// [`LangError`][`ink_primitives::LangError`].
186///
187/// If you want to handle these errors use the [`CallBuilder::try_invoke`] methods instead
188/// of the [`CallBuilder::invoke`] ones.
189///
190/// **Note:** The shown examples panic because there is currently no cross-calling
191///           support in the off-chain testing environment. However, this code
192///           should work fine in on-chain environments.
193///
194/// ## Example: Handling a `LangError`
195///
196/// ```should_panic
197/// # use ::ink_env::{
198/// #     Environment,
199/// #     DefaultEnvironment,
200/// #     call::{build_call, Selector, ExecutionInput}
201/// # };
202/// # use ink_env::call::Call;
203/// # use ink_primitives::Address;
204///
205/// type AccountId = <DefaultEnvironment as Environment>::AccountId;
206/// # type Balance = <DefaultEnvironment as Environment>::Balance;
207/// let call_result = build_call::<DefaultEnvironment>()
208///     .call(Address::from([0x42; 20]))
209///     .ref_time_limit(5000)
210///     .transferred_value(ink::U256::from(10))
211///     .try_invoke()
212///     .expect("Got an error from the Contract's pallet.");
213///
214/// match call_result {
215///     Ok(_) => unimplemented!(),
216///     Err(e @ ink_primitives::LangError::CouldNotReadInput) => unimplemented!(),
217///     Err(_) => unimplemented!(),
218/// }
219/// ```
220#[allow(clippy::type_complexity)]
221pub fn build_call<E>() -> CallBuilder<
222    E,
223    Unset<Call>,
224    Unset<ExecutionInput<EmptyArgumentList<crate::DefaultAbi>, crate::DefaultAbi>>,
225    Unset<ReturnType<()>>,
226>
227where
228    E: Environment,
229{
230    CallBuilder {
231        call_type: Default::default(),
232        exec_input: Default::default(),
233        return_type: Default::default(),
234        _phantom: Default::default(),
235    }
236}
237
238/// Returns a new [`CallBuilder`] to build up the parameters to a cross-contract call
239/// that uses ink! ABI Encoding (i.e. with SCALE codec for input/output encode/decode).
240///
241/// See [`build_call`] for more details on usage.
242#[allow(clippy::type_complexity)]
243pub fn build_call_ink<E>() -> CallBuilder<
244    E,
245    Unset<Call>,
246    Unset<ExecutionInput<EmptyArgumentList<Ink>, Ink>>,
247    Unset<ReturnType<()>>,
248>
249where
250    E: Environment,
251{
252    CallBuilder {
253        call_type: Default::default(),
254        exec_input: Default::default(),
255        return_type: Default::default(),
256        _phantom: Default::default(),
257    }
258}
259
260/// Returns a new [`CallBuilder`] to build up the parameters to a cross-contract call
261/// that uses Solidity ABI Encoding.
262///
263/// See [`build_call`] for more details on usage.
264#[allow(clippy::type_complexity)]
265pub fn build_call_sol<E>() -> CallBuilder<
266    E,
267    Unset<Call>,
268    Unset<ExecutionInput<EmptyArgumentList<Sol>, Sol>>,
269    Unset<ReturnType<()>>,
270>
271where
272    E: Environment,
273{
274    CallBuilder {
275        call_type: Default::default(),
276        exec_input: Default::default(),
277        return_type: Default::default(),
278        _phantom: Default::default(),
279    }
280}
281
282/// Builds up a cross contract call.
283#[derive(Clone)]
284pub struct CallBuilder<E, CallType, Args, RetType>
285where
286    E: Environment,
287{
288    /// The current parameters that have been built up so far.
289    call_type: CallType,
290    exec_input: Args,
291    return_type: RetType,
292    _phantom: PhantomData<fn() -> E>, // todo possibly remove?
293}
294
295impl<E, Args, RetType, Abi> From<Execution<Args, RetType, Abi>>
296    for CallBuilder<
297        E,
298        Unset<Call>,
299        Set<ExecutionInput<Args, Abi>>,
300        Set<ReturnType<RetType>>,
301    >
302where
303    E: Environment,
304{
305    fn from(invoke: Execution<Args, RetType, Abi>) -> Self {
306        CallBuilder {
307            call_type: Default::default(),
308            exec_input: Set(invoke.input),
309            return_type: Set(invoke.output),
310            _phantom: Default::default(),
311        }
312    }
313}
314
315impl<E, CallType, Args, RetType> CallBuilder<E, Unset<CallType>, Args, RetType>
316where
317    E: Environment,
318{
319    /// The type of the call.
320    #[inline]
321    #[must_use]
322    pub fn call_type<NewCallType>(
323        self,
324        call_type: NewCallType,
325    ) -> CallBuilder<E, Set<NewCallType>, Args, RetType> {
326        CallBuilder {
327            call_type: Set(call_type),
328            exec_input: self.exec_input,
329            return_type: self.return_type,
330            _phantom: Default::default(),
331        }
332    }
333}
334
335impl<E, CallType, Args> CallBuilder<E, CallType, Args, Unset<ReturnType<()>>>
336where
337    E: Environment,
338{
339    /// Sets the type of the returned value upon the execution of the call.
340    ///
341    /// # Note
342    ///
343    /// Either use `.returns::<()>` to signal that the call does not return a value
344    /// or use `.returns::<T>` to signal that the call returns a value of type `T`.
345    #[inline]
346    pub fn returns<R>(self) -> CallBuilder<E, CallType, Args, Set<ReturnType<R>>> {
347        CallBuilder {
348            call_type: self.call_type,
349            exec_input: self.exec_input,
350            return_type: Set(Default::default()),
351            _phantom: Default::default(),
352        }
353    }
354}
355
356impl<E, CallType, RetType, Abi>
357    CallBuilder<E, CallType, Unset<ExecutionInput<EmptyArgumentList<Abi>, Abi>>, RetType>
358where
359    E: Environment,
360{
361    /// Sets the execution input to the given value.
362    pub fn exec_input<Args>(
363        self,
364        exec_input: ExecutionInput<Args, Abi>,
365    ) -> CallBuilder<E, CallType, Set<ExecutionInput<Args, Abi>>, RetType> {
366        CallBuilder {
367            call_type: self.call_type,
368            exec_input: Set(exec_input),
369            return_type: self.return_type,
370            _phantom: Default::default(),
371        }
372    }
373}
374
375impl<E, CallType, Args, RetType> CallBuilder<E, Unset<CallType>, Args, RetType>
376where
377    E: Environment,
378{
379    /// Prepares the `CallBuilder` for a cross-contract [`Call`] to the latest `call_v2`
380    /// host function.
381    pub fn call(self, callee: Address) -> CallBuilder<E, Set<Call>, Args, RetType> {
382        CallBuilder {
383            call_type: Set(Call::new(callee)),
384            exec_input: self.exec_input,
385            return_type: self.return_type,
386            _phantom: Default::default(),
387        }
388    }
389
390    /// Prepares the `CallBuilder` for a cross-contract [`DelegateCall`].
391    pub fn delegate(
392        self,
393        address: Address,
394    ) -> CallBuilder<E, Set<DelegateCall>, Args, RetType> {
395        CallBuilder {
396            // todo Generic `Set` can be removed
397            call_type: Set(DelegateCall::new(address)),
398            exec_input: self.exec_input,
399            return_type: self.return_type,
400            _phantom: Default::default(),
401        }
402    }
403}