contract_transcode/
env_types.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::{
18    AccountId32,
19    Hex,
20    Value,
21};
22use anyhow::{
23    Context,
24    Result,
25};
26use scale::{
27    Decode,
28    Encode,
29    Output,
30};
31use scale_info::{
32    form::PortableForm,
33    IntoPortable,
34    Path,
35    TypeInfo,
36};
37use std::{
38    boxed::Box,
39    collections::HashMap,
40    convert::TryFrom,
41    str::FromStr,
42};
43
44/// Provides custom encoding and decoding for predefined environment types.
45#[derive(Default)]
46pub struct EnvTypesTranscoder {
47    encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
48    decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
49}
50
51impl EnvTypesTranscoder {
52    /// Construct an `EnvTypesTranscoder` from the given type registry.
53    pub fn new(
54        encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
55        decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
56    ) -> Self {
57        Self { encoders, decoders }
58    }
59
60    /// If the given type id is for a type with custom encoding, encodes the given value
61    /// with the custom encoder and returns `true`. Otherwise returns `false`.
62    ///
63    /// # Errors
64    ///
65    /// - If the custom encoding fails.
66    pub fn try_encode<O>(
67        &self,
68        type_id: u32,
69        value: &Value,
70        output: &mut O,
71    ) -> Result<bool>
72    where
73        O: Output,
74    {
75        match self.encoders.get(&type_id) {
76            Some(encoder) => {
77                tracing::debug!("Encoding type {:?} with custom encoder", type_id);
78                let encoded_env_type = encoder
79                    .encode_value(value)
80                    .context("Error encoding custom type")?;
81                output.write(&encoded_env_type);
82                Ok(true)
83            }
84            None => Ok(false),
85        }
86    }
87
88    /// If the given type lookup id is for an environment type with custom
89    /// decoding, decodes the given input with the custom decoder and returns
90    /// `Some(value)`. Otherwise returns `None`.
91    ///
92    /// # Errors
93    ///
94    /// - If the custom decoding fails.
95    pub fn try_decode(&self, type_id: u32, input: &mut &[u8]) -> Result<Option<Value>> {
96        match self.decoders.get(&type_id) {
97            Some(decoder) => {
98                tracing::debug!("Decoding type {:?} with custom decoder", type_id);
99                let decoded = decoder.decode_value(input)?;
100                Ok(Some(decoded))
101            }
102            None => {
103                tracing::debug!("No custom decoder found for type {:?}", type_id);
104                Ok(None)
105            }
106        }
107    }
108}
109
110#[derive(Clone, Debug, Eq, PartialEq, Hash)]
111pub struct PathKey(Vec<String>);
112
113impl PathKey {
114    pub fn from_type<T>() -> Self
115    where
116        T: TypeInfo,
117    {
118        let type_info = T::type_info();
119        let path = type_info.path.into_portable(&mut Default::default());
120        PathKey::from(&path)
121    }
122}
123
124impl From<&Path<PortableForm>> for PathKey {
125    fn from(path: &Path<PortableForm>) -> Self {
126        PathKey(path.segments.to_vec())
127    }
128}
129
130pub type TypesByPath = HashMap<PathKey, u32>;
131
132/// Implement this trait to define custom encoding for a type in a `scale-info` type
133/// registry.
134pub trait CustomTypeEncoder: Send + Sync {
135    fn encode_value(&self, value: &Value) -> Result<Vec<u8>>;
136}
137
138/// Implement this trait to define custom decoding for a type in a `scale-info` type
139/// registry.
140pub trait CustomTypeDecoder: Send + Sync {
141    fn decode_value(&self, input: &mut &[u8]) -> Result<Value>;
142}
143
144/// Custom encoding/decoding for the Substrate `AccountId` type.
145///
146/// Enables an `AccountId` to be input/ouput as an SS58 Encoded literal e.g.
147/// 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
148#[derive(Clone)]
149pub struct AccountId;
150
151impl CustomTypeEncoder for AccountId {
152    fn encode_value(&self, value: &Value) -> Result<Vec<u8>> {
153        let account_id = match value {
154            Value::Literal(literal) => {
155                AccountId32::from_str(literal).map_err(|e| {
156                    anyhow::anyhow!(
157                        "Error parsing AccountId from literal `{}`: {}",
158                        literal,
159                        e
160                    )
161                })?
162            }
163            Value::String(string) => {
164                AccountId32::from_str(string).map_err(|e| {
165                    anyhow::anyhow!(
166                        "Error parsing AccountId from string '{}': {}",
167                        string,
168                        e
169                    )
170                })?
171            }
172            Value::Hex(hex) => {
173                AccountId32::try_from(hex.bytes()).map_err(|_| {
174                    anyhow::anyhow!(
175                        "Error converting hex bytes `{:?}` to AccountId",
176                        hex.bytes()
177                    )
178                })?
179            }
180            _ => {
181                return Err(anyhow::anyhow!(
182                    "Expected a string or a literal for an AccountId"
183                ))
184            }
185        };
186        Ok(account_id.encode())
187    }
188}
189
190impl CustomTypeDecoder for AccountId {
191    fn decode_value(&self, input: &mut &[u8]) -> Result<Value> {
192        let account_id = AccountId32::decode(input)?;
193        Ok(Value::Literal(account_id.to_ss58check()))
194    }
195}
196
197/// Custom decoding for the `Hash` or `[u8; 32]` type so that it is displayed as a hex
198/// encoded string.
199pub struct Hash;
200
201impl CustomTypeDecoder for Hash {
202    fn decode_value(&self, input: &mut &[u8]) -> Result<Value> {
203        let hash = primitive_types::H256::decode(input)?;
204        Ok(Value::Hex(Hex::from_str(&format!("{hash:?}"))?))
205    }
206}