Unverified Commit 1dab046b authored by Andrew Jones's avatar Andrew Jones Committed by GitHub
Browse files

Display `H256` instances in events as hex encoded string (#550)

* Fix comment

* Remove some unnecessary path prefixes

* Allow custom encoding OR decoding, add tests

* Fmt

* Register custom type decoder for contract event transcoder
parent cb10dba5
Pipeline #192587 failed with stages
in 2 minutes
......@@ -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::<sp_runtime::AccountId32, _>(env_types::AccountId)
.with_default_custom_type_transcoders()
.done();
const EVENT_FIELD_INDENT: usize = DEFAULT_KEY_COL_WIDTH - 3;
......
......@@ -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>;
}
......
......@@ -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,
......@@ -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
......@@ -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<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 => {
......@@ -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>);
......@@ -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) => {
......@@ -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))?))
}
}
......@@ -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,
......@@ -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<u8> 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<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();
......
......@@ -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<u32, Box<dyn CustomTypeTranscoder>>,
encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
}
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<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;
......@@ -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::<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)?;
......@@ -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))?;
......
......@@ -78,7 +78,7 @@ pub(crate) struct ContractArgs {
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct HexData(pub Vec<u8>);
impl std::str::FromStr for HexData {
impl FromStr for HexData {
type Err = hex::FromHexError;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
......@@ -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<Self, Self::Err> {
......@@ -139,7 +139,7 @@ impl std::str::FromStr for OptimizationPasses {
}
}
impl From<std::string::String> for OptimizationPasses {
impl From<String> 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,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment