diff --git a/src/cmd/extrinsics/transcode/decode.rs b/src/cmd/extrinsics/transcode/decode.rs index f22f061c8bec4f36b39645feaea4f657146a43c7..1654a74488c2b6c33907adb0f18c94445e297595 100644 --- a/src/cmd/extrinsics/transcode/decode.rs +++ b/src/cmd/extrinsics/transcode/decode.rs @@ -91,18 +91,12 @@ impl<'a> Decoder<'a> { anyhow::anyhow!("Failed to find type with id '{}'", type_id) })?; - if *ty.type_def() == TypeDef::Primitive(TypeDefPrimitive::U8) { - let mut bytes = vec![0u8; len]; - input.read(&mut bytes)?; - Ok(Value::Bytes(bytes.into())) - } else { - let mut elems = Vec::new(); - while elems.len() < len as usize { - let elem = self.decode_type(type_id, ty, input)?; - elems.push(elem) - } - Ok(Value::Seq(elems.into())) + let mut elems = Vec::new(); + while elems.len() < len as usize { + let elem = self.decode_type(type_id, ty, input)?; + elems.push(elem) } + Ok(Value::Seq(elems.into())) } fn decode_type( diff --git a/src/cmd/extrinsics/transcode/encode.rs b/src/cmd/extrinsics/transcode/encode.rs index aa7d5c22273b8e3bc30b50ddf4f280373f282f66..86fdc833fac9ab21f8d367f73b76cb6f337031e3 100644 --- a/src/cmd/extrinsics/transcode/encode.rs +++ b/src/cmd/extrinsics/transcode/encode.rs @@ -248,11 +248,11 @@ impl<'a> Encoder<'a> { self.encode(ty.id(), value, output)?; } } - Value::Bytes(bytes) => { + Value::Hex(hex) => { if encode_len { - Compact(bytes.bytes().len() as u32).encode_to(output); + Compact(hex.bytes().len() as u32).encode_to(output); } - for byte in bytes.bytes() { + for byte in hex.bytes() { output.push_byte(*byte); } } @@ -410,7 +410,7 @@ impl<'a> Encoder<'a> { fn uint_from_value(value: &Value, expected: &str) -> Result where - T: TryFrom + FromStr, + T: TryFrom + TryFromHex + FromStr, >::Error: Error + Send + Sync + 'static, ::Err: Error + Send + Sync + 'static, { @@ -424,6 +424,10 @@ where let uint = T::from_str(&sanitized)?; Ok(uint) } + Value::Hex(hex) => { + let uint = T::try_from_hex(hex.as_str())?; + Ok(uint) + } _ => { Err(anyhow::anyhow!( "Expected a {} or a String value, got {}", @@ -436,7 +440,7 @@ where fn encode_uint(value: &Value, expected: &str, output: &mut O) -> Result<()> where - T: TryFrom + FromStr + Encode, + T: TryFrom + TryFromHex + FromStr + Encode, >::Error: Error + Send + Sync + 'static, ::Err: Error + Send + Sync + 'static, O: Output, @@ -479,3 +483,21 @@ where int.encode_to(output); Ok(()) } + +/// Attempt to instantiate a type from its little-endian bytes representation. +pub trait TryFromHex: Sized { + /// Create a new instance from the little-endian bytes representation. + fn try_from_hex(hex: &str) -> Result; +} + +macro_rules! impl_try_from_hex { + ( $($ty:ident),* ) => { $( + impl TryFromHex for $ty { + fn try_from_hex(hex: &str) -> Result { + $ty::from_str_radix(hex, 16).map_err(Into::into) + } + } + )* } +} + +impl_try_from_hex!(u8, u16, u32, u64, u128); diff --git a/src/cmd/extrinsics/transcode/env_types.rs b/src/cmd/extrinsics/transcode/env_types.rs index 7e526569246d8b1fd1f2fe9fc8c83983f4811d03..7ecaad99a845fb5b4f422525891e9f385b7620f3 100644 --- a/src/cmd/extrinsics/transcode/env_types.rs +++ b/src/cmd/extrinsics/transcode/env_types.rs @@ -161,9 +161,12 @@ impl CustomTypeTranscoder for AccountId { ) })? } - Value::Bytes(bytes) => { - AccountId32::try_from(bytes.bytes()).map_err(|_| { - anyhow::anyhow!("Error converting bytes `{:?}` to AccountId", bytes) + Value::Hex(hex) => { + AccountId32::try_from(hex.bytes()).map_err(|_| { + anyhow::anyhow!( + "Error converting hex bytes `{:?}` to AccountId", + hex.bytes() + ) })? } _ => { diff --git a/src/cmd/extrinsics/transcode/mod.rs b/src/cmd/extrinsics/transcode/mod.rs index daf95b16cc173511e1c030e20510aa34dacabce6..bac8be4260985113d8af744690589d14b38cc28f 100644 --- a/src/cmd/extrinsics/transcode/mod.rs +++ b/src/cmd/extrinsics/transcode/mod.rs @@ -398,6 +398,22 @@ mod tests { pub fn primitive_vec_args(&self, args: Vec) { let _ = args; } + + #[ink(message)] + pub fn uint_args( + &self, + _u8: u8, + _u16: u16, + _u32: u32, + _u64: u64, + _u128: u128, + ) { + } + + #[ink(message)] + pub fn uint_array_args(&self, arr: [u8; 4]) { + let _ = arr; + } } } @@ -489,6 +505,52 @@ mod tests { Ok(()) } + #[test] + fn encode_uint_hex_literals() -> Result<()> { + let metadata = generate_metadata(); + let transcoder = ContractMessageTranscoder::new(&metadata); + + let encoded = transcoder.encode( + "uint_args", + &[ + "0x00", + "0xDEAD", + "0xDEADBEEF", + "0xDEADBEEF12345678", + "0xDEADBEEF0123456789ABCDEF01234567", + ], + )?; + + // encoded args follow the 4 byte selector + let encoded_args = &encoded[4..]; + + let expected = ( + 0x00u8, + 0xDEADu16, + 0xDEADBEEFu32, + 0xDEADBEEF12345678u64, + 0xDEADBEEF0123456789ABCDEF01234567u128, + ); + assert_eq!(expected.encode(), encoded_args); + Ok(()) + } + + #[test] + fn encode_uint_arr_hex_literals() -> Result<()> { + let metadata = generate_metadata(); + let transcoder = ContractMessageTranscoder::new(&metadata); + + let encoded = + transcoder.encode("uint_array_args", &["[0xDE, 0xAD, 0xBE, 0xEF]"])?; + + // encoded args follow the 4 byte selector + let encoded_args = &encoded[4..]; + + let expected: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; + assert_eq!(expected.encode(), encoded_args); + Ok(()) + } + #[test] fn decode_primitive_return() -> Result<()> { let metadata = generate_metadata(); diff --git a/src/cmd/extrinsics/transcode/scon/display.rs b/src/cmd/extrinsics/transcode/scon/display.rs index 8ccaf8e39ef4a55fe19bc9e3463516ee9fbf15b3..499634bf4b91ac6bed342dbc76368d12de3678a7 100644 --- a/src/cmd/extrinsics/transcode/scon/display.rs +++ b/src/cmd/extrinsics/transcode/scon/display.rs @@ -15,7 +15,7 @@ // along with cargo-contract. If not, see . use super::{ - Bytes, + Hex, Map, Seq, Tuple, @@ -43,7 +43,7 @@ impl<'a> Debug for DisplayValue<'a> { Value::Tuple(tuple) => ::fmt(&DisplayTuple(tuple), f), Value::String(string) => ::fmt(string, f), Value::Seq(seq) => ::fmt(&DisplaySeq(seq), f), - Value::Bytes(bytes) => ::fmt(bytes, f), + Value::Hex(hex) => ::fmt(hex, f), Value::Literal(literal) => ::fmt(literal, f), Value::Unit => write!(f, "()"), } @@ -107,13 +107,13 @@ impl<'a> Debug for DisplaySeq<'a> { } } -impl Debug for Bytes { +impl Debug for Hex { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{:#x}", self) } } -impl LowerHex for Bytes { +impl LowerHex for Hex { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if f.alternate() { write!(f, "0x{}", hex::encode(&self.bytes)) diff --git a/src/cmd/extrinsics/transcode/scon/mod.rs b/src/cmd/extrinsics/transcode/scon/mod.rs index 66b45ca258cc9018f4bc986c0d99b3b45e1b0bff..85882c52886958a49265ece48158cc6cf9f64250 100644 --- a/src/cmd/extrinsics/transcode/scon/mod.rs +++ b/src/cmd/extrinsics/transcode/scon/mod.rs @@ -21,6 +21,7 @@ mod parse; use indexmap::IndexMap; +use crate::util; use std::{ cmp::{ Eq, @@ -35,6 +36,7 @@ use std::{ Index, IndexMut, }, + str::FromStr, }; pub use self::parse::parse_value; @@ -49,7 +51,7 @@ pub enum Value { Tuple(Tuple), String(String), Seq(Seq), - Bytes(Bytes), + Hex(Hex), Literal(String), Unit, } @@ -199,20 +201,24 @@ impl Seq { } #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Bytes { +pub struct Hex { + s: String, bytes: Vec, } -impl From> for Bytes { - fn from(bytes: Vec) -> Self { - Self { bytes } +impl FromStr for Hex { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + let s = s.trim_start_matches("0x").to_string(); + let bytes = util::decode_hex(&s)?; + Ok(Self { s, bytes }) } } -impl Bytes { - pub fn from_hex_string(s: &str) -> Result { - let bytes = crate::util::decode_hex(s)?; - Ok(Self { bytes }) +impl Hex { + pub fn as_str(&self) -> &str { + &self.s } pub fn bytes(&self) -> &[u8] { diff --git a/src/cmd/extrinsics/transcode/scon/parse.rs b/src/cmd/extrinsics/transcode/scon/parse.rs index 2e6ae484258d25ddb6e2d83f6a2ba909b3ae236a..1df831667f205bbfaeed56b7738367fe765e3d8b 100644 --- a/src/cmd/extrinsics/transcode/scon/parse.rs +++ b/src/cmd/extrinsics/transcode/scon/parse.rs @@ -15,7 +15,7 @@ // along with cargo-contract. If not, see . use super::{ - Bytes, + Hex, Map, Tuple, Value, @@ -53,6 +53,7 @@ use nom_supreme::{ error::ErrorTree, ParserExt, }; +use std::str::FromStr as _; /// Attempt to parse a SCON value pub fn parse_value(input: &str) -> anyhow::Result { @@ -64,7 +65,7 @@ pub fn parse_value(input: &str) -> anyhow::Result { fn scon_value(input: &str) -> IResult<&str, Value, ErrorTree<&str>> { ws(alt(( scon_unit, - scon_bytes, + scon_hex, scon_seq, scon_tuple, scon_map, @@ -221,12 +222,12 @@ fn scon_map(input: &str) -> IResult<&str, Value, ErrorTree<&str>> { .parse(input) } -fn scon_bytes(input: &str) -> IResult<&str, Value, ErrorTree<&str>> { +fn scon_hex(input: &str) -> IResult<&str, Value, ErrorTree<&str>> { tag("0x") .precedes(hex_digit1) .map_res::<_, _, hex::FromHexError>(|byte_str| { - let bytes = Bytes::from_hex_string(byte_str)?; - Ok(Value::Bytes(bytes)) + let hex = Hex::from_str(byte_str)?; + Ok(Value::Hex(hex)) }) .parse(input) } @@ -602,10 +603,13 @@ mod tests { #[test] fn test_bytes() { - assert_scon_value(r#"0x0000"#, Value::Bytes(vec![0u8; 2].into())); + assert_scon_value("0x0000", Value::Hex(Hex::from_str("0x0000").unwrap())); assert_scon_value( - r#"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"#, - Value::Bytes(vec![255u8; 23].into()), + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + Value::Hex( + Hex::from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + .unwrap(), + ), ); } } diff --git a/src/cmd/extrinsics/transcode/transcoder.rs b/src/cmd/extrinsics/transcode/transcoder.rs index 591266de6542103bdb7c8de979ded1a3689e6d8f..c575c442ac74022b30a1d6b6bb4435f0ca6dd3e0 100644 --- a/src/cmd/extrinsics/transcode/transcoder.rs +++ b/src/cmd/extrinsics/transcode/transcoder.rs @@ -133,6 +133,7 @@ mod tests { use super::{ super::scon::{ self, + Hex, Map, Seq, Tuple, @@ -140,16 +141,14 @@ mod tests { }, *, }; - use crate::cmd::extrinsics::{ - transcode, - transcode::scon::Bytes, - }; + 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 @@ -264,15 +263,31 @@ mod tests { fn transcode_byte_array() -> Result<()> { transcode_roundtrip::<[u8; 2]>( r#"0x0000"#, - Value::Bytes(vec![0x00, 0x00].into()), + Value::Seq(vec![Value::UInt(0x00), Value::UInt(0x00)].into()), )?; transcode_roundtrip::<[u8; 4]>( r#"0xDEADBEEF"#, - Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into()), + Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + ), )?; transcode_roundtrip::<[u8; 4]>( r#"0xdeadbeef"#, - Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into()), + Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + ), ) } @@ -321,7 +336,15 @@ mod tests { vec![ Value::UInt(1), Value::String("ink!".to_string()), - Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into()), + Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + ), ], )), ) @@ -350,7 +373,15 @@ mod tests { ), ( Value::String("c".to_string()), - Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into()), + Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + ), ), ( Value::String("d".to_string()), @@ -364,7 +395,15 @@ mod tests { ), ( Value::String("c".to_string()), - Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into()), + Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + ), ), ( Value::String("d".to_string()), @@ -439,7 +478,15 @@ mod tests { ), ( Value::String("c".to_string()), - Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into()), + Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + ), ), ] .into_iter() @@ -461,7 +508,15 @@ mod tests { vec![ Value::UInt(1), Value::String("ink!".to_string()), - Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into()), + Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + ), ], )), ) @@ -477,7 +532,15 @@ mod tests { r#"0xDEADBEEF"#, Value::Tuple(Tuple::new( Some("S"), - vec![Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into())], + vec![Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + )], )), ) } @@ -488,7 +551,15 @@ mod tests { r#"0xDEADBEEF"#, Value::Tuple(Tuple::new( None, - vec![Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF].into())], + vec![Value::Seq( + vec![ + Value::UInt(0xDE), + Value::UInt(0xAD), + Value::UInt(0xBE), + Value::UInt(0xEF), + ] + .into(), + )], )), ) } @@ -611,6 +682,12 @@ mod tests { #[test] fn transcode_account_id_custom_ss58_encoding_seq() -> Result<()> { + let hex_to_bytes = |hex: &str| -> Result { + 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::>( r#"[ 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY, @@ -621,13 +698,13 @@ mod tests { Value::Tuple( Tuple::new( Some("AccountId32"), - vec![Value::Bytes(Bytes::from_hex_string("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d").unwrap())] + vec![hex_to_bytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d")?] ) ), Value::Tuple( Tuple::new( Some("AccountId32"), - vec![Value::Bytes(Bytes::from_hex_string("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48").unwrap())] + vec![hex_to_bytes("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")?] ) ) ] @@ -678,4 +755,26 @@ mod tests { )), ) } + + #[test] + fn transcode_hex_literal_uints() -> Result<()> { + #[derive(scale::Encode, TypeInfo)] + struct S(u8, u16, u32, u64, u128); + + transcode_roundtrip::( + 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(), + )), + ) + } }