diff --git a/src/cmd/extrinsics/events.rs b/src/cmd/extrinsics/events.rs index 4cf9251fa909c476b594f8b392539484ce1d37ea..58669f5dc5eafb5e4aa8098d79946cb0342d93a0 100644 --- a/src/cmd/extrinsics/events.rs +++ b/src/cmd/extrinsics/events.rs @@ -17,7 +17,6 @@ use super::{ runtime_api::api::contracts::events::ContractEmitted, transcode::{ - env_types, ContractMessageTranscoder, TranscoderBuilder, }, @@ -54,7 +53,7 @@ pub fn display_events( let runtime_metadata = subxt_metadata.runtime_metadata(); let events_transcoder = TranscoderBuilder::new(&runtime_metadata.types) - .register_custom_type::(env_types::AccountId) + .with_default_custom_type_transcoders() .done(); const EVENT_FIELD_INDENT: usize = DEFAULT_KEY_COL_WIDTH - 3; diff --git a/src/cmd/extrinsics/transcode/encode.rs b/src/cmd/extrinsics/transcode/encode.rs index 86fdc833fac9ab21f8d367f73b76cb6f337031e3..e7617953647e2e902a7c0cc9865b1246476fd1d2 100644 --- a/src/cmd/extrinsics/transcode/encode.rs +++ b/src/cmd/extrinsics/transcode/encode.rs @@ -484,9 +484,9 @@ where Ok(()) } -/// Attempt to instantiate a type from its little-endian bytes representation. +/// Attempt to instantiate a type from its hex-encoded bytes representation. pub trait TryFromHex: Sized { - /// Create a new instance from the little-endian bytes representation. + /// Create a new instance from the hex-encoded bytes representation. fn try_from_hex(hex: &str) -> Result; } diff --git a/src/cmd/extrinsics/transcode/env_types.rs b/src/cmd/extrinsics/transcode/env_types.rs index 7ecaad99a845fb5b4f422525891e9f385b7620f3..f3135ab633fdb8ef156d70586eba645558a1b8ca 100644 --- a/src/cmd/extrinsics/transcode/env_types.rs +++ b/src/cmd/extrinsics/transcode/env_types.rs @@ -15,6 +15,7 @@ // along with cargo-contract. If not, see . use super::scon::Value; +use crate::cmd::extrinsics::transcode::scon::Hex; use anyhow::{ Context, Result, @@ -44,13 +45,17 @@ use std::{ /// Provides custom encoding and decoding for predefined environment types. #[derive(Default)] pub struct EnvTypesTranscoder { - transcoders: HashMap>, + encoders: HashMap>, + decoders: HashMap>, } impl EnvTypesTranscoder { /// Construct an `EnvTypesTranscoder` from the given type registry. - pub fn new(transcoders: HashMap>) -> Self { - Self { transcoders } + pub fn new( + encoders: HashMap>, + decoders: HashMap>, + ) -> Self { + Self { encoders, decoders } } /// If the given type id is for a type with custom encoding, encodes the given value with the @@ -68,10 +73,10 @@ impl EnvTypesTranscoder { where O: Output, { - match self.transcoders.get(&type_id) { - Some(transcoder) => { + match self.encoders.get(&type_id) { + Some(encoder) => { log::debug!("Encoding type {:?} with custom encoder", type_id); - let encoded_env_type = transcoder + let encoded_env_type = encoder .encode_value(value) .context("Error encoding custom type")?; output.write(&encoded_env_type); @@ -89,10 +94,10 @@ impl EnvTypesTranscoder { /// /// - If the custom decoding fails. pub fn try_decode(&self, type_id: u32, input: &mut &[u8]) -> Result> { - match self.transcoders.get(&type_id) { - Some(transcoder) => { + match self.decoders.get(&type_id) { + Some(decoder) => { log::debug!("Decoding type {:?} with custom decoder", type_id); - let decoded = transcoder.decode_value(input)?; + let decoded = decoder.decode_value(input)?; Ok(Some(decoded)) } None => { @@ -103,13 +108,6 @@ impl EnvTypesTranscoder { } } -/// Implement this trait to define custom transcoding for a type in a `scale-info` type registry. -pub trait CustomTypeTranscoder { - fn aliases(&self) -> &[&str]; - fn encode_value(&self, value: &Value) -> Result>; - fn decode_value(&self, input: &mut &[u8]) -> Result; -} - #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct PathKey(Vec); @@ -135,12 +133,24 @@ impl From<&Path> for PathKey { pub type TypesByPath = HashMap; +/// Implement this trait to define custom encoding for a type in a `scale-info` type registry. +pub trait CustomTypeEncoder { + fn encode_value(&self, value: &Value) -> Result>; +} + +/// Implement this trait to define custom decoding for a type in a `scale-info` type registry. +pub trait CustomTypeDecoder { + fn decode_value(&self, input: &mut &[u8]) -> Result; +} + +/// Custom encoding/decoding for the Substrate `AccountId` type. +/// +/// Enables an `AccountId` to be input/ouput as an SS58 Encoded literal e.g. +/// 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY +#[derive(Clone)] pub struct AccountId; -impl CustomTypeTranscoder for AccountId { - fn aliases(&self) -> &[&'static str] { - &["AccountId"] - } +impl CustomTypeEncoder for AccountId { fn encode_value(&self, value: &Value) -> Result> { let account_id = match value { Value::Literal(literal) => { @@ -177,9 +187,22 @@ impl CustomTypeTranscoder for AccountId { }; Ok(account_id.encode()) } +} +impl CustomTypeDecoder for AccountId { fn decode_value(&self, input: &mut &[u8]) -> Result { let account_id = AccountId32::decode(input)?; Ok(Value::Literal(account_id.to_ss58check())) } } + +/// Custom decoding for the `Hash` or `[u8; 32]` type so that it is displayed as a hex encoded +/// string. +pub struct Hash; + +impl CustomTypeDecoder for Hash { + fn decode_value(&self, input: &mut &[u8]) -> Result { + let hash = sp_core::H256::decode(input)?; + Ok(Value::Hex(Hex::from_str(&format!("{:?}", hash))?)) + } +} diff --git a/src/cmd/extrinsics/transcode/mod.rs b/src/cmd/extrinsics/transcode/mod.rs index bac8be4260985113d8af744690589d14b38cc28f..cd1ed8e39b42a3b6753384bfd0e88b943ee690d5 100644 --- a/src/cmd/extrinsics/transcode/mod.rs +++ b/src/cmd/extrinsics/transcode/mod.rs @@ -128,7 +128,8 @@ pub struct ContractMessageTranscoder<'a> { impl<'a> ContractMessageTranscoder<'a> { pub fn new(metadata: &'a InkProject) -> Self { let transcoder = TranscoderBuilder::new(metadata.registry()) - .register_custom_type::<::AccountId, _>(env_types::AccountId) + .register_custom_type_transcoder::<::AccountId, _>(env_types::AccountId) + .register_custom_type_decoder::<::Hash, _>(env_types::Hash) .done(); Self { metadata, @@ -346,6 +347,7 @@ mod tests { use scon::Value; use std::str::FromStr; + use crate::cmd::extrinsics::transcode::scon::Hex; use ink_lang as ink; #[ink::contract] @@ -568,7 +570,8 @@ mod tests { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); - let encoded = ([0u32; 32], [1u32; 32]).encode(); + // raw encoded event with event index prefix + let encoded = (0u8, [0u32; 32], [1u32; 32]).encode(); // encode again as a Vec which has a len prefix. let encoded_bytes = encoded.encode(); let _ = transcoder.decode_contract_event(&mut &encoded_bytes[..])?; @@ -576,6 +579,39 @@ mod tests { Ok(()) } + #[test] + fn decode_hash_as_hex_encoded_string() -> Result<()> { + let metadata = generate_metadata(); + let transcoder = ContractMessageTranscoder::new(&metadata); + + let hash = [ + 52u8, 40, 235, 225, 70, 245, 184, 36, 21, 218, 130, 114, 75, 207, 117, 240, + 83, 118, 135, 56, 220, 172, 95, 131, 171, 125, 130, 167, 10, 15, 242, 222, + ]; + // raw encoded event with event index prefix + let encoded = (0u8, hash, [0u32; 32]).encode(); + // encode again as a Vec which has a len prefix. + let encoded_bytes = encoded.encode(); + let decoded = transcoder.decode_contract_event(&mut &encoded_bytes[..])?; + + if let Value::Map(ref map) = decoded { + let name_field = &map[&Value::String("name".into())]; + if let Value::Hex(hex) = name_field { + assert_eq!(&Hex::from_str("0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de")?, hex); + Ok(()) + } else { + Err(anyhow::anyhow!( + "Expected a name field hash encoded as Hex value, was {:?}", + name_field + )) + } + } else { + Err(anyhow::anyhow!( + "Expected a Value::Map for the decoded event" + )) + } + } + #[test] fn decode_contract_message() -> Result<()> { let metadata = generate_metadata(); diff --git a/src/cmd/extrinsics/transcode/transcoder.rs b/src/cmd/extrinsics/transcode/transcoder.rs index c575c442ac74022b30a1d6b6bb4435f0ca6dd3e0..dc07bd7062918391648a9780ba3002058ecdefd0 100644 --- a/src/cmd/extrinsics/transcode/transcoder.rs +++ b/src/cmd/extrinsics/transcode/transcoder.rs @@ -18,7 +18,9 @@ use super::{ decode::Decoder, encode::Encoder, env_types::{ - CustomTypeTranscoder, + self, + CustomTypeDecoder, + CustomTypeEncoder, EnvTypesTranscoder, PathKey, TypesByPath, @@ -76,7 +78,8 @@ impl<'a> Transcoder<'a> { pub struct TranscoderBuilder<'a> { registry: &'a PortableRegistry, types_by_path: TypesByPath, - transcoders: HashMap>, + encoders: HashMap>, + decoders: HashMap>, } impl<'a> TranscoderBuilder<'a> { @@ -89,14 +92,60 @@ impl<'a> TranscoderBuilder<'a> { Self { registry, types_by_path, - transcoders: HashMap::new(), + encoders: HashMap::new(), + decoders: HashMap::new(), } } - pub fn register_custom_type(self, transcoder: U) -> Self + pub fn with_default_custom_type_transcoders(self) -> Self { + self.register_custom_type_transcoder::( + env_types::AccountId, + ) + .register_custom_type_decoder::(env_types::Hash) + } + + pub fn register_custom_type_transcoder(self, transcoder: U) -> Self + where + T: TypeInfo + 'static, + U: CustomTypeEncoder + CustomTypeDecoder + Clone + 'static, + { + self.register_custom_type_encoder::(transcoder.clone()) + .register_custom_type_decoder::(transcoder) + } + + pub fn register_custom_type_encoder(self, encoder: U) -> Self + where + T: TypeInfo + 'static, + U: CustomTypeEncoder + '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.encoders.insert(*type_id, Box::new(encoder)); + log::debug!("Registered custom encoder for type `{:?}`", type_id); + if existing.is_some() { + panic!( + "Attempted to register encoder 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 register_custom_type_decoder(self, encoder: U) -> Self where T: TypeInfo + 'static, - U: CustomTypeTranscoder + 'static, + U: CustomTypeDecoder + 'static, { let mut this = self; @@ -105,11 +154,11 @@ impl<'a> TranscoderBuilder<'a> { match type_id { Some(type_id) => { - let existing = this.transcoders.insert(*type_id, Box::new(transcoder)); - log::debug!("Registered environment type `{:?}`", type_id); + let existing = this.decoders.insert(*type_id, Box::new(encoder)); + log::debug!("Registered custom decoder for type `{:?}`", type_id); if existing.is_some() { panic!( - "Attempted to register transcoder with existing type id {:?}", + "Attempted to register decoder with existing type id {:?}", type_id ); } @@ -123,7 +172,7 @@ impl<'a> TranscoderBuilder<'a> { } pub fn done(self) -> Transcoder<'a> { - let env_types_transcoder = EnvTypesTranscoder::new(self.transcoders); + let env_types_transcoder = EnvTypesTranscoder::new(self.encoders, self.decoders); Transcoder::new(self.registry, env_types_transcoder) } } @@ -141,7 +190,6 @@ mod tests { }, *, }; - use crate::cmd::extrinsics::transcode; use scale::Encode; use scale_info::{ MetaType, @@ -167,9 +215,7 @@ mod tests { { let (registry, ty) = registry_with_type::()?; let transcoder = TranscoderBuilder::new(®istry) - .register_custom_type::( - transcode::env_types::AccountId, - ) + .with_default_custom_type_transcoders() .done(); let value = scon::parse_value(input)?; @@ -714,6 +760,34 @@ mod tests { ) } + #[test] + fn decode_h256_as_hex_string() -> Result<()> { + #[allow(dead_code)] + #[derive(TypeInfo)] + struct S { + hash: sp_core::H256, + } + + transcode_roundtrip::( + 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::>(r#"33"#, Value::UInt(33))?; diff --git a/src/main.rs b/src/main.rs index 92f4ec46e190ab77fb9f4d21916a8f550d548e0d..6f526a9cfd39ae7b20935a585a0e640c64e06881 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,7 +78,7 @@ pub(crate) struct ContractArgs { #[derive(Debug, Default, Clone, PartialEq, Eq)] pub(crate) struct HexData(pub Vec); -impl std::str::FromStr for HexData { +impl FromStr for HexData { type Err = hex::FromHexError; fn from_str(input: &str) -> std::result::Result { @@ -118,7 +118,7 @@ impl Default for OptimizationPasses { } } -impl std::str::FromStr for OptimizationPasses { +impl FromStr for OptimizationPasses { type Err = Error; fn from_str(input: &str) -> std::result::Result { @@ -139,7 +139,7 @@ impl std::str::FromStr for OptimizationPasses { } } -impl From for OptimizationPasses { +impl From for OptimizationPasses { fn from(str: String) -> Self { OptimizationPasses::from_str(&str).expect("conversion failed") } @@ -553,14 +553,14 @@ mod tests { "verbosity": "Quiet" }"#; - let build_result = crate::BuildResult { + let build_result = BuildResult { dest_wasm: Some(PathBuf::from("/path/to/contract.wasm")), - metadata_result: Some(crate::cmd::metadata::MetadataResult { + metadata_result: Some(MetadataResult { dest_metadata: PathBuf::from("/path/to/metadata.json"), dest_bundle: PathBuf::from("/path/to/contract.contract"), }), target_directory: PathBuf::from("/path/to/target"), - optimization_result: Some(crate::OptimizationResult { + optimization_result: Some(OptimizationResult { dest_wasm: PathBuf::from("/path/to/contract.wasm"), original_size: 64.0, optimized_size: 32.0,