Skip to content

Commit

Permalink
Display H256 instances in events as hex encoded string (use-ink#550)
Browse files Browse the repository at this point in the history
* Fix comment

* Remove some unnecessary path prefixes

* Allow custom encoding OR decoding, add tests

* Fmt

* Register custom type decoder for contract event transcoder
  • Loading branch information
ascjones authored May 6, 2022
1 parent cb10dba commit 1dab046
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 45 deletions.
3 changes: 1 addition & 2 deletions src/cmd/extrinsics/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use super::{
runtime_api::api::contracts::events::ContractEmitted,
transcode::{
env_types,
ContractMessageTranscoder,
TranscoderBuilder,
},
Expand Down Expand Up @@ -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::<sp_runtime::AccountId32, _>(env_types::AccountId)
.with_default_custom_type_transcoders()
.done();

const EVENT_FIELD_INDENT: usize = DEFAULT_KEY_COL_WIDTH - 3;
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/extrinsics/transcode/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self>;
}

Expand Down
63 changes: 43 additions & 20 deletions src/cmd/extrinsics/transcode/env_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with cargo-contract. If not, see <http://www.gnu.org/licenses/>.

use super::scon::Value;
use crate::cmd::extrinsics::transcode::scon::Hex;
use anyhow::{
Context,
Result,
Expand Down Expand Up @@ -44,13 +45,17 @@ use std::{
/// Provides custom encoding and decoding for predefined environment types.
#[derive(Default)]
pub struct EnvTypesTranscoder {
transcoders: HashMap<u32, Box<dyn CustomTypeTranscoder>>,
encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
}

impl EnvTypesTranscoder {
/// Construct an `EnvTypesTranscoder` from the given type registry.
pub fn new(transcoders: HashMap<u32, Box<dyn CustomTypeTranscoder>>) -> Self {
Self { transcoders }
pub fn new(
encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
) -> Self {
Self { encoders, decoders }
}

/// If the given type id is for a type with custom encoding, encodes the given value with the
Expand All @@ -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);
Expand All @@ -89,10 +94,10 @@ impl EnvTypesTranscoder {
///
/// - If the custom decoding fails.
pub fn try_decode(&self, type_id: u32, input: &mut &[u8]) -> Result<Option<Value>> {
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 => {
Expand All @@ -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<Vec<u8>>;
fn decode_value(&self, input: &mut &[u8]) -> Result<Value>;
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct PathKey(Vec<String>);

Expand All @@ -135,12 +133,24 @@ impl From<&Path<PortableForm>> for PathKey {

pub type TypesByPath = HashMap<PathKey, u32>;

/// 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<Vec<u8>>;
}

/// 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<Value>;
}

/// 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<Vec<u8>> {
let account_id = match value {
Value::Literal(literal) => {
Expand Down Expand Up @@ -177,9 +187,22 @@ impl CustomTypeTranscoder for AccountId {
};
Ok(account_id.encode())
}
}

impl CustomTypeDecoder for AccountId {
fn decode_value(&self, input: &mut &[u8]) -> Result<Value> {
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<Value> {
let hash = sp_core::H256::decode(input)?;
Ok(Value::Hex(Hex::from_str(&format!("{:?}", hash))?))
}
}
40 changes: 38 additions & 2 deletions src/cmd/extrinsics/transcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<<ink_env::DefaultEnvironment as ink_env::Environment>::AccountId, _>(env_types::AccountId)
.register_custom_type_transcoder::<<ink_env::DefaultEnvironment as ink_env::Environment>::AccountId, _>(env_types::AccountId)
.register_custom_type_decoder::<<ink_env::DefaultEnvironment as ink_env::Environment>::Hash, _>(env_types::Hash)
.done();
Self {
metadata,
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -568,14 +570,48 @@ 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<u8> which has a len prefix.
let encoded_bytes = encoded.encode();
let _ = transcoder.decode_contract_event(&mut &encoded_bytes[..])?;

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<u8> 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();
Expand Down
100 changes: 87 additions & 13 deletions src/cmd/extrinsics/transcode/transcoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ use super::{
decode::Decoder,
encode::Encoder,
env_types::{
CustomTypeTranscoder,
self,
CustomTypeDecoder,
CustomTypeEncoder,
EnvTypesTranscoder,
PathKey,
TypesByPath,
Expand Down Expand Up @@ -76,7 +78,8 @@ impl<'a> Transcoder<'a> {
pub struct TranscoderBuilder<'a> {
registry: &'a PortableRegistry,
types_by_path: TypesByPath,
transcoders: HashMap<u32, Box<dyn CustomTypeTranscoder>>,
encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
}

impl<'a> TranscoderBuilder<'a> {
Expand All @@ -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<T, U>(self, transcoder: U) -> Self
pub fn with_default_custom_type_transcoders(self) -> Self {
self.register_custom_type_transcoder::<sp_runtime::AccountId32, _>(
env_types::AccountId,
)
.register_custom_type_decoder::<sp_core::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));
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<T, U>(self, encoder: U) -> Self
where
T: TypeInfo + 'static,
U: CustomTypeTranscoder + 'static,
U: CustomTypeDecoder + 'static,
{
let mut this = self;

Expand All @@ -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
);
}
Expand All @@ -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)
}
}
Expand All @@ -141,7 +190,6 @@ mod tests {
},
*,
};
use crate::cmd::extrinsics::transcode;
use scale::Encode;
use scale_info::{
MetaType,
Expand All @@ -167,9 +215,7 @@ mod tests {
{
let (registry, ty) = registry_with_type::<T>()?;
let transcoder = TranscoderBuilder::new(&registry)
.register_custom_type::<sp_runtime::AccountId32, _>(
transcode::env_types::AccountId,
)
.with_default_custom_type_transcoders()
.done();

let value = scon::parse_value(input)?;
Expand Down Expand Up @@ -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::<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))?;
Expand Down
Loading

0 comments on commit 1dab046

Please sign in to comment.