Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Ethereum]: Add support for abi.encodePacked() | Resolves #3679 #3862

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
256 changes: 254 additions & 2 deletions rust/tw_evm/src/abi/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,77 @@ fn fixed_bytes_append(result: &mut Vec<H256>, bytes: &[u8]) {
}
}

pub fn encode_packed_tokens(tokens: &[Token]) -> Data {
let mut encoded = Vec::new();

for token in tokens {
match token {
Token::Address(address) => {
encoded.extend_from_slice(address.as_slice());
},
Token::Bytes(bytes) => {
encoded.extend_from_slice(bytes);
},
Token::String(s) => {
encoded.extend_from_slice(s.as_bytes());
},
Token::FixedBytes(bytes) => {
encoded.extend_from_slice(bytes.as_ref());
},
Token::Int { int, bits } => {
let buf = int.to_big_endian().as_slice().to_vec();
let bits_value = bits.get();
let padding_len = (bits_value / 8) - buf.len();
let padding = vec![0; padding_len];
encoded.extend_from_slice(&padding);
encoded.extend_from_slice(&buf);
},
Token::Uint { uint, bits } => {
let buf = uint.to_big_endian().as_slice().to_vec();
let bits_value = bits.get();
let padding_len = (bits_value / 8) - buf.len();
let padding = vec![0; padding_len];
encoded.extend_from_slice(&padding);
encoded.extend_from_slice(&buf);
},
Token::Bool(b) => {
encoded.push(*b as u8);
},
Token::Array { arr, .. } => {
for element in arr.iter() {
let element_data = encode_packed_tokens(&[element.clone()]);
encoded.extend_from_slice(&element_data);
}
},
Token::FixedArray { arr, .. } => {
for element in arr.iter() {
let element_data = encode_packed_tokens(&[element.clone()]);
encoded.extend_from_slice(&element_data);
}
},
Token::Tuple { params } => {
for element in params.iter() {
let element_data = encode_packed_tokens(&[element.value.clone()]);
encoded.extend_from_slice(&element_data);
}
},
}
}
encoded
}

#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
use crate::abi::non_empty_array::{NonEmptyArray, NonEmptyBytes};
use crate::abi::param::Param;
use crate::abi::param_token::NamedToken;
use crate::abi::param_type::constructor::TypeConstructor;
use crate::abi::param_type::ParamType;
use tw_encoding::hex::DecodeHex;
use tw_encoding::hex::{DecodeHex};
use tw_number::{I256, U256};
use crate::abi::uint::UintBits;

#[test]
fn encode_address() {
Expand Down Expand Up @@ -1083,4 +1144,195 @@ mod tests {
.unwrap();
assert_eq!(encoded, expected);
}
}

