use super::{
decode::Decoder,
encode::Encoder,
env_types::{
self,
CustomTypeDecoder,
CustomTypeEncoder,
EnvTypesTranscoder,
PathKey,
TypesByPath,
},
scon::Value,
AccountId32,
};
use anyhow::Result;
use scale::Output;
use scale_info::{
PortableRegistry,
TypeInfo,
};
use std::{
collections::HashMap,
fmt::Debug,
};
pub struct Transcoder {
env_types: EnvTypesTranscoder,
}
impl Transcoder {
pub fn new(env_types: EnvTypesTranscoder) -> Self {
Self { env_types }
}
pub fn encode<O>(
&self,
registry: &PortableRegistry,
type_id: u32,
value: &Value,
output: &mut O,
) -> Result<()>
where
O: Output + Debug,
{
let encoder = Encoder::new(registry, &self.env_types);
encoder.encode(type_id, value, output)
}
pub fn decode(
&self,
registry: &PortableRegistry,
type_id: u32,
input: &mut &[u8],
) -> Result<Value> {
let decoder = Decoder::new(registry, &self.env_types);
decoder.decode(type_id, input)
}
}
pub struct TranscoderBuilder {
types_by_path: TypesByPath,
encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
}
impl TranscoderBuilder {
pub fn new(registry: &PortableRegistry) -> Self {
let types_by_path = registry
.types
.iter()
.map(|ty| (PathKey::from(&ty.ty.path), ty.id))
.collect::<TypesByPath>();
Self {
types_by_path,
encoders: HashMap::new(),
decoders: HashMap::new(),
}
}
pub fn with_default_custom_type_transcoders(self) -> Self {
self.register_custom_type_transcoder::<AccountId32, _>(env_types::AccountId)
.register_custom_type_decoder::<primitive_types::H256, _>(env_types::Hash)
}
pub fn register_custom_type_transcoder<T, U>(self, transcoder: U) -> Self
where
T: TypeInfo + 'static,
U: CustomTypeEncoder + CustomTypeDecoder + Clone + 'static,
{
self.register_custom_type_encoder::<T, U>(transcoder.clone())
.register_custom_type_decoder::<T, U>(transcoder)
}
pub fn register_custom_type_encoder<T, U>(self, encoder: U) -> Self
where
T: TypeInfo + 'static,
U: CustomTypeEncoder + 'static,
{
let mut this = self;
let path_key = PathKey::from_type::<T>();
let type_id = this.types_by_path.get(&path_key);
match type_id {
Some(type_id) => {
let existing = this.encoders.insert(*type_id, Box::new(encoder));
tracing::debug!("Registered custom encoder for type `{:?}`", type_id);
if existing.is_some() {
panic!(
"Attempted to register encoder with existing type id {type_id:?}"
);
}
}
None => {
tracing::debug!("No matching type in registry for path {:?}.", path_key);
}
}
this
}
pub fn register_custom_type_decoder<T, U>(self, encoder: U) -> Self
where
T: TypeInfo + 'static,
U: CustomTypeDecoder + 'static,
{
let mut this = self;
let path_key = PathKey::from_type::<T>();
let type_id = this.types_by_path.get(&path_key);
match type_id {
Some(type_id) => {
let existing = this.decoders.insert(*type_id, Box::new(encoder));
tracing::debug!("Registered custom decoder for type `{:?}`", type_id);
if existing.is_some() {
panic!(
"Attempted to register decoder with existing type id {type_id:?}"
);
}
}
None => {
tracing::debug!("No matching type in registry for path {:?}.", path_key);
}
}
this
}
pub fn done(self) -> Transcoder {
let env_types_transcoder = EnvTypesTranscoder::new(self.encoders, self.decoders);
Transcoder::new(env_types_transcoder)
}
}
#[cfg(test)]
mod tests {
use super::{
super::scon::{
self,
Hex,
Map,
Seq,
Tuple,
Value,
},
*,
};
use scale::Encode;
use scale_info::{
MetaType,
Registry,
TypeInfo,
};
use std::str::FromStr;
fn registry_with_type<T>() -> Result<(PortableRegistry, u32)>
where
T: scale_info::TypeInfo + 'static,
{
let mut registry = Registry::new();
let type_id = registry.register_type(&MetaType::new::<T>());
let registry: PortableRegistry = registry.into();
Ok((registry, type_id.id))
}
fn transcode_roundtrip<T>(input: &str, expected_output: Value) -> Result<()>
where
T: scale_info::TypeInfo + 'static,
{
let (registry, ty) = registry_with_type::<T>()?;
let transcoder = TranscoderBuilder::new(®istry)
.with_default_custom_type_transcoders()
.done();
let value = scon::parse_value(input)?;
let mut output = Vec::new();
transcoder.encode(®istry, ty, &value, &mut output)?;
let decoded = transcoder.decode(®istry, ty, &mut &output[..])?;
assert_eq!(expected_output, decoded, "decoding");
Ok(())
}
#[test]
fn transcode_bool() -> Result<()> {
transcode_roundtrip::<bool>("true", Value::Bool(true))?;
transcode_roundtrip::<bool>("false", Value::Bool(false))
}
#[test]
fn transcode_char_unsupported() -> Result<()> {
let (registry, ty) = registry_with_type::<char>()?;
let transcoder = Transcoder::new(Default::default());
let encoded = u32::from('c').encode();
assert!(transcoder
.encode(®istry, ty, &Value::Char('c'), &mut Vec::new())
.is_err());
assert!(transcoder.decode(®istry, ty, &mut &encoded[..]).is_err());
Ok(())
}
#[test]
fn transcode_str() -> Result<()> {
transcode_roundtrip::<String>("\"ink!\"", Value::String("ink!".to_string()))
}
#[test]
fn transcode_unsigned_integers() -> Result<()> {
transcode_roundtrip::<u8>("0", Value::UInt(0))?;
transcode_roundtrip::<u8>("255", Value::UInt(255))?;
transcode_roundtrip::<u16>("0", Value::UInt(0))?;
transcode_roundtrip::<u16>("65535", Value::UInt(65535))?;
transcode_roundtrip::<u32>("0", Value::UInt(0))?;
transcode_roundtrip::<u32>("4294967295", Value::UInt(4294967295))?;
transcode_roundtrip::<u64>("0", Value::UInt(0))?;
transcode_roundtrip::<u64>(
"\"18_446_744_073_709_551_615\"",
Value::UInt(18446744073709551615),
)?;
transcode_roundtrip::<u128>("0", Value::UInt(0))?;
transcode_roundtrip::<u128>(
"\"340_282_366_920_938_463_463_374_607_431_768_211_455\"",
Value::UInt(340282366920938463463374607431768211455),
)
}
#[test]
fn transcode_integers() -> Result<()> {
transcode_roundtrip::<i8>("-128", Value::Int(i8::min_value().into()))?;
transcode_roundtrip::<i8>("127", Value::Int(i8::max_value().into()))?;
transcode_roundtrip::<i16>("-32768", Value::Int(i16::min_value().into()))?;
transcode_roundtrip::<i16>("32767", Value::Int(i16::max_value().into()))?;
transcode_roundtrip::<i32>("-2147483648", Value::Int(i32::min_value().into()))?;
transcode_roundtrip::<i32>("2147483647", Value::Int(i32::max_value().into()))?;
transcode_roundtrip::<i64>(
"-9223372036854775808",
Value::Int(i64::min_value().into()),
)?;
transcode_roundtrip::<i64>(
"\"9_223_372_036_854_775_807\"",
Value::Int(i64::max_value().into()),
)?;
transcode_roundtrip::<i128>(
"-170141183460469231731687303715884105728",
Value::Int(i128::min_value()),
)?;
transcode_roundtrip::<i128>(
"\"170141183460469231731687303715884105727\"",
Value::Int(i128::max_value()),
)
}
#[test]
fn transcode_byte_array() -> Result<()> {
transcode_roundtrip::<[u8; 2]>(
r#"0x0000"#,
Value::Seq(vec![Value::UInt(0x00), Value::UInt(0x00)].into()),
)?;
transcode_roundtrip::<[u8; 4]>(
r#"0xDEADBEEF"#,
Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
),
)?;
transcode_roundtrip::<[u8; 4]>(
r#"0xdeadbeef"#,
Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
),
)
}
#[test]
fn transcode_array() -> Result<()> {
transcode_roundtrip::<[u32; 3]>(
"[1, 2, 3]",
Value::Seq(vec![Value::UInt(1), Value::UInt(2), Value::UInt(3)].into()),
)?;
transcode_roundtrip::<[String; 2]>(
"[\"hello\", \"world\"]",
Value::Seq(
vec![
Value::String("hello".to_string()),
Value::String("world".to_string()),
]
.into(),
),
)
}
#[test]
fn transcode_seq() -> Result<()> {
transcode_roundtrip::<Vec<u32>>(
"[1, 2, 3]",
Value::Seq(vec![Value::UInt(1), Value::UInt(2), Value::UInt(3)].into()),
)?;
transcode_roundtrip::<Vec<String>>(
"[\"hello\", \"world\"]",
Value::Seq(
vec![
Value::String("hello".to_string()),
Value::String("world".to_string()),
]
.into(),
),
)
}
#[test]
fn transcode_tuple() -> Result<()> {
transcode_roundtrip::<(u32, String, [u8; 4])>(
r#"(1, "ink!", 0xDEADBEEF)"#,
Value::Tuple(Tuple::new(
None,
vec![
Value::UInt(1),
Value::String("ink!".to_string()),
Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
),
],
)),
)
}
#[test]
fn transcode_composite_struct() -> Result<()> {
#[allow(dead_code)]
#[derive(TypeInfo)]
struct S {
a: u32,
b: String,
c: [u8; 4],
d: Vec<S>,
}
transcode_roundtrip::<S>(
r#"S(a: 1, b: "ink!", c: 0xDEADBEEF, d: [S(a: 2, b: "ink!", c: 0xDEADBEEF, d: [])])"#,
Value::Map(
vec![
(Value::String("a".to_string()), Value::UInt(1)),
(
Value::String("b".to_string()),
Value::String("ink!".to_string()),
),
(
Value::String("c".to_string()),
Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
),
),
(
Value::String("d".to_string()),
Value::Seq(
vec![Value::Map(
vec![
(Value::String("a".to_string()), Value::UInt(2)),
(
Value::String("b".to_string()),
Value::String("ink!".to_string()),
),
(
Value::String("c".to_string()),
Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
),
),
(
Value::String("d".to_string()),
Value::Seq(
Vec::new()
.into_iter()
.collect::<Vec<_>>()
.into(),
),
),
]
.into_iter()
.collect(),
)]
.into(),
),
),
]
.into_iter()
.collect(),
),
)
}
#[test]
fn transcode_composite_struct_nested() -> Result<()> {
#[allow(dead_code)]
#[derive(TypeInfo)]
struct S {
nested: Nested,
}
#[allow(dead_code)]
#[derive(TypeInfo)]
struct Nested(u32);
transcode_roundtrip::<S>(
r#"S { nested: Nested(33) }"#,
Value::Map(Map::new(
Some("S"),
vec![(
Value::String("nested".to_string()),
Value::Tuple(Tuple::new(
Some("Nested"),
vec![Value::UInt(33)].into_iter().collect(),
)),
)]
.into_iter()
.collect(),
)),
)
}
#[test]
fn transcode_composite_struct_out_of_order_fields() -> Result<()> {
#[allow(dead_code)]
#[derive(TypeInfo)]
struct S {
a: u32,
b: String,
c: [u8; 4],
}
transcode_roundtrip::<S>(
r#"S(b: "ink!", a: 1, c: 0xDEADBEEF)"#,
Value::Map(
vec![
(Value::String("a".to_string()), Value::UInt(1)),
(
Value::String("b".to_string()),
Value::String("ink!".to_string()),
),
(
Value::String("c".to_string()),
Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
),
),
]
.into_iter()
.collect(),
),
)
}
#[test]
fn transcode_composite_tuple_struct() -> Result<()> {
#[allow(dead_code)]
#[derive(TypeInfo)]
struct S(u32, String, [u8; 4]);
transcode_roundtrip::<S>(
r#"S(1, "ink!", 0xDEADBEEF)"#,
Value::Tuple(Tuple::new(
Some("S"),
vec![
Value::UInt(1),
Value::String("ink!".to_string()),
Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
),
],
)),
)
}
#[test]
fn transcode_composite_single_field_struct() -> Result<()> {
#[allow(dead_code)]
#[derive(TypeInfo)]
struct S([u8; 4]);
transcode_roundtrip::<S>(
r#"0xDEADBEEF"#,
Value::Tuple(Tuple::new(
Some("S"),
vec![Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
)],
)),
)
}
#[test]
fn transcode_composite_single_field_tuple() -> Result<()> {
transcode_roundtrip::<([u8; 4],)>(
r#"0xDEADBEEF"#,
Value::Tuple(Tuple::new(
None,
vec![Value::Seq(
vec![
Value::UInt(0xDE),
Value::UInt(0xAD),
Value::UInt(0xBE),
Value::UInt(0xEF),
]
.into(),
)],
)),
)
}
#[test]
fn transcode_enum_variant_tuple() -> Result<()> {
#[derive(TypeInfo)]
#[allow(dead_code)]
enum E {
A(u32, String),
B { a: [u8; 4], b: Vec<E> },
C,
}
transcode_roundtrip::<E>(
r#"A(1, "2")"#,
Value::Tuple(Tuple::new(
Some("A"),
vec![Value::UInt(1), Value::String("2".into())],
)),
)
}
#[test]
fn transcode_enum_variant_map() -> Result<()> {
#[derive(TypeInfo)]
#[allow(dead_code)]
enum E {
A { a: u32, b: bool },
}
transcode_roundtrip::<E>(
r#"A { a: 33, b: false }"#,
Value::Map(Map::new(
Some("A"),
vec![
(Value::String("a".to_string()), Value::UInt(33)),
(Value::String("b".to_string()), Value::Bool(false)),
]
.into_iter()
.collect(),
)),
)
}
#[test]
fn transcode_enum_variant_map_out_of_order_fields() -> Result<()> {
#[derive(TypeInfo)]
#[allow(dead_code)]
enum E {
A { a: u32, b: bool },
}
transcode_roundtrip::<E>(
r#"A { a: 33, b: false }"#,
Value::Map(Map::new(
Some("A"),
vec![
(Value::String("a".to_string()), Value::UInt(33)),
(Value::String("b".to_string()), Value::Bool(false)),
]
.into_iter()
.collect(),
)),
)
}
#[test]
fn transcode_option() -> Result<()> {
transcode_roundtrip::<Option<u32>>(
r#"Some(32)"#,
Value::Tuple(Tuple::new(Some("Some"), vec![Value::UInt(32)])),
)?;
transcode_roundtrip::<Option<u32>>(
r#"None"#,
Value::Tuple(Tuple::new(Some("None"), Vec::new())),
)
}
#[test]
fn transcode_account_id_custom_ss58_encoding() -> Result<()> {
type AccountId = AccountId32;
#[allow(dead_code)]
#[derive(TypeInfo)]
struct S {
no_alias: AccountId32,
aliased: AccountId,
}
transcode_roundtrip::<S>(
r#"S(
no_alias: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,
aliased: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty,
)"#,
Value::Map(Map::new(
Some("S"),
vec![
(
Value::String("no_alias".into()),
Value::Literal(
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".into(),
),
),
(
Value::String("aliased".into()),
Value::Literal(
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(),
),
),
]
.into_iter()
.collect(),
)),
)
}
#[test]
fn transcode_account_id_custom_ss58_encoding_seq() -> Result<()> {
let hex_to_bytes = |hex: &str| -> Result<Value> {
let hex = Hex::from_str(hex)?;
let values = hex.bytes().iter().map(|b| Value::UInt((*b).into()));
Ok(Value::Seq(Seq::new(values.collect())))
};
transcode_roundtrip::<Vec<AccountId32>>(
r#"[
5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,
5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty,
]"#,
Value::Seq(Seq::new(
vec![
Value::Tuple(
Tuple::new(
Some("AccountId32"),
vec![hex_to_bytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d")?]
)
),
Value::Tuple(
Tuple::new(
Some("AccountId32"),
vec![hex_to_bytes("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")?]
)
)
]
.into_iter()
.collect(),
)),
)
}
#[test]
fn decode_h256_as_hex_string() -> Result<()> {
#[allow(dead_code)]
#[derive(TypeInfo)]
struct S {
hash: primitive_types::H256,
}
transcode_roundtrip::<S>(
r#"S(
hash: 0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de,
)"#,
Value::Map(Map::new(
Some("S"),
vec![
(
Value::String("hash".into()),
Value::Hex(
Hex::from_str("0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de")?,
),
),
]
.into_iter()
.collect(),
)),
)
}
#[test]
fn transcode_compact_primitives() -> Result<()> {
transcode_roundtrip::<scale::Compact<u8>>(r#"33"#, Value::UInt(33))?;
transcode_roundtrip::<scale::Compact<u16>>(r#"33"#, Value::UInt(33))?;
transcode_roundtrip::<scale::Compact<u32>>(r#"33"#, Value::UInt(33))?;
transcode_roundtrip::<scale::Compact<u64>>(r#"33"#, Value::UInt(33))?;
transcode_roundtrip::<scale::Compact<u128>>(r#"33"#, Value::UInt(33))
}
#[test]
fn transcode_compact_struct() -> Result<()> {
#[derive(scale::Encode, scale::CompactAs, TypeInfo)]
struct CompactStruct(u32);
#[allow(dead_code)]
#[derive(scale::Encode, TypeInfo)]
struct S {
#[codec(compact)]
a: CompactStruct,
}
transcode_roundtrip::<S>(
r#"S { a: CompactStruct(33) }"#,
Value::Map(Map::new(
Some("S"),
vec![(
Value::String("a".to_string()),
Value::Tuple(Tuple::new(
Some("CompactStruct"),
vec![Value::UInt(33)],
)),
)]
.into_iter()
.collect(),
)),
)
}
#[test]
fn transcode_hex_literal_uints() -> Result<()> {
#[derive(scale::Encode, TypeInfo)]
struct S(u8, u16, u32, u64, u128);
transcode_roundtrip::<S>(
r#"S (0xDE, 0xDEAD, 0xDEADBEEF, 0xDEADBEEF12345678, 0xDEADBEEF0123456789ABCDEF01234567)"#,
Value::Tuple(Tuple::new(
Some("S"),
vec![
Value::UInt(0xDE),
Value::UInt(0xDEAD),
Value::UInt(0xDEADBEEF),
Value::UInt(0xDEADBEEF12345678),
Value::UInt(0xDEADBEEF0123456789ABCDEF01234567),
]
.into_iter()
.collect(),
)),
)
}
}