// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of cargo-contract.
//
// cargo-contract is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// cargo-contract is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with cargo-contract. If not, see .
use super::{
decode::Decoder,
encode::Encoder,
env_types::{
CustomTypeTranscoder,
EnvTypesTranscoder,
PathKey,
TypesByPath,
},
scon::Value,
};
use anyhow::Result;
use scale::Output;
use scale_info::{
PortableRegistry,
TypeInfo,
};
use std::{
collections::HashMap,
fmt::Debug,
};
/// Encode strings to SCALE encoded output.
/// Decode SCALE encoded input into `Value` objects.
pub struct Transcoder<'a> {
registry: &'a PortableRegistry,
env_types: EnvTypesTranscoder,
}
impl<'a> Transcoder<'a> {
pub fn new(registry: &'a PortableRegistry, env_types: EnvTypesTranscoder) -> Self {
Self {
registry,
env_types,
}
}
pub fn encoder(&self) -> Encoder {
Encoder::new(self.registry, &self.env_types)
}
pub fn encode(&self, type_id: u32, value: &Value, output: &mut O) -> Result<()>
where
O: Output + Debug,
{
self.encoder().encode(type_id, value, output)
}
pub fn decoder(&self) -> Decoder {
Decoder::new(self.registry, &self.env_types)
}
pub fn decode(&self, type_id: u32, input: &mut &[u8]) -> Result {
self.decoder().decode(type_id, input)
}
}
/// Construct a [`Transcoder`], allows registering custom transcoders for certain types.
pub struct TranscoderBuilder<'a> {
registry: &'a PortableRegistry,
types_by_path: TypesByPath,
transcoders: HashMap>,
}
impl<'a> TranscoderBuilder<'a> {
pub fn new(registry: &'a PortableRegistry) -> Self {
let types_by_path = registry
.types()
.iter()
.map(|ty| (PathKey::from(ty.ty().path()), ty.id()))
.collect::();
Self {
registry,
types_by_path,
transcoders: HashMap::new(),
}
}
pub fn register_custom_type(self, transcoder: U) -> Self
where
T: TypeInfo + 'static,
U: CustomTypeTranscoder + 'static,
{
let mut this = self;
let path_key = PathKey::from_type::();
let type_id = this.types_by_path.get(&path_key);
match type_id {
Some(type_id) => {
let existing = this.transcoders.insert(*type_id, Box::new(transcoder));
log::debug!("Registered environment type `{:?}`", type_id);
if existing.is_some() {
panic!(
"Attempted to register transcoder with existing type id {:?}",
type_id
);
}
}
None => {
// if the type is not present in the registry, it just means it has not been used.
log::info!("No matching type in registry for path {:?}.", path_key);
}
}
this
}
pub fn done(self) -> Transcoder<'a> {
let env_types_transcoder = EnvTypesTranscoder::new(self.transcoders);
Transcoder::new(self.registry, env_types_transcoder)
}
}
#[cfg(test)]
mod tests {
use super::{
super::scon::{
self,
Hex,
Map,
Seq,
Tuple,
Value,
},
*,
};
use crate::cmd::extrinsics::transcode;
use scale::Encode;
use scale_info::{
MetaType,
Registry,
TypeInfo,
};
use std::str::FromStr;
fn registry_with_type() -> Result<(PortableRegistry, u32)>
where
T: scale_info::TypeInfo + 'static,
{
let mut registry = Registry::new();
let type_id = registry.register_type(&MetaType::new::());
let registry: PortableRegistry = registry.into();
Ok((registry, type_id.id()))
}
fn transcode_roundtrip(input: &str, expected_output: Value) -> Result<()>
where
T: scale_info::TypeInfo + 'static,
{
let (registry, ty) = registry_with_type::()?;
let transcoder = TranscoderBuilder::new(®istry)
.register_custom_type::(
transcode::env_types::AccountId,
)
.done();
let value = scon::parse_value(input)?;
let mut output = Vec::new();
transcoder.encode(ty, &value, &mut output)?;
let decoded = transcoder.decode(ty, &mut &output[..])?;
assert_eq!(expected_output, decoded, "decoding");
Ok(())
}
#[test]
fn transcode_bool() -> Result<()> {
transcode_roundtrip::("true", Value::Bool(true))?;
transcode_roundtrip::("false", Value::Bool(false))
}
#[test]
fn transcode_char_unsupported() -> Result<()> {
let (registry, ty) = registry_with_type::()?;
let transcoder = Transcoder::new(®istry, Default::default());
let encoded = u32::from('c').encode();
assert!(transcoder
.encode(ty, &Value::Char('c'), &mut Vec::new())
.is_err());
assert!(transcoder.decode(ty, &mut &encoded[..]).is_err());
Ok(())
}
#[test]
fn transcode_str() -> Result<()> {
transcode_roundtrip::("\"ink!\"", Value::String("ink!".to_string()))
}
#[test]
fn transcode_unsigned_integers() -> Result<()> {
transcode_roundtrip::("0", Value::UInt(0))?;
transcode_roundtrip::("255", Value::UInt(255))?;
transcode_roundtrip::("0", Value::UInt(0))?;
transcode_roundtrip::("65535", Value::UInt(65535))?;
transcode_roundtrip::("0", Value::UInt(0))?;
transcode_roundtrip::("4294967295", Value::UInt(4294967295))?;
transcode_roundtrip::("0", Value::UInt(0))?;
transcode_roundtrip::(
"\"18_446_744_073_709_551_615\"",
Value::UInt(18446744073709551615),
)?;
transcode_roundtrip::("0", Value::UInt(0))?;
transcode_roundtrip::(
"\"340_282_366_920_938_463_463_374_607_431_768_211_455\"",
Value::UInt(340282366920938463463374607431768211455),
)
}
#[test]
fn transcode_integers() -> Result<()> {
transcode_roundtrip::("-128", Value::Int(i8::min_value().into()))?;
transcode_roundtrip::("127", Value::Int(i8::max_value().into()))?;
transcode_roundtrip::("-32768", Value::Int(i16::min_value().into()))?;
transcode_roundtrip::("32767", Value::Int(i16::max_value().into()))?;
transcode_roundtrip::("-2147483648", Value::Int(i32::min_value().into()))?;
transcode_roundtrip::("2147483647", Value::Int(i32::max_value().into()))?;
transcode_roundtrip::(
"-9223372036854775808",
Value::Int(i64::min_value().into()),
)?;
transcode_roundtrip::(
"\"9_223_372_036_854_775_807\"",
Value::Int(i64::max_value().into()),
)?;
transcode_roundtrip::(
"-170141183460469231731687303715884105728",
Value::Int(i128::min_value()),
)?;
transcode_roundtrip::(
"\"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::>(
"[1, 2, 3]",
Value::Seq(vec![Value::UInt(1), Value::UInt(2), Value::UInt(3)].into()),
)?;
transcode_roundtrip::>(
"[\"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],
// recursive struct ref
d: Vec,
}
transcode_roundtrip::(
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::>()
.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::(
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::(
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::(
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::(
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 },
C,
}
transcode_roundtrip::(
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::(
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::(
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::