From 6ae56974a6845c58e5d27c60654d8387f0fb17ab Mon Sep 17 00:00:00 2001 From: yp945 Date: Thu, 7 Mar 2024 11:45:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20u256=20and=20event?= =?UTF-8?q?=20decode=20and=20encode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: 🧨 y --- Cargo.toml | 4 +- examples/BookExample.json | 66 ++++++---- examples/log_data.rs | 37 ++++++ src/abi.rs | 66 +++++++++- src/event.rs | 256 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/params.rs | 68 +++++++--- src/types.rs | 6 +- src/values.rs | 104 +++++++++++++++- 9 files changed, 559 insertions(+), 50 deletions(-) create mode 100644 examples/log_data.rs create mode 100644 src/event.rs diff --git a/Cargo.toml b/Cargo.toml index 9ad2b3c..6fc4f3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ola-lang-abi" -version = "1.0.5" +version = "1.0.6" edition = "2021" license = "MIT" description = "Ola Smart Contract ABI parsing library" @@ -16,7 +16,7 @@ nom = { version = "7.1.3", default-features = false, features = ["std"] } serde = { version = "1.0.193", default-features = false, features = ["derive"] } serde_json = { version = "1.0.108", default-features = false, features = ["std"] } tiny-keccak = { version = "2.0", default-features = false, features = ["keccak"] } - +mini-goldilocks = { git = "https://github.com/Sin7Y/mini-goldilocks", branch = "main" } [dev-dependencies] pretty_assertions = "1.0" rand = "0.8" diff --git a/examples/BookExample.json b/examples/BookExample.json index a83e777..aa56edf 100644 --- a/examples/BookExample.json +++ b/examples/BookExample.json @@ -5,30 +5,29 @@ "inputs": [ { "name": "id", - "type": "u32", - "internalType": "u32" + "type": "u32" }, { "name": "name", - "type": "string", - "internalType": "string" + "type": "string" } ], "outputs": [ { "name": "", "type": "tuple", - "internalType": "struct BookExample.Book", "components": [ { "name": "book_id", - "type": "u32", - "internalType": "u32" + "type": "u32" }, { "name": "book_name", - "type": "string", - "internalType": "string" + "type": "string" + }, + { + "name": "author", + "type": "string" } ] } @@ -41,17 +40,18 @@ { "name": "_book", "type": "tuple", - "internalType": "struct BookExample.Book", "components": [ { "name": "book_id", - "type": "u32", - "internalType": "u32" + "type": "u32" }, { "name": "book_name", - "type": "string", - "internalType": "string" + "type": "string" + }, + { + "name": "author", + "type": "string" } ] } @@ -59,8 +59,7 @@ "outputs": [ { "name": "", - "type": "string", - "internalType": "string" + "type": "string" } ] }, @@ -71,17 +70,18 @@ { "name": "_book", "type": "tuple", - "internalType": "struct BookExample.Book", "components": [ { "name": "book_id", - "type": "u32", - "internalType": "u32" + "type": "u32" }, { "name": "book_name", - "type": "string", - "internalType": "string" + "type": "string" + }, + { + "name": "author", + "type": "string" } ] } @@ -89,9 +89,29 @@ "outputs": [ { "name": "", - "type": "u32", - "internalType": "u32" + "type": "u32" } ] + }, + { + "name": "BookCreated", + "type": "event", + "inputs": [ + { + "name": "id", + "type": "u32", + "indexed": true + }, + { + "name": "name", + "type": "string", + "indexed": true + }, + { + "name": "author", + "type": "string" + } + ], + "anonymous": false } ] \ No newline at end of file diff --git a/examples/log_data.rs b/examples/log_data.rs new file mode 100644 index 0000000..4f192ab --- /dev/null +++ b/examples/log_data.rs @@ -0,0 +1,37 @@ +use std::fs::File; + +use ola_lang_abi::{Abi, FixedArray4}; + +fn main() { + // Parse ABI JSON file + let abi: Abi = { + let file = File::open("examples/BookExample.json").expect("failed to open ABI file"); + + serde_json::from_reader(file).expect("failed to parse ABI") + }; + + let topics = vec![ + FixedArray4([ + 876009939773297099, + 9423535973325601276, + 68930750687700470, + 16776232995860792718, + ]), + FixedArray4([0, 0, 0, 10]), + FixedArray4([ + 1298737262017568572, + 12445360621592034485, + 13004999764278192581, + 3441866816748036873, + ]), + ]; + + let data = vec![5, 104, 101, 108, 108, 111]; + + // Decode + let (evt, decoded_data) = abi + .decode_log_from_slice(&topics, &data) + .expect("failed decoding log"); + + println!("event: {}\ndata: {:?}", evt.name, decoded_data); +} diff --git a/src/abi.rs b/src/abi.rs index 691001a..7ccbd58 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use serde::{de::Visitor, Deserialize, Serialize}; -use crate::{params::Param, DecodedParams, Value}; +use crate::{params::Param, DecodedParams, Event, FixedArray4, Value}; /// Contract ABI (Abstract Binary Interface). /// @@ -22,6 +22,8 @@ use crate::{params::Param, DecodedParams, Value}; pub struct Abi { /// Contract defined functions. pub functions: Vec, + + pub events: Vec, } impl Abi { @@ -62,6 +64,27 @@ impl Abi { Ok((f, decoded_params)) } + /// Decode event data from slice. + pub fn decode_log_from_slice<'a>( + &'a self, + topics: &[FixedArray4], + data: &[u64], + ) -> Result<(&'a Event, DecodedParams)> { + if topics.is_empty() { + return Err(anyhow!("missing event topic id")); + } + + let e = self + .events + .iter() + .find(|e| e.topic() == topics[0]) + .ok_or_else(|| anyhow!("ABI event not found"))?; + + let decoded_params = e.decode_data_from_slice(topics, data)?; + + Ok((e, decoded_params)) + } + pub fn encode_input_with_signature( &self, signature: &str, @@ -101,6 +124,17 @@ impl Serialize for Abi { name: Some(f.name.clone()), inputs: Some(f.inputs.clone()), outputs: Some(f.outputs.clone()), + anonymous: None, + }); + } + + for e in &self.events { + entries.push(AbiEntry { + type_: String::from("event"), + name: Some(e.name.clone()), + inputs: Some(e.inputs.clone()), + outputs: None, + anonymous: Some(e.anonymous), }); } entries.serialize(serializer) @@ -198,6 +232,8 @@ struct AbiEntry { inputs: Option>, #[serde(skip_serializing_if = "Option::is_none")] outputs: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + anonymous: Option, } struct AbiVisitor; @@ -213,7 +249,10 @@ impl<'de> Visitor<'de> for AbiVisitor { where A: serde::de::SeqAccess<'de>, { - let mut abi = Abi { functions: vec![] }; + let mut abi = Abi { + functions: vec![], + events: vec![], + }; loop { let entry = seq.next_element::()?; @@ -237,6 +276,23 @@ impl<'de> Visitor<'de> for AbiVisitor { outputs, }); } + "event" => { + let inputs = entry.inputs.unwrap_or_default(); + + let name = entry.name.ok_or_else(|| { + serde::de::Error::custom("missing function name".to_string()) + })?; + + let anonymous = entry.anonymous.ok_or_else(|| { + serde::de::Error::custom("missing event anonymous field".to_string()) + })?; + + abi.events.push(Event { + name, + inputs, + anonymous, + }); + } _ => { return Err(serde::de::Error::custom(format!( @@ -334,10 +390,12 @@ mod test { Param { name: "".to_string(), type_: Type::Address, + indexed: None, }, Param { name: "x".to_string(), type_: Type::FixedArray(Box::new(Type::U32), 2), + indexed: None, }, ], outputs: vec![], @@ -370,6 +428,7 @@ mod test { let fun = test_function(); let abi = Abi { functions: vec![fun], + events: vec![], }; let mut params = Value::encode(&input_values); @@ -436,6 +495,7 @@ mod test { Param { name: "n".to_string(), type_: Type::U32, + indexed: None, }, Param { name: "x".to_string(), @@ -443,10 +503,12 @@ mod test { ("a".to_string(), Type::U32), ("b".to_string(), Type::String) ]), + indexed: None, } ], outputs: vec![], }], + events: vec![], } ); } diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..87c50f6 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,256 @@ +use anyhow::{anyhow, Result}; +use mini_goldilocks::poseidon::unsafe_poseidon_bytes_auto_padded; +use std::collections::VecDeque; + +use crate::{DecodedParams, FixedArray4, Param, Type, Value}; + +/// Contract Error Definition +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Error { + /// Error name. + pub name: String, + /// Error inputs. + pub inputs: Vec, +} + +/// Contract event definition. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Event { + /// Event name. + pub name: String, + /// Event inputs. + pub inputs: Vec, + /// Whether the event is anonymous or not. + pub anonymous: bool, +} + +impl Event { + /// Returns the event's signature. + pub fn signature(&self) -> String { + format!( + "{}({})", + self.name, + self.inputs + .iter() + .map(|param| param.type_.to_string()) + .collect::>() + .join(",") + ) + } + + /// Compute the event's topic hash + + pub fn topic(&self) -> FixedArray4 { + FixedArray4(unsafe_poseidon_bytes_auto_padded( + &self.signature().as_bytes(), + )) + } + + /// Decode event params from a log's topics and data. + pub fn decode_data_from_slice( + &self, + mut topics: &[FixedArray4], + data: &[u64], + ) -> Result { + // strip event topic from the topics array + // so that we end up with only the values we + // need to decode + if !self.anonymous { + topics = topics + .get(1..) + .ok_or_else(|| anyhow!("missing event topic"))?; + } + + let mut topics_values = VecDeque::from(topics.to_vec()); + + let mut data_values = VecDeque::from(Value::decode_from_slice( + data, + &self + .inputs + .iter() + .filter(|input| !input.indexed.unwrap_or(false)) + .map(|input| input.type_.clone()) + .collect::>(), + )?); + + let mut decoded = vec![]; + for input in self.inputs.iter().cloned() { + let decoded_value = if input.indexed.unwrap_or(false) { + let val = topics_values + .pop_front() + .ok_or_else(|| anyhow!("insufficient topics entries"))?; + + if Self::is_encoded_to_hash(&input.type_) { + Ok(Value::Hash(val)) + } else if input.type_ == Type::U32 + || input.type_ == Type::Bool + || input.type_ == Type::Field + { + // decode value from topics entry, using the input type + // If the input type is hash or address, take the value directly. + // If the input type is u32, bool, field, take the last value (big-endian). + + Value::decode_from_slice( + &[val.0.get(3).unwrap().clone()], + &[input.type_.clone()], + )? + .first() + .ok_or_else(|| anyhow!("no value decoded from topics entry")) + .map(Clone::clone) + } else { + Value::decode_from_slice(&val.0, &[input.type_.clone()])? + .first() + .ok_or_else(|| anyhow!("no value decoded from topics entry")) + .map(Clone::clone) + } + } else { + data_values + .pop_front() + .ok_or_else(|| anyhow!("insufficient data values")) + }; + + decoded.push((input, decoded_value?)); + } + + Ok(DecodedParams::from(decoded)) + } + + fn is_encoded_to_hash(ty: &Type) -> bool { + matches!( + ty, + Type::FixedArray(_, _) + | Type::U256 + | Type::Array(_) + | Type::Fields + | Type::String + | Type::Tuple(_) + ) + } +} + +#[cfg(test)] +mod test { + + use crate::{Abi, DecodedParams, Type}; + + use super::*; + + use pretty_assertions::assert_eq; + + fn test_event() -> Event { + Event { + name: "Approve".to_string(), + inputs: vec![ + Param { + name: "x".to_string(), + type_: Type::U32, + indexed: Some(true), + }, + Param { + name: "y".to_string(), + type_: Type::String, + indexed: Some(true), + }, + ], + anonymous: false, + } + } + + #[test] + fn test_poseidon_hash() { + let result = unsafe_poseidon_bytes_auto_padded("world".as_bytes()); + assert_eq!( + result, + [ + 1298737262017568572, + 12445360621592034485, + 13004999764278192581, + 3441866816748036873 + ] + ); + } + + #[test] + fn test_signature() { + let evt = test_event(); + + assert_eq!(evt.signature(), "Approve(u32,string)"); + } + + #[test] + fn test_topic() { + let evt = test_event(); + assert_eq!( + evt.topic(), + FixedArray4::from("0xF9C165D12ACC9776822FF3684D676F567781B3609185E4A01ED1EA5138EAF215") + ); + } + + #[test] + fn test_decode_data_from_slice() { + let topics: Vec<_> = vec![ + FixedArray4([ + 13964306673005018703, + 10894260269595496822, + 17848333703059337299, + 3412739309839435658, + ]), + FixedArray4([0, 0, 0, 10]), + FixedArray4([0, 0, 0, 11]), + ]; + + let data = vec![1, 2, 3, 97, 98, 99]; + + let x = Param { + name: "x".to_string(), + type_: Type::U32, + indexed: None, + }; + let y = Param { + name: "y".to_string(), + type_: Type::U32, + indexed: Some(true), + }; + let x1 = Param { + name: "x1".to_string(), + type_: Type::U32, + indexed: None, + }; + let y1 = Param { + name: "y1".to_string(), + type_: Type::U32, + indexed: Some(true), + }; + let s = Param { + name: "s".to_string(), + type_: Type::String, + indexed: None, + }; + + let evt = Event { + name: "Test".to_string(), + inputs: vec![x.clone(), y.clone(), x1.clone(), y1.clone(), s.clone()], + anonymous: false, + }; + + let abi = Abi { + functions: vec![], + events: vec![evt], + }; + + assert_eq!( + abi.decode_log_from_slice(&topics, &data) + .expect("decode_log_from_slice failed"), + ( + &abi.events[0], + DecodedParams::from(vec![ + (x, Value::U32(1)), + (y, Value::U32(10)), + (x1, Value::U32(2)), + (y1, Value::U32(11)), + (s, Value::String("abc".to_string())) + ]) + ) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 492abf4..4ce346c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ //! Ethereum Smart Contracts ABI (abstract binary interface) utility library. mod abi; +mod event; mod params; mod types; mod values; pub use abi::*; +pub use event::*; pub use params::*; pub use types::*; pub use values::*; diff --git a/src/params.rs b/src/params.rs index 5d17cb2..2f13264 100644 --- a/src/params.rs +++ b/src/params.rs @@ -76,6 +76,8 @@ pub struct Param { pub name: String, /// Parameter type. pub type_: Type, + /// Whether it is an indexed parameter (events only). + pub indexed: Option, } impl Param { @@ -99,6 +101,7 @@ impl Param { Param { name: name.clone(), type_: ty.clone(), + indexed: None, } .build_param_entry() }) @@ -108,6 +111,7 @@ impl Param { ParamEntry { name: self.name.clone(), type_: param_type_string(&self.type_), + indexed: self.indexed, components, } } @@ -135,6 +139,7 @@ impl<'a> Deserialize<'a> for Param { Ok(Param { name: entry.name.to_string(), type_: ty, + indexed: entry.indexed, }) } } @@ -154,6 +159,8 @@ struct ParamEntry { #[serde(rename = "type")] pub type_: String, #[serde(skip_serializing_if = "Option::is_none")] + pub indexed: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub components: Option>, } @@ -161,7 +168,7 @@ use nom::{ branch::alt, bytes::complete::tag, character::complete::{char, digit1}, - combinator::{all_consuming, map_res, opt, recognize, verify}, + combinator::{all_consuming, map_res, opt, recognize}, multi::many1, sequence::delimited, IResult, @@ -220,7 +227,8 @@ fn parse_simple_type( alt(( parse_tuple(components.clone()), parse_fields, - parse_uint, + parse_u32, + parse_u256, parse_field, parse_address, parse_hash, @@ -230,8 +238,12 @@ fn parse_simple_type( } } -fn parse_uint(input: &str) -> TypeParseResult<&str, Type> { - map_error(verify(parse_sized("u"), check_int_size)(input).map(|(i, _)| (i, Type::U32))) +fn parse_u32(input: &str) -> TypeParseResult<&str, Type> { + map_error(tag("u32")(input).map(|(i, _)| (i, Type::U32))) +} + +fn parse_u256(input: &str) -> TypeParseResult<&str, Type> { + map_error(tag("u256")(input).map(|(i, _)| (i, Type::U256))) } fn parse_field(input: &str) -> TypeParseResult<&str, Type> { @@ -310,24 +322,10 @@ fn parse_tuple( } } -fn parse_sized(t: &str) -> impl Fn(&str) -> IResult<&str, u64> + '_ { - move |input: &str| { - let (i, _) = tag(t)(input)?; - - parse_integer(i) - } -} - fn parse_integer(input: &str) -> IResult<&str, u64> { map_res(recognize(many1(digit1)), str::parse)(input) } -fn check_int_size(i: &u64) -> bool { - let i = *i; - - i > 0 && i <= 256 && i % 8 == 0 -} - #[cfg(test)] mod test { use super::*; @@ -349,6 +347,30 @@ mod test { Param { name: "a".to_string(), type_: Type::U32, + indexed: None + } + ); + + let param_json = serde_json::to_value(param).expect("param serialized"); + + assert_eq!(v, param_json); + } + + #[test] + fn serde_u256() { + let v = json!({ + "name": "a", + "type": "u256", + }); + + let param: Param = serde_json::from_value(v.clone()).expect("param deserialized"); + + assert_eq!( + param, + Param { + name: "a".to_string(), + type_: Type::U256, + indexed: None } ); @@ -371,6 +393,7 @@ mod test { Param { name: "a".to_string(), type_: Type::Field, + indexed: None } ); @@ -393,6 +416,7 @@ mod test { Param { name: "a".to_string(), type_: Type::Address, + indexed: None } ); @@ -415,6 +439,7 @@ mod test { Param { name: "a".to_string(), type_: Type::Bool, + indexed: None } ); @@ -437,6 +462,7 @@ mod test { Param { name: "a".to_string(), type_: Type::String, + indexed: None } ); @@ -459,6 +485,7 @@ mod test { Param { name: "a".to_string(), type_: Type::Fields, + indexed: None } ); @@ -480,6 +507,7 @@ mod test { Param { name: "a".to_string(), type_: Type::Array(Box::new(Type::U32)), + indexed: None } ); @@ -501,6 +529,7 @@ mod test { Param { name: "a".to_string(), type_: Type::Array(Box::new(Type::Array(Box::new(Type::Address)))), + indexed: None } ); @@ -522,6 +551,7 @@ mod test { Param { name: "a".to_string(), type_: Type::Array(Box::new(Type::FixedArray(Box::new(Type::String), 2))), + indexed: None } ); @@ -541,6 +571,7 @@ mod test { Param { name: "a".to_string(), type_: Type::FixedArray(Box::new(Type::Array(Box::new(Type::String))), 3), + indexed: None } ); @@ -597,6 +628,7 @@ mod test { ]))) ) ]), + indexed: None } ); diff --git a/src/types.rs b/src/types.rs index b5ed025..6bf5052 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,8 +1,10 @@ /// Available ABI types. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Type { - /// Unsigned int type (uint). + /// Unsigned int type uint32. U32, + /// Unsigned int type uint256. + U256, /// Field Field, /// Hash type (address). @@ -28,6 +30,7 @@ impl Type { pub fn is_dynamic(&self) -> bool { match self { Type::U32 => false, + Type::U256 => false, Type::Field => false, Type::Address => false, Type::Hash => false, @@ -45,6 +48,7 @@ impl std::fmt::Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Type::U32 => write!(f, "u32"), + Type::U256 => write!(f, "u256"), Type::Field => write!(f, "field"), Type::Hash => write!(f, "hash"), Type::Address => write!(f, "address"), diff --git a/src/values.rs b/src/values.rs index 0e0b53b..3f22079 100644 --- a/src/values.rs +++ b/src/values.rs @@ -9,8 +9,9 @@ pub struct FixedArray4(pub [u64; 4]); impl From<&str> for FixedArray4 { fn from(s: &str) -> Self { let cleaned = s.trim_start_matches("0x"); + let padded = format!("{:0>64}", cleaned); let mut result = [0; 4]; - for (i, chunk) in cleaned.as_bytes().rchunks(16).rev().enumerate() { + for (i, chunk) in padded.as_bytes().rchunks(16).rev().enumerate() { let chunk_str = std::str::from_utf8(chunk).expect("Invalid UTF-8"); result[i] = u64::from_str_radix(chunk_str, 16).expect("Failed to parse hex string") as u64; @@ -40,11 +41,51 @@ impl fmt::Display for FixedArray4 { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FixedArray8(pub [u64; 8]); + +impl From<&str> for FixedArray8 { + fn from(s: &str) -> Self { + let cleaned = s.trim_start_matches("0x"); + let padded = format!("{:0>64}", cleaned); + let mut result = [0; 8]; + for (i, chunk) in padded.as_bytes().rchunks(8).rev().enumerate() { + let chunk_str = std::str::from_utf8(chunk).expect("Invalid UTF-8"); + result[i] = + u64::from_str_radix(chunk_str, 16).expect("Failed to parse hex string") as u64; + } + FixedArray8(result) + } +} + +impl FixedArray8 { + pub fn to_hex_string(&self) -> String { + let mut hex_string = String::with_capacity(66); // 64 for data + 2 for "0x" prefix + hex_string.push_str("0x"); + for &value in self.0.iter() { + hex_string.push_str(&format!("{:08x}", value as u32)); + } + hex_string + } +} + +impl fmt::Display for FixedArray8 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x")?; + for &value in self.0.iter() { + write!(f, "{:08x}", value as u32)?; + } + Ok(()) + } +} + /// ABI decoded value. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Value { - /// Unsigned int value (uint). + /// Unsigned int value (uint32). U32(u64), + /// Unsigned int value (uint256). + U256(FixedArray8), /// Signed int value (int). Field(u64), /// Address value (address). @@ -91,6 +132,14 @@ impl Value { buf.resize(start + 1, *i); } + Value::U256(num) => { + let start = buf.len(); + buf.resize(start + 8, 0); + + // big-endian, as if it were a uint160. + buf[start..(start + 8)].copy_from_slice(&num.0); + } + Value::Field(i) => { let start = buf.len(); buf.resize(start + 1, *i); @@ -180,6 +229,7 @@ impl Value { pub fn type_of(&self) -> Type { match self { Value::U32(_) => Type::U32, + Value::U256(_) => Type::U256, Value::Field(_) => Type::Field, Value::Address(_) => Type::Address, Value::Hash(_) => Type::Hash, @@ -212,6 +262,18 @@ impl Value { Ok((Value::U32(u32_value), 1)) } + Type::U256 => { + let at = base_addr + at; + let slice = bs + .get(at..(at + 8)) + .ok_or_else(|| anyhow!("reached end of input while decoding {:?}", ty))?; + + let mut u256_value = [0u64; 8]; + u256_value.copy_from_slice(slice); + + Ok((Value::U256(FixedArray8(u256_value)), 8)) + } + Type::Field => { let at = base_addr + at; let slice = bs @@ -353,6 +415,21 @@ mod test { assert_eq!(v, vec![Value::U32(100), Value::U32(200), Value::U32(300)]); } + #[test] + fn decode_u256() { + let bs = FixedArray8::from("0x0a"); + + let v = Value::decode_from_slice(&bs.0, &[Type::U256]).expect("decode_from_slice failed"); + + assert_eq!(v, vec![Value::U256(FixedArray8([0, 0, 0, 0, 0, 0, 0, 10]))]); + let bs = + FixedArray8::from("0x000000010000000200000003000000040000000500000006000000070000000a"); + + let v = Value::decode_from_slice(&bs.0, &[Type::U256]).expect("decode_from_slice failed"); + + assert_eq!(v, vec![Value::U256(FixedArray8([1, 2, 3, 4, 5, 6, 7, 10]))]); + } + #[test] fn decode_field() { let bs = vec![100, 200, 300]; @@ -367,6 +444,12 @@ mod test { } #[test] fn decode_address() { + let bs = FixedArray4::from("0x000000020000000000000003"); + + let v = + Value::decode_from_slice(&bs.0, &[Type::Address]).expect("decode_from_slice failed"); + + assert_eq!(v, vec![Value::Address(FixedArray4([0, 0, 2, 3]))]); let bs = FixedArray4::from("0x0000000000000000000000000000000100000000000000020000000000000003"); @@ -605,7 +688,7 @@ mod test { } #[test] - fn encode_uint() { + fn encode_u32() { let value = Value::U32(12); let expected_bytes = vec![12]; @@ -613,14 +696,27 @@ mod test { assert_eq!(Value::encode(&[value]), expected_bytes); } + #[test] + fn encode_u256() { + let u256 = [1, 2, 3, 4, 5, 6, 7, 8]; + let value = Value::U256(FixedArray8(u256)); + + let expected_bytes = vec![1, 2, 3, 4, 5, 6, 7, 8]; + assert_eq!(Value::encode(&[value]), expected_bytes); + let expected_hex = "0x0000000100000002000000030000000400000005000000060000000700000008"; + assert_eq!(FixedArray8(u256).to_hex_string(), expected_hex); + } + #[test] fn encode_address() { let addr = [1, 2, 3, 4]; let value = Value::Address(FixedArray4(addr)); let expected_bytes = vec![1, 2, 3, 4]; - assert_eq!(Value::encode(&[value]), expected_bytes); + + let expected_hex = "0x0000000000000001000000000000000200000000000000030000000000000004"; + assert_eq!(FixedArray4(addr).to_hex_string(), expected_hex); } #[test]