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}