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