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}