Skip to content

Commit

Permalink
Allow hex literals for unsigned integers (#547)
Browse files Browse the repository at this point in the history
* WIP in hex literals

* Convert bytes to seqs

* Fmt

* Warnings

* Add transcode hex literal test

* Fix Hex construction trim leading 0x

* Fix custom account id transcode test

* Fix uint hex literal transcoder test
  • Loading branch information
ascjones authored May 5, 2022
1 parent c174a67 commit 0215f1b
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 56 deletions.
16 changes: 5 additions & 11 deletions src/cmd/extrinsics/transcode/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
32 changes: 27 additions & 5 deletions src/cmd/extrinsics/transcode/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -410,7 +410,7 @@ impl<'a> Encoder<'a> {

fn uint_from_value<T>(value: &Value, expected: &str) -> Result<T>
where
T: TryFrom<u128> + FromStr,
T: TryFrom<u128> + TryFromHex + FromStr,
<T as TryFrom<u128>>::Error: Error + Send + Sync + 'static,
<T as FromStr>::Err: Error + Send + Sync + 'static,
{
Expand All @@ -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 {}",
Expand All @@ -436,7 +440,7 @@ where

fn encode_uint<T, O>(value: &Value, expected: &str, output: &mut O) -> Result<()>
where
T: TryFrom<u128> + FromStr + Encode,
T: TryFrom<u128> + TryFromHex + FromStr + Encode,
<T as TryFrom<u128>>::Error: Error + Send + Sync + 'static,
<T as FromStr>::Err: Error + Send + Sync + 'static,
O: Output,
Expand Down Expand Up @@ -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<Self>;
}

macro_rules! impl_try_from_hex {
( $($ty:ident),* ) => { $(
impl TryFromHex for $ty {
fn try_from_hex(hex: &str) -> Result<Self> {
$ty::from_str_radix(hex, 16).map_err(Into::into)
}
}
)* }
}

impl_try_from_hex!(u8, u16, u32, u64, u128);
9 changes: 6 additions & 3 deletions src/cmd/extrinsics/transcode/env_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
})?
}
_ => {
Expand Down
62 changes: 62 additions & 0 deletions src/cmd/extrinsics/transcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,22 @@ mod tests {
pub fn primitive_vec_args(&self, args: Vec<u32>) {
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;
}
}
}

Expand Down Expand Up @@ -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();
Expand Down
8 changes: 4 additions & 4 deletions src/cmd/extrinsics/transcode/scon/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// along with cargo-contract. If not, see <http://www.gnu.org/licenses/>.

use super::{
Bytes,
Hex,
Map,
Seq,
Tuple,
Expand Down Expand Up @@ -43,7 +43,7 @@ impl<'a> Debug for DisplayValue<'a> {
Value::Tuple(tuple) => <DisplayTuple as Debug>::fmt(&DisplayTuple(tuple), f),
Value::String(string) => <String as Display>::fmt(string, f),
Value::Seq(seq) => <DisplaySeq as Debug>::fmt(&DisplaySeq(seq), f),
Value::Bytes(bytes) => <Bytes as Debug>::fmt(bytes, f),
Value::Hex(hex) => <Hex as Debug>::fmt(hex, f),
Value::Literal(literal) => <String as Display>::fmt(literal, f),
Value::Unit => write!(f, "()"),
}
Expand Down Expand Up @@ -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))
Expand Down
24 changes: 15 additions & 9 deletions src/cmd/extrinsics/transcode/scon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod parse;

use indexmap::IndexMap;

use crate::util;
use std::{
cmp::{
Eq,
Expand All @@ -35,6 +36,7 @@ use std::{
Index,
IndexMut,
},
str::FromStr,
};

pub use self::parse::parse_value;
Expand All @@ -49,7 +51,7 @@ pub enum Value {
Tuple(Tuple),
String(String),
Seq(Seq),
Bytes(Bytes),
Hex(Hex),
Literal(String),
Unit,
}
Expand Down Expand Up @@ -199,20 +201,24 @@ impl Seq {
}

#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Bytes {
pub struct Hex {
s: String,
bytes: Vec<u8>,
}

impl From<Vec<u8>> for Bytes {
fn from(bytes: Vec<u8>) -> Self {
Self { bytes }
impl FromStr for Hex {
type Err = hex::FromHexError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
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<Self, hex::FromHexError> {
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] {
Expand Down
20 changes: 12 additions & 8 deletions src/cmd/extrinsics/transcode/scon/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// along with cargo-contract. If not, see <http://www.gnu.org/licenses/>.

use super::{
Bytes,
Hex,
Map,
Tuple,
Value,
Expand Down Expand Up @@ -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<Value> {
Expand All @@ -64,7 +65,7 @@ pub fn parse_value(input: &str) -> anyhow::Result<Value> {
fn scon_value(input: &str) -> IResult<&str, Value, ErrorTree<&str>> {
ws(alt((
scon_unit,
scon_bytes,
scon_hex,
scon_seq,
scon_tuple,
scon_map,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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(),
),
);
}
}
Loading

0 comments on commit 0215f1b

Please sign in to comment.