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}