ink_env/
chain_extension.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
15//! Definitions and utilities for calling chain extension methods.
16//!
17//! Users should not use these types and definitions directly but rather use the provided
18//! `#[ink::chain_extension]` procedural macro defined in the `ink` crate.
19
20#[cfg(feature = "unstable-hostfn")]
21use crate::{
22    backend::EnvBackend,
23    engine::{
24        EnvInstance,
25        OnInstance,
26    },
27};
28use core::marker::PhantomData;
29
30/// Implemented by error codes in order to construct them from status codes.
31///
32/// A status code is returned by calling an ink! chain extension method.
33/// It is the `u32` return value.
34///
35/// The purpose of an `ErrorCode` type that implements this trait is to provide
36/// more context information about the status of an ink! chain extension method call.
37pub trait FromStatusCode: Sized {
38    /// Returns `Ok` if the status code for the called chain extension method is valid.
39    ///
40    /// Returning `Ok` will query the output buffer of the call if the chain extension
41    /// method definition has a return value.
42    ///
43    /// # Note
44    ///
45    /// The convention is to use `0` as the only `raw` value that yields `Ok` whereas
46    /// every other value represents one error code. By convention this mapping should
47    /// never panic and therefore every `raw` value must map to either `Ok` or to a proper
48    /// `ErrorCode` variant.
49    fn from_status_code(status_code: u32) -> Result<(), Self>;
50}
51
52/// A concrete instance of a chain extension method.
53///
54/// This is a utility type used to drive the execution of a chain extension method call.
55/// It has several specializations of its `call` method for different ways to manage
56/// error handling when calling a predefined chain extension method.
57///
58/// - `I` represents the input type of the chain extension method. All tuple types that
59///   may act as input parameters for the chain extension method are valid. Examples
60///   include `()`, `i32`, `(u8, [u8; 5], i32)`, etc.
61/// - `O` represents the return (or output) type of the chain extension method.
62/// - `ErrorCode` represents how the chain extension method handles the chain extension's
63///   error code. Only `HandleErrorCode<E>` and `IgnoreErrorCode` types are allowed that
64///   each say to either properly handle or ignore the chain extension's error code
65///   respectively.
66/// - `const IS_RESULT: bool` indicates if the `O` (output type) is of `Result<T, E>`
67///   type.
68///
69/// The type states for type parameter `O` and `ErrorCode` represent 4 different states:
70///
71/// 1. The chain extension method makes use of the chain extension's error code:
72///    `HandleErrorCode(E)`
73///     - **A:** The chain extension method returns a `Result<T, E>` type, i.e.
74///       `IS_RESULT` is set to `true`.
75///     - **B:** The chain extension method returns a type `O` that is not a `Result`
76///       type. The return type is still wrapped into `Result<O, E>`
77/// 2. The chain extension ignores the chain extension's error code: `IgnoreErrorCode`
78///     - **A:** The chain extension method returns a `Result<T, E>` type, i.e.
79///       `IS_RESULT` is set to `true`.
80///     - **B:** The chain extension method returns a type `O` that is not a `Result`
81///       type. The method just returns `O`.
82#[derive(Debug)]
83pub struct ChainExtensionMethod<I, O, ErrorCode, const IS_RESULT: bool> {
84    id: u32,
85    #[allow(clippy::type_complexity)]
86    state: PhantomData<fn() -> (I, O, ErrorCode)>,
87}
88
89impl ChainExtensionMethod<(), (), (), false> {
90    /// Creates a new chain extension method instance.
91    #[inline]
92    pub fn build(id: u32) -> Self {
93        Self {
94            id,
95            state: Default::default(),
96        }
97    }
98}
99
100impl<O, ErrorCode, const IS_RESULT: bool>
101    ChainExtensionMethod<(), O, ErrorCode, IS_RESULT>
102{
103    /// Sets the input types of the chain extension method call to `I`.
104    ///
105    /// # Note
106    ///
107    /// `I` represents the input type of the chain extension method.
108    /// All tuple types that may act as input parameters for the chain extension method
109    /// are valid. Examples include `()`, `i32`, `(u8, [u8; 5], i32)`, etc.
110    #[inline]
111    pub fn input<I>(self) -> ChainExtensionMethod<I, O, ErrorCode, IS_RESULT>
112    where
113        I: scale::Encode,
114    {
115        ChainExtensionMethod {
116            id: self.id,
117            state: Default::default(),
118        }
119    }
120}
121
122impl<I, ErrorCode> ChainExtensionMethod<I, (), ErrorCode, false> {
123    /// Sets the output type, `O`, of the chain extension method call.
124    ///
125    /// If `const IS_RESULT: bool` is set to `true`,
126    /// `O` is treated as `Result<T, E>`
127    ///
128    /// # Note
129    ///
130    /// If `O` is incorrectly indicated as `Return<T, E>`,
131    /// the type will not satisfy trait bounds later in method builder pipeline.
132    #[inline]
133    pub fn output<O, const IS_RESULT: bool>(
134        self,
135    ) -> ChainExtensionMethod<I, O, ErrorCode, IS_RESULT>
136    where
137        O: scale::Decode,
138    {
139        ChainExtensionMethod {
140            id: self.id,
141            state: Default::default(),
142        }
143    }
144}
145
146impl<I, O, const IS_RESULT: bool> ChainExtensionMethod<I, O, (), IS_RESULT> {
147    /// Makes the chain extension method call assume that the returned status code is
148    /// always success.
149    ///
150    /// # Note
151    ///
152    /// This will avoid handling of failure status codes returned by the chain extension
153    /// method call. Use this only if you are sure that the chain extension method
154    /// call will never return an error code that represents failure.
155    ///
156    /// The output of the chain extension method call is always decoded and returned in
157    /// this case.
158    #[inline]
159    pub fn ignore_error_code(
160        self,
161    ) -> ChainExtensionMethod<I, O, state::IgnoreErrorCode, IS_RESULT> {
162        ChainExtensionMethod {
163            id: self.id,
164            state: Default::default(),
165        }
166    }
167
168    /// Makes the chain extension method call handle the returned status code.
169    ///
170    /// # Note
171    ///
172    /// This will handle the returned status code and only loads and decodes the value
173    /// returned as the output of the chain extension method call in case of success.
174    #[inline]
175    pub fn handle_error_code<ErrorCode>(
176        self,
177    ) -> ChainExtensionMethod<I, O, state::HandleErrorCode<ErrorCode>, IS_RESULT>
178    where
179        ErrorCode: FromStatusCode,
180    {
181        ChainExtensionMethod {
182            id: self.id,
183            state: Default::default(),
184        }
185    }
186}
187
188/// Type states of the chain extension method instance.
189pub mod state {
190    use core::marker::PhantomData;
191
192    /// Type state meaning that the chain extension method ignores the chain extension's
193    /// error code.
194    #[derive(Debug)]
195    pub enum IgnoreErrorCode {}
196
197    /// Type state meaning that the chain extension method uses the chain extension's
198    /// error code.
199    #[derive(Debug)]
200    pub struct HandleErrorCode<T> {
201        error_code: PhantomData<fn() -> T>,
202    }
203}
204
205impl<I, O, ErrorCode> ChainExtensionMethod<I, O, state::HandleErrorCode<ErrorCode>, true>
206where
207    O: IsResultType,
208    I: scale::Encode,
209    <O as IsResultType>::Ok: scale::Decode,
210    <O as IsResultType>::Err: scale::Decode + From<ErrorCode> + From<scale::Error>,
211    ErrorCode: FromStatusCode,
212{
213    /// Calls the chain extension method for case 1.A described [here].
214    ///
215    /// [here]: [`ChainExtensionMethod`]
216    ///
217    /// # Errors
218    ///
219    /// - If the called chain extension method returns a non-successful error code.
220    /// - If the `Result` return value of the called chain extension represents an error.
221    /// - If the `Result` return value cannot be SCALE decoded properly.
222    /// - If custom constraints specified by the called chain extension method are
223    ///   violated.
224    ///     - These constraints are determined and defined by the author of the chain
225    ///       extension method.
226    ///
227    /// # Example
228    ///
229    /// Declares a chain extension method with the unique ID of 5 that requires a `bool`
230    /// and an `i32` as input parameters and returns a `Result<i32, MyError>` upon
231    /// completion. Note how we set const constant argument to `true` to indicate that
232    /// return type is `Result<T, E>`. It will handle the shared error code from the
233    /// chain extension. The call is finally invoked with arguments `true` and `42`
234    /// for the `bool` and `i32` input parameter respectively.
235    ///
236    /// ```should_panic
237    /// # // Panics because the off-chain environment has not
238    /// # // registered a chain extension method for the ID.
239    /// # use ink_env::chain_extension::{ChainExtensionMethod, FromStatusCode};
240    /// let result = ChainExtensionMethod::build(5)
241    ///     .input::<(bool, i32)>()
242    ///     .output::<Result<i32, MyError>, true>()
243    ///     .handle_error_code::<MyErrorCode>()
244    ///     .call(&(true, 42));
245    /// # #[derive(scale::Encode, scale::Decode)]
246    /// # pub struct MyError {}
247    /// # impl From<scale::Error> for MyError {
248    /// #     fn from(_error: scale::Error) -> Self { Self {} }
249    /// # }
250    /// # impl From<MyErrorCode> for MyError {
251    /// #     fn from(_error: MyErrorCode) -> Self { Self {} }
252    /// # }
253    /// # pub struct MyErrorCode {}
254    /// # impl FromStatusCode for MyErrorCode {
255    /// #     fn from_status_code(status_code: u32) -> Result<(), Self> { Ok(()) }
256    /// # }
257    /// ```
258    #[inline]
259    #[cfg(feature = "unstable-hostfn")]
260    pub fn call(
261        self,
262        input: &I,
263    ) -> Result<<O as IsResultType>::Ok, <O as IsResultType>::Err> {
264        <EnvInstance as OnInstance>::on_instance(|instance| {
265            EnvBackend::call_chain_extension::<
266                I,
267                <O as IsResultType>::Ok,
268                <O as IsResultType>::Err,
269                ErrorCode,
270                _,
271                _,
272            >(
273                instance,
274                self.id,
275                input,
276                ErrorCode::from_status_code,
277                |mut output| scale::Decode::decode(&mut output).map_err(Into::into),
278            )
279        })
280    }
281}
282
283impl<I, O> ChainExtensionMethod<I, O, state::IgnoreErrorCode, true>
284where
285    O: IsResultType,
286    I: scale::Encode,
287    <O as IsResultType>::Ok: scale::Decode,
288    <O as IsResultType>::Err: scale::Decode + From<scale::Error>,
289{
290    /// Calls the chain extension method for case 2.A described [here].
291    ///
292    /// [here]: [`ChainExtensionMethod`]
293    ///
294    /// # Errors
295    ///
296    /// - If the `Result` return value of the called chain extension represents an error.
297    /// - If the `Result` return value cannot be SCALE decoded properly.
298    /// - If custom constraints specified by the called chain extension method are
299    ///   violated.
300    ///     - These constraints are determined and defined by the author of the chain
301    ///       extension method.
302    ///
303    /// # Example
304    ///
305    /// Declares a chain extension method with the unique ID of 5 that requires a `bool`
306    /// and an `i32` as input parameters and returns a `Result<i32, MyError>` upon
307    /// completion. Note how we set const constant argument to `true` to indicate that
308    /// return type is `Result<T, E>`. It will ignore the shared error code from the
309    /// chain extension and assumes that the call succeeds. The call is finally
310    /// invoked with arguments `true` and `42` for the `bool` and `i32` input
311    /// parameter respectively.
312    ///
313    /// ```should_panic
314    /// # // Panics because the off-chain environment has not
315    /// # // registered a chain extension method for the ID.
316    /// # use ink_env::chain_extension::{ChainExtensionMethod};
317    /// let result = ChainExtensionMethod::build(5)
318    ///     .input::<(bool, i32)>()
319    ///     .output::<Result<i32, MyError>, true>()
320    ///     .ignore_error_code()
321    ///     .call(&(true, 42));
322    /// # #[derive(scale::Encode, scale::Decode)]
323    /// # pub struct MyError {}
324    /// # impl From<scale::Error> for MyError {
325    /// #     fn from(_error: scale::Error) -> Self { Self {} }
326    /// # }
327    /// ```
328    #[inline]
329    #[cfg(feature = "unstable-hostfn")]
330    pub fn call(
331        self,
332        input: &I,
333    ) -> Result<<O as IsResultType>::Ok, <O as IsResultType>::Err> {
334        <EnvInstance as OnInstance>::on_instance(|instance| {
335            EnvBackend::call_chain_extension::<
336                I,
337                <O as IsResultType>::Ok,
338                <O as IsResultType>::Err,
339                <O as IsResultType>::Err,
340                _,
341                _,
342            >(
343                instance,
344                self.id,
345                input,
346                |_status_code| Ok(()),
347                |mut output| scale::Decode::decode(&mut output).map_err(Into::into),
348            )
349        })
350    }
351}
352
353impl<I, O, ErrorCode> ChainExtensionMethod<I, O, state::HandleErrorCode<ErrorCode>, false>
354where
355    I: scale::Encode,
356    O: scale::Decode,
357    ErrorCode: FromStatusCode,
358{
359    /// Calls the chain extension method for case 1.B described [here].
360    ///
361    /// [here]: [`ChainExtensionMethod`]
362    ///
363    /// # Errors
364    ///
365    /// - If the called chain extension method returns a non-successful error code.
366    /// - If custom constraints specified by the called chain extension method are
367    ///   violated.
368    ///     - These constraints are determined and defined by the author of the chain
369    ///       extension method.
370    ///
371    /// # Panics
372    ///
373    /// - If the return value cannot be SCALE decoded properly.
374    ///
375    /// # Example
376    ///
377    /// Declares a chain extension method with the unique ID of 5 that requires a `bool`
378    /// and an `i32` as input parameters and returns a `Result<i32, MyErrorCode>` upon
379    /// completion, because `handle_status` flag is set.
380    /// We still need to indicate that the original type is not `Result<T, E>`, so
381    /// `const IS_RESULT` is set `false`.
382    /// It will handle the shared error code from the chain extension.
383    /// The call is finally invoked with arguments `true` and `42` for the `bool` and
384    /// `i32` input parameter respectively.
385    ///
386    /// ```should_panic
387    /// # // Panics because the off-chain environment has not
388    /// # // registered a chain extension method for the ID.
389    /// # use ink_env::chain_extension::{ChainExtensionMethod, FromStatusCode};
390    /// let result = ChainExtensionMethod::build(5)
391    ///     .input::<(bool, i32)>()
392    ///     .output::<i32, false>()
393    ///     .handle_error_code::<MyErrorCode>()
394    ///     .call(&(true, 42));
395    /// # pub struct MyErrorCode {}
396    /// # impl FromStatusCode for MyErrorCode {
397    /// #     fn from_status_code(status_code: u32) -> Result<(), Self> { Ok(()) }
398    /// # }
399    /// ```
400    #[inline]
401    #[cfg(feature = "unstable-hostfn")]
402    pub fn call(self, input: &I) -> Result<O, ErrorCode> {
403        <EnvInstance as OnInstance>::on_instance(|instance| {
404            EnvBackend::call_chain_extension::<I, O, ErrorCode, ErrorCode, _, _>(
405                instance,
406                self.id,
407                input,
408                ErrorCode::from_status_code,
409                |mut output| {
410                    let decoded = <O as scale::Decode>::decode(&mut output)
411                        .expect("encountered error while decoding chain extension method call return value");
412                    Ok(decoded)
413                },
414            )
415        })
416    }
417}
418
419impl<I, O> ChainExtensionMethod<I, O, state::IgnoreErrorCode, false>
420where
421    I: scale::Encode,
422    O: scale::Decode,
423{
424    /// Calls the chain extension method for case 2.B described [here].
425    ///
426    /// [here]: [`ChainExtensionMethod`]
427    ///
428    /// # Panics
429    ///
430    /// - If the return value cannot be SCALE decoded properly.
431    ///
432    /// # Example
433    ///
434    /// Declares a chain extension method with the unique ID of 5 that requires a `bool`
435    /// and an `i32` as input parameters and returns a `i32` upon completion. Hence,
436    /// `const IS_RESULT` is set `false`. It will ignore the shared error code from
437    /// the chain extension and assumes that the call succeeds. The call is finally
438    /// invoked with arguments `true` and `42` for the `bool` and `i32` input
439    /// parameter respectively.
440    ///
441    /// ```should_panic
442    /// # // Panics because the off-chain environment has not
443    /// # // registered a chain extension method for the ID.
444    /// # use ink_env::chain_extension::ChainExtensionMethod;
445    /// let result = ChainExtensionMethod::build(5)
446    ///     .input::<(bool, i32)>()
447    ///     .output::<i32, false>()
448    ///     .ignore_error_code()
449    ///     .call(&(true, 42));
450    /// ```
451    #[inline]
452    #[cfg(feature = "unstable-hostfn")]
453    pub fn call(self, input: &I) -> O {
454        <EnvInstance as OnInstance>::on_instance(|instance| {
455            EnvBackend::call_chain_extension::<I, O, (), (), _, _>(
456                instance,
457                self.id,
458                input,
459                |_status_code| Ok(()),
460                |mut output| {
461                    let decoded = <O as scale::Decode>::decode(&mut output)
462                        .expect("encountered error while decoding chain extension method call return value");
463                    Ok(decoded)
464                },
465            ).expect("assume the chain extension method never fails")
466        })
467    }
468}
469
470/// Extract `Ok` and `Err` variants from `Result` type.
471pub trait IsResultType: private::IsResultTypeSealed {
472    /// The `T` type of the `Result<T, E>`.
473    type Ok;
474    /// The `E` type of the `Result<T, E>`.
475    type Err;
476}
477
478impl<T, E> private::IsResultTypeSealed for Result<T, E> {}
479impl<T, E> IsResultType for Result<T, E> {
480    type Ok = T;
481    type Err = E;
482}
483
484mod private {
485    /// Seals the `IsResultType` trait so that it cannot be implemented outside this
486    /// module.
487    pub trait IsResultTypeSealed {}
488}