#[test]
fn encode_packed_address() {
let address = Token::Address("0x1111111111111111111111111111111111111111".into());
let encoded = encode_packed_tokens(&[address]);
let expected = "1111111111111111111111111111111111111111".decode_hex().unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_bytes() {
let bytes = Token::Bytes(vec![0x12, 0x34]);
let encoded = encode_packed_tokens(&[bytes]);
let expected = "1234".decode_hex().unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_string() {
let s = Token::String("Hello, world!".to_owned());
let encoded = encode_packed_tokens(&[s]);
let expected = "48656c6c6f2c20776f726c6421".decode_hex().unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_int() {
let int = Token::Int {
int: I256::from(-1),
bits: UintBits::new(256).unwrap(),
};
let encoded = encode_packed_tokens(&[int]);
let expected = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".decode_hex().unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_bool() {
let b = Token::Bool(true);
let encoded = encode_packed_tokens(&[b]);
let expected = "01".decode_hex().unwrap();
assert_eq!(encoded, expected);

let b = Token::Bool(false);
let encoded = encode_packed_tokens(&[b]);
let expected = "00".decode_hex().unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_uint() {
let uint = Token::Uint {
uint: U256::from(3_u64),
bits: UintBits::new(256).unwrap(),
};
let encoded = encode_packed_tokens(&[uint]);
let expected = "03".decode_hex().unwrap();
theghostmac marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_fixed_bytes() {
let bytes = Token::FixedBytes(NonEmptyBytes::new(vec![0x12, 0x34]).unwrap());
let encoded = encode_packed_tokens(&[bytes]);
let expected = "1234".decode_hex().unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_mixed() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. does this look good enough?:

 #[test]
    fn encode_packed_custom_case() {
        // bytes32 stateRoots
        let state_roots = NonEmptyBytes::from_slice(
            "3a53dc4890241dbe03e486e785761577d1c369548f6b09aa38017828dcdf5c27"
                .decode_hex()
                .unwrap()
                .as_slice(),
        ).unwrap();

        // uint256[2] calldata signatures
        let signatures = Token::Array {
            arr: vec![
                Token::Uint {
                    uint: U256::from_str(
                        "3402053321874964899321528271743396700217057178612185975187363512030360053932"
                    ).unwrap(),
                    bits: UintBits::new(256).unwrap(),
                },
                Token::Uint {
                    uint: U256::from_str(
                        "1235124644010117237054094970590473241953434069965207718920579820322861537001"
                    ).unwrap(),
                    bits: UintBits::new(256).unwrap(),
                },
            ],
            kind: ParamType::Uint {
                bits: UintBits::new(256).unwrap(),
            },
        };

        // uint256 feeReceivers
        let fee_receivers = Token::Uint {
            uint: U256::zero(),
            bits: UintBits::new(256).unwrap(),
        };

        // bytes calldata txss
        let txss = Token::Bytes(
            "000000000000000100010000"
                .decode_hex()
                .unwrap(),
        );

        let tokens = vec![Token::FixedBytes(state_roots), signatures, fee_receivers, txss];
        let encoded = encode_packed_tokens(&tokens);

        let expected = "3a53dc4890241dbe03e486e785761577d1c369548f6b09aa38017828dcdf5c2707857e73108d077c5b7ef89540d6493f70d940f1763a9d34c9d98418a39d28ac02bb0e4743a7d0586711ee3dd6311256579ab7abcd53c9c76f040bfde4d6d6e90000000000000000000000000000000000000000000000000000000000000000000000000000000100010000"
            .decode_hex()
            .unwrap();

        assert_eq!(encoded, expected);
    }
    ```

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code can be cleaned up a bit if you move strings to a separate variable. For example,

let uint1 = "3402053321874964899321528271743396700217057178612185975187363512030360053932";

Token::Uint {
    uint: U256::from_str(uint1).unwrap(),
    bits: UintBits::new(256).unwrap(),
},

Anyway, does this test work?

let tokens = vec![
Token::Int { int: I256::from(-1), bits: UintBits::new(256).unwrap() },
Token::FixedBytes(NonEmptyBytes::new(vec![0x42]).unwrap()),
Token::Uint { uint: U256::from(3_u64), bits: UintBits::new(256).unwrap() },
Token::String("Hello, world!".to_owned()),
];
let encoded = encode_packed_tokens(&tokens);
let expected = concat!(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // -1 padded
"42", // 0x42
"0000000000000000000000000000000000000000000000000000000000000003", // 3 padded
"48656c6c6f2c20776f726c6421" // "Hello, world!" in ASCII
).decode_hex().unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_array() {
let array = Token::Array {
arr: vec![
Token::Uint {
uint: U256::from(1_u64),
bits: UintBits::new(256).unwrap(),
},
Token::Uint {
uint: U256::from(2_u64),
bits: UintBits::new(256).unwrap(),
},
Token::Uint {
uint: U256::from(3_u64),
bits: UintBits::new(256).unwrap(),
},
],
kind: ParamType::Uint { bits: UintBits::new(256).unwrap() },
};
let encoded = encode_packed_tokens(&[array]);

let expected = tw_encoding::hex::decode("010203").unwrap();

assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_uint_256bits() {
let uint = Token::Uint {
uint: U256::from(3_u64),
bits: UintBits::new(256).unwrap(),
};
let encoded = encode_packed_tokens(&[uint]);
let expected = "0000000000000000000000000000000000000000000000000000000000000003"
.decode_hex()
.unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_int_256bits() {
let int = Token::Int {
int: I256::from(3),
bits: UintBits::new(256).unwrap(),
};
let encoded = encode_packed_tokens(&[int]);
let expected = "0000000000000000000000000000000000000000000000000000000000000003"
.decode_hex()
.unwrap();
assert_eq!(encoded, expected);
}

#[test]
fn encode_packed_custom_case() {
// bytes32 stateRoots
let state_roots = NonEmptyBytes::from_slice(
"3a53dc4890241dbe03e486e785761577d1c369548f6b09aa38017828dcdf5c27"
.decode_hex()
.unwrap()
.as_slice(),
).unwrap();

// uint256[2] calldata signatures
let signatures = Token::Array {
arr: vec![
Token::Uint {
uint: U256::from_str(
"3402053321874964899321528271743396700217057178612185975187363512030360053932"
).unwrap(),
bits: UintBits::new(256).unwrap(),
},
Token::Uint {
uint: U256::from_str(
"1235124644010117237054094970590473241953434069965207718920579820322861537001"
).unwrap(),
bits: UintBits::new(256).unwrap(),
},
],
kind: ParamType::Uint {
bits: UintBits::new(256).unwrap(),
},
};

// uint256 feeReceivers
let fee_receivers = Token::Uint {
uint: U256::zero(),
bits: UintBits::new(256).unwrap(),
};

// bytes calldata txss
let txss = Token::Bytes(
"000000000000000100010000"
.decode_hex()
.unwrap(),
);

let tokens = vec![Token::FixedBytes(state_roots), signatures, fee_receivers, txss];
let encoded = encode_packed_tokens(&tokens);

let expected = "3a53dc4890241dbe03e486e785761577d1c369548f6b09aa38017828dcdf5c2707857e73108d077c5b7ef89540d6493f70d940f1763a9d34c9d98418a39d28ac02bb0e4743a7d0586711ee3dd6311256579ab7abcd53c9c76f040bfde4d6d6e90000000000000000000000000000000000000000000000000000000000000000000000000000000100010000"
.decode_hex()
.unwrap();

assert_eq!(encoded, expected);
}
}
21 changes: 21 additions & 0 deletions rust/tw_evm/src/abi/non_empty_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@ impl<T> NonEmptyArray<T> {
pub fn into_vec(self) -> Vec<T> {
self.0
}

pub fn get(&self) -> &Vec<T> {
&self.0
}

pub fn set(&mut self, value: Vec<T>) {
if value.is_empty() {
panic!("`NonEmptyArray` must have at least one element");
}
self.0 = value;
}
theghostmac marked this conversation as resolved.
Show resolved Hide resolved

pub fn from_slice(slice: &[T]) -> AbiResult<Self>
where
T: Clone,
{
if slice.is_empty() {
return AbiError::err(AbiErrorKind::Error_empty_type);
}
Ok(NonEmptyArray(slice.to_vec()))
}
theghostmac marked this conversation as resolved.
Show resolved Hide resolved
}

impl<T> IntoIterator for NonEmptyArray<T> {
Expand Down
6 changes: 6 additions & 0 deletions rust/tw_evm/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ pub struct Address {
bytes: H160,
}

impl AsRef<[u8]> for Address {
fn as_ref(&self) -> &[u8] {
self.bytes.as_slice()
}
}

/// cbindgen:ignore
impl Address {
pub const LEN: usize = 20;
Expand Down
12 changes: 12 additions & 0 deletions rust/tw_number/src/i256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ impl I256 {
}
}

impl I256 {
// Getter method for the internal value
pub fn get_value(&self) -> BaseU256 {
self.0
}

// Setter method for the internal value
pub fn set_value(&mut self, value: BaseU256) {
self.0 = value;
}
theghostmac marked this conversation as resolved.
Show resolved Hide resolved
}

#[cfg(feature = "helpers")]
impl I256 {
#[inline]
Expand Down
12 changes: 12 additions & 0 deletions rust/tw_number/src/u256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ impl U256 {
}
}

impl U256 {
// Getter method for the internal value
pub fn get_value(&self) -> primitive_types::U256 {
self.0
}

// Setter method for the internal value
pub fn set_value(&mut self, value: primitive_types::U256) {
self.0 = value;
}
theghostmac marked this conversation as resolved.
Show resolved Hide resolved
}

#[cfg(feature = "helpers")]
impl U256 {
#[inline]
Expand Down