//! A module to simplify ABI encoding
//!
//! For simplicity, it is based on tokens (as in items, not as in coin tokens). You have to specify a list of
//! tokens and they will be automatically encoded.
//!
//! Additionally there are helpers to help deal with deriving a function
//! signatures.
//!
//! This is not a full fledged implementation of ABI encoder, it is more
//! like a bunch of helpers that would help to successfully encode a contract
//! call.
//!
//! ## Limitation
//!
//! Currently this module can only serialize types that can be represented by a [Token](#struct.Token).
//!
//! Unfortunately if you need to support custom type that is not currently supported you are welcome to open an issue [on issues page](https://github.com/althea-net/clarity/issues/new),
//! or do the serialization yourself by converting your custom type into a `[u8; 32]` array and creating a proper Token instance.
use crate::address::Address;
use crate::error::Error;
use num256::Uint256;
use sha3::{Digest, Keccak256};
/// A token represents a value of parameter of the contract call.
///
/// For each supported type there is separate entry that later is helpful to determine
/// actual byte representation.
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum Token {
/// Unsigned type with value already encoded.
Uint(Uint256),
/// Ethereum Address
Address(Address),
/// A boolean logic
Bool(bool),
/// Represents a string
String(String),
/// Represents a string encoded into a fixed size bytes32
FixedString(String),
/// Fixed size array of bytes
Bytes(Vec<u8>),
/// This is a dynamic array of bytes that reflects dynamic "bytes" type in Solidity
UnboundedBytes(Vec<u8>),
/// Dynamic array with supported values of supported types already converted
Dynamic(Vec<Token>),
/// A struct to be encoded as a contract call argument
Struct(Vec<Token>),
}
/// Representation of a serialized token.
///
/// Serialization occurs once a list of tokens is passed. After that
/// the library will determine the actual ABI encoding of a each type wrapped in
/// a token, and then it will return a
/// [SerializedToken::Static](#variant.Static), or
/// [SerializedToken::Dynamic](#variant.Dynamic) depending on encoding rules
/// used for a given type.
///
/// With a list of values of type `SerializedToken` a caller can construct a final
/// binary data that will represent a valid ABI encoding of function parameters.
pub enum SerializedToken {
/// This data can be safely appended to the output stream
Static([u8; 32]),
/// This data should be saved up in a buffer, and an offset should be
/// appended to the output stream instead.
Dynamic(Vec<u8>),
}
impl SerializedToken {
/// Gets a reference to value held by Static
fn as_static_ref(&self) -> Option<&[u8; 32]> {
match *self {
SerializedToken::Static(ref data) => Some(data),
_ => None,
}
}
}
impl Token {
/// Serializes a token into a [SerializedToken]()
pub fn serialize(&self) -> SerializedToken {
match *self {
Token::Uint(ref value) => {
assert!(value.bits() <= 256);
let bytes = value.to_be_bytes();
let mut res: [u8; 32] = Default::default();
res[32 - bytes.len()..].copy_from_slice(&bytes);
SerializedToken::Static(res)
}
Token::Bool(value) => {
let mut res: [u8; 32] = Default::default();
res[31] = value as u8;
SerializedToken::Static(res)
}
Token::Dynamic(ref tokens) => {
let mut wtr = vec![];
let prefix: Token = (tokens.len() as u64).into();
wtr.extend(prefix.serialize().as_static_ref().unwrap());
wtr.extend(encode_tokens(tokens));
SerializedToken::Dynamic(wtr)
}
Token::Struct(ref tokens) => SerializedToken::Dynamic(encode_tokens(tokens)),
Token::UnboundedBytes(ref v) => {
let mut wtr = vec![];
// Encode prefix
let prefix: Token = (v.len() as u64).into();
wtr.extend(prefix.serialize().as_static_ref().unwrap());
// Pad on the right
wtr.extend(v);
let pad_right = (((v.len() - 1) / 32) + 1) * 32;
wtr.extend(vec![0x00u8; pad_right - v.len()]);
SerializedToken::Dynamic(wtr)
}
Token::String(ref s) => {
let mut wtr = vec![];
// Encode prefix
let prefix: Token = (s.len() as u64).into();
wtr.extend(prefix.serialize().as_static_ref().unwrap());
// Pad on the right
wtr.extend(s.as_bytes());
let pad_right = (((s.len() - 1) / 32) + 1) * 32;
wtr.extend(vec![0x00u8; pad_right - s.len()]);
SerializedToken::Dynamic(wtr)
}
Token::FixedString(ref s) => {
// gets the utf8 encoded bytes of the string value
let value = s.to_string().as_bytes().to_vec();
// This value is padded at the end. It is limited to 32 bytes.
// if the fixed string is too long here we panic
assert!(value.len() <= 32);
let mut wtr: [u8; 32] = Default::default();
wtr[0..value.len()].copy_from_slice(&value[..]);
SerializedToken::Static(wtr)
}
Token::Bytes(ref value) => {
// This value is padded at the end. It is limited to 32 bytes.
assert!(value.len() <= 32);
let mut wtr: [u8; 32] = Default::default();
wtr[0..value.len()].copy_from_slice(&value[..]);
SerializedToken::Static(wtr)
}
Token::Address(ref address) => {
// Address is the same as above, but for extra syntax sugar
// we treat it as separate case.
let mut wtr: [u8; 32] = Default::default();
let bytes = address.as_bytes();
wtr[32 - bytes.len()..].copy_from_slice(bytes);
SerializedToken::Static(wtr)
}
}
}
}
impl From<u8> for Token {
fn from(v: u8) -> Token {
Token::Uint(Uint256::from(v))
}
}
impl From<u16> for Token {
fn from(v: u16) -> Token {
Token::Uint(Uint256::from(v))
}
}
impl From<u32> for Token {
fn from(v: u32) -> Token {
Token::Uint(Uint256::from(v))
}
}
impl From<u64> for Token {
fn from(v: u64) -> Token {
Token::Uint(Uint256::from(v))
}
}
impl From<u128> for Token {
fn from(v: u128) -> Token {
Token::Uint(Uint256::from(v))
}
}
impl From<bool> for Token {
fn from(v: bool) -> Token {
Token::Bool(v)
}
}
impl From<Vec<u8>> for Token {
fn from(v: Vec<u8>) -> Token {
Token::UnboundedBytes(v)
}
}
impl From<Vec<u16>> for Token {
fn from(v: Vec<u16>) -> Token {
Token::Dynamic(v.into_iter().map(Into::into).collect())
}
}
impl From<Vec<u32>> for Token {
fn from(v: Vec<u32>) -> Token {
Token::Dynamic(v.into_iter().map(Into::into).collect())
}
}
impl From<Vec<u64>> for Token {
fn from(v: Vec<u64>) -> Token {
Token::Dynamic(v.into_iter().map(Into::into).collect())
}
}
impl From<Vec<u128>> for Token {
fn from(v: Vec<u128>) -> Token {
Token::Dynamic(v.into_iter().map(Into::into).collect())
}
}
impl From<Address> for Token {
fn from(v: Address) -> Token {
Token::Address(v)
}
}
impl From<&Address> for Token {
fn from(v: &Address) -> Token {
Token::Address(*v)
}
}
impl<'a> From<&'a str> for Token {
fn from(v: &'a str) -> Token {
Token::String(v.into())
}
}
impl From<Vec<Address>> for Token {
fn from(v: Vec<Address>) -> Token {
Token::Dynamic(v.into_iter().map(Into::into).collect())
}
}
impl From<Vec<Token>> for Token {
fn from(v: Vec<Token>) -> Token {
Token::Dynamic(v.into_iter().map(Into::into).collect())
}
}
impl From<&[Address]> for Token {
fn from(v: &[Address]) -> Token {
Token::Dynamic(v.iter().map(Into::into).collect())
}
}
impl From<Uint256> for Token {
fn from(v: Uint256) -> Token {
Token::Uint(v)
}
}
impl From<&Uint256> for Token {
fn from(v: &Uint256) -> Token {
Token::Uint(*v)
}
}
impl From<Vec<Uint256>> for Token {
fn from(v: Vec<Uint256>) -> Token {
Token::Dynamic(v.into_iter().map(Into::into).collect())
}
}
impl From<&[Uint256]> for Token {
fn from(v: &[Uint256]) -> Token {
Token::Dynamic(v.iter().map(Into::into).collect())
}
}
/// Raw derive for a Keccak256 digest from a string
///
/// This function should be used when trying to filter out interesting
/// events from a contract. This is different than contract function
/// calls because it uses whole 32 bytes of the hash digest.
pub fn derive_signature(data: &str) -> Result<[u8; 32], Error> {
if data.contains(' ') {
return Err(Error::InvalidCallError(
"No spaces are allowed in call names".to_string(),
));
} else if !(data.contains('(') && data.contains(')')) {
return Err(Error::InvalidCallError(
"Mismatched call braces".to_string(),
));
}
let digest = Keccak256::digest(data.as_bytes());
let mut result: [u8; 32] = Default::default();
result.copy_from_slice(&digest);
Ok(result)
}
/// Given a signature it derives a Method ID
pub fn derive_method_id(signature: &str) -> Result<[u8; 4], Error> {
let digest = derive_signature(signature)?;
let mut result: [u8; 4] = Default::default();
result.copy_from_slice(&digest[0..4]);
Ok(result)
}
/// This one is a very simplified ABI encoder that takes a bunch of tokens,
/// and serializes them.
///
/// Use with caution!
pub fn encode_tokens(tokens: &[Token]) -> Vec<u8> {
// This is the result data buffer
let mut res = Vec::new();
// A cache of dynamic data buffers that are stored here.
let mut dynamic_data: Vec<Vec<u8>> = Vec::new();
for token in tokens.iter() {
match token.serialize() {
SerializedToken::Static(data) => res.extend(data),
SerializedToken::Dynamic(data) => {
// This is the offset for dynamic data that is calculated
// based on the length of all dynamic data buffers stored,
// and added to the "base" offset which is all tokens length.
// The base offset is assumed to be 32 * len(tokens) which is true
// since dynamic data is actually an static variable of size of
// 32 bytes.
let dynamic_offset = dynamic_data
.iter()
.map(|data| data.len() as u64)
.fold(tokens.len() as u64 * 32, |r, v| r + v);
// Store next dynamic buffer *after* dynamic offset is calculated.
dynamic_data.push(data);
// static structs do not require offsets as they aren't actually
// of dynamic length
if !is_static_struct_array(tokens) {
// Convert into token for easy serialization
let offset: Token = dynamic_offset.into();
// Write the offset of the dynamic data as a value of static size.
match offset.serialize() {
SerializedToken::Static(bytes) => res.extend(bytes),
_ => panic!("Offset token is expected to be static"),
}
}
}
}
}
// Concat all the dynamic data buffers at the end of the process
// All the offsets are calculated while iterating and properly stored
// in a single pass.
// let values = &dynamic_data.iter();
for data in dynamic_data.iter() {
res.extend(&data[..]);
}
res
}
/// Gets the Keccak256 hash of some input bytes. Signatures in Ethereum are nearly without
/// exception performed after encoding using the ABI, then hashing using this function.
pub fn get_hash(bytes: &[u8]) -> [u8; 32] {
Keccak256::digest(bytes).into()
}
/// A helper function that encodes both signature and a list of tokens.
pub fn encode_call(sig: &str, tokens: &[Token]) -> Result<Vec<u8>, Error> {
let mut wtr = vec![];
wtr.extend(derive_method_id(sig)?);
let args_count = get_args_count(sig)?;
let token_count = get_tokens_count(tokens);
if args_count != token_count {
return Err(Error::InvalidCallError(format!(
"Function call contains {args_count} arguments, but {token_count} provided"
)));
}
wtr.extend(encode_tokens(tokens));
Ok(wtr)
}
/// Counts the number of tokens in a token array, including nested tokens
/// this will give you the number of tokens you need in a function call
/// argument string
fn get_tokens_count(tokens: &[Token]) -> usize {
let mut count = 0;
for token in tokens {
match token {
Token::Struct(v) => count += get_tokens_count(v),
// for the case of an array of structs we count that structs members
// that is what we'll see in the function header
Token::Dynamic(d) => {
if is_struct_array(d) && !d.is_empty() {
count += get_tokens_count(&[d[0].clone()])
} else {
count += 1
}
}
_ => count += 1,
}
}
count
}
/// Simple utility function to detect arrays of structs
fn is_struct_array(input: &[Token]) -> bool {
// arguable null case, could go either way
if input.is_empty() {
return false;
}
for t in input {
match t {
Token::Struct(_) => {}
_ => return false,
}
}
true
}
/// Simple utility function to detect arrays of structs that are all static in size
fn is_static_struct_array(input: &[Token]) -> bool {
// arguable null case, could go either way
if input.is_empty() {
return false;
}
for t in input {
match t {
Token::Struct(v) => {
for t in v {
if let SerializedToken::Dynamic(_) = t.serialize() {
return false;
}
}
}
_ => return false,
}
}
true
}
/// Gets the number of arguments by parsing a function signature
/// string.
fn get_args_count(sig: &str) -> Result<usize, Error> {
// number of opening brackets must match number of closing brackets
if sig.matches('(').count() != sig.matches(')').count() {
return Err(Error::InvalidCallError(
"Mismatched call braces".to_string(),
));
}
// split on either an opening or closing bracket, substrings are now all batches of arguments
let args = sig.split(|ch| ch == '(' || ch == ')');
let mut num_args = 0;
for substring in args {
// leading or trailing ,'s or []
let substring = substring.trim_matches(|c| c == ']' || c == '[');
let substring = substring.trim_matches(',');
let substring = substring.trim();
if !substring.is_empty() {
num_args += substring.split(',').count();
}
}
// subtract one because the function signature will be in
// one substring always
Ok(num_args - 1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::hex_str_to_bytes;
#[test]
fn derive_event_signature() {
use crate::utils::bytes_to_hex_str;
let derived = derive_signature("HelloWorld(string)").unwrap();
assert_eq!(
bytes_to_hex_str(&derived),
"86066750c0fd4457fd16f79750914fbd72db952f2ff0a7b5c6a2a531bc15ce2c"
);
let derived = derive_signature("Transfer(address,address,uint256)").unwrap();
assert_eq!(
bytes_to_hex_str(&derived),
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
);
let derived = derive_signature("Approval(address,address,uint256)").unwrap();
assert_eq!(
bytes_to_hex_str(&derived),
"8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
);
}
#[test]
fn derive_baz() {
use crate::utils::bytes_to_hex_str;
assert_eq!(
bytes_to_hex_str(&derive_method_id("baz(uint32,bool)").unwrap()),
"cdcd77c0"
);
}
#[test]
fn derive_bar() {
use crate::utils::bytes_to_hex_str;
assert_eq!(
bytes_to_hex_str(&derive_method_id("bar(bytes3[2])").unwrap()),
"fce353f6"
);
}
#[test]
fn derive_sam() {
use crate::utils::bytes_to_hex_str;
assert_eq!(
bytes_to_hex_str(&derive_method_id("sam(bytes,bool,uint256[])").unwrap()),
"a5643bf2"
);
}
#[test]
fn derive_complex_signatures() {
use crate::utils::bytes_to_hex_str;
assert_eq!(
bytes_to_hex_str(&derive_method_id("dummyUpdateValset(address[])").unwrap()),
"fd9b9103"
);
assert_eq!(
bytes_to_hex_str(&derive_method_id("dummyUpdateValset(address[],uint256[])").unwrap()),
"711ca6ac"
);
assert_eq!(bytes_to_hex_str(&derive_method_id("updateValset((address[],uint256[],uint256,uint256,address),(address[],uint256[],uint256,uint256,address),(uint8,bytes32,bytes32)[])").unwrap()), "aca6b1c1");
assert_eq!(bytes_to_hex_str(&derive_method_id("submitLogicCall((address[],uint256[],uint256,uint256,address),(uint8,bytes32,bytes32)[],(uint256[],address[],uint256[],address[],address,bytes,uint256,bytes32,uint256))").unwrap()), "6941db93");
}
#[test]
fn derive_f() {
use crate::utils::bytes_to_hex_str;
assert_eq!(
bytes_to_hex_str(&derive_method_id("f(uint256,uint32[],bytes10,bytes)").unwrap()),
"8be65246"
);
}
#[test]
fn derive_function_with_args() {
encode_call("f()", &[]).unwrap();
encode_call("f(uint256)", &["66u64".into()]).unwrap();
encode_call("f(uint256,uint256)", &["66u64".into(), "66u64".into()]).unwrap();
encode_call(
"f(uint256,uint256,uint256)",
&["66u64".into(), "66u64".into(), "66u64".into()],
)
.unwrap();
}
#[test]
fn attempt_to_derive_invalid_function_signatures() {
assert!(derive_method_id("dummyUpdateValset( address[])").is_err());
assert!(derive_method_id("dummyUpdateValsetaddress[],uint256[])").is_err());
assert!(encode_call("dummyUpdateValset(address[],uint256[])", &["66u64".into()]).is_err());
}
#[test]
fn encode_simple() {
use crate::utils::bytes_to_hex_str;
let result = encode_tokens(&[69u32.into(), true.into()]);
assert_eq!(
bytes_to_hex_str(&result),
concat!(
"0000000000000000000000000000000000000000000000000000000000000045",
"0000000000000000000000000000000000000000000000000000000000000001"
)
);
}
#[test]
fn encode_sam() {
use crate::utils::bytes_to_hex_str;
let result = encode_tokens(&["dave".into(), true.into(), vec![1u32, 2u32, 3u32].into()]);
assert!(result.len() % 8 == 0);
assert_eq!(
bytes_to_hex_str(&result),
concat![
// the location of the data part of the first parameter
// (dynamic type), measured in bytes from the start of the
// arguments block. In this case, 0x60.
"0000000000000000000000000000000000000000000000000000000000000060",
// the second parameter: boolean true.
"0000000000000000000000000000000000000000000000000000000000000001",
// the location of the data part of the third parameter
// (dynamic type), measured in bytes. In this case, 0xa0.
"00000000000000000000000000000000000000000000000000000000000000a0",
// the data part of the first argument, it starts with the length
// of the byte array in elements, in this case, 4.
"0000000000000000000000000000000000000000000000000000000000000004",
// the contents of the first argument: the UTF-8 (equal to ASCII
// in this case) encoding of "dave", padded on the right to 32
// bytes.
"6461766500000000000000000000000000000000000000000000000000000000",
// the data part of the third argument, it starts with the length
// of the array in elements, in this case, 3.
"0000000000000000000000000000000000000000000000000000000000000003",
// the first entry of the third parameter.
"0000000000000000000000000000000000000000000000000000000000000001",
// the second entry of the third parameter.
"0000000000000000000000000000000000000000000000000000000000000002",
// the third entry of the third parameter.
"0000000000000000000000000000000000000000000000000000000000000003",
]
);
}
#[test]
fn encode_f() {
use crate::utils::bytes_to_hex_str;
let result = encode_tokens(&[
0x123u32.into(),
vec![0x456u32, 0x789u32].into(),
Token::Bytes(b"1234567890".to_vec()),
"Hello, world!".into(),
]);
assert!(result.len() % 8 == 0);
assert_eq!(
result[..]
.chunks(32)
.map(bytes_to_hex_str)
.collect::<Vec<String>>(),
vec![
"0000000000000000000000000000000000000000000000000000000000000123".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000080".to_owned(),
"3132333435363738393000000000000000000000000000000000000000000000".to_owned(),
"00000000000000000000000000000000000000000000000000000000000000e0".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000002".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000456".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000789".to_owned(),
"000000000000000000000000000000000000000000000000000000000000000d".to_owned(),
"48656c6c6f2c20776f726c642100000000000000000000000000000000000000".to_owned(),
]
);
}
#[test]
fn encode_f_with_real_unbounded_bytes() {
use crate::utils::bytes_to_hex_str;
let result = encode_tokens(&[
0x123u32.into(),
vec![0x456u32, 0x789u32].into(),
Token::Bytes(b"1234567890".to_vec()),
b"Hello, world!".to_vec().into(),
]);
assert!(result.len() % 8 == 0);
assert_eq!(
result[..]
.chunks(32)
.map(bytes_to_hex_str)
.collect::<Vec<String>>(),
vec![
"0000000000000000000000000000000000000000000000000000000000000123".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000080".to_owned(),
"3132333435363738393000000000000000000000000000000000000000000000".to_owned(),
"00000000000000000000000000000000000000000000000000000000000000e0".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000002".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000456".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000789".to_owned(),
"000000000000000000000000000000000000000000000000000000000000000d".to_owned(),
"48656c6c6f2c20776f726c642100000000000000000000000000000000000000".to_owned(),
]
);
}
#[test]
fn encode_address() {
use crate::utils::bytes_to_hex_str;
let result = encode_tokens(&["0x00000000000000000000000000000000deadbeef"
.parse::<Address>()
.expect("Unable to parse address")
.into()]);
assert!(result.len() % 8 == 0);
assert_eq!(
result[..]
.chunks(32)
.map(bytes_to_hex_str)
.collect::<Vec<String>>(),
vec!["00000000000000000000000000000000000000000000000000000000deadbeef".to_owned(),]
);
}
#[test]
fn encode_dynamic_only() {
use crate::utils::bytes_to_hex_str;
let result = encode_tokens(&["foo".into(), "bar".into()]);
assert!(result.len() % 8 == 0);
assert_eq!(
result[..]
.chunks(32)
.map(bytes_to_hex_str)
.collect::<Vec<String>>(),
vec![
"0000000000000000000000000000000000000000000000000000000000000040".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000080".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000003".to_owned(),
"666f6f0000000000000000000000000000000000000000000000000000000000".to_owned(),
"0000000000000000000000000000000000000000000000000000000000000003".to_owned(),
"6261720000000000000000000000000000000000000000000000000000000000".to_owned(),
]
);
}
#[test]
fn encode_peggy_checkpoint_hash() {
use crate::utils::bytes_to_hex_str;
// the valset nonce
let nonce: Uint256 = 0u32.into();
// the list of validator ethereum addresses represented by this
let validators: Token = vec![
"0xc783df8a850f42e7F7e57013759C285caa701eB6"
.parse::<Address>()
.unwrap(),
"0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4"
.parse()
.unwrap(),
"0xE5904695748fe4A84b40b3fc79De2277660BD1D3"
.parse()
.unwrap(),
]
.into();
// list of powers represented
let powers: Token = vec![3333u32, 3333, 3333].into();
let encoded = "666f6f0000000000000000000000000000000000000000000000000000000000636865636b706f696e7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000ead9c93b79ae7c1591b1fb5323bd777e86e150d4000000000000000000000000e5904695748fe4a84b40b3fc79de2277660bd1d300000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000d050000000000000000000000000000000000000000000000000000000000000d050000000000000000000000000000000000000000000000000000000000000d05";
// the hash resulting from the encode call
let encoded_hash = "88165860d955aee7dc3e83d9d1156a5864b708841965585d206dbef6e9e1a499";
let result = encode_tokens(&[
Token::FixedString("foo".to_string()),
Token::FixedString("checkpoint".to_string()),
nonce.into(),
validators,
powers,
]);
assert_eq!(encoded, bytes_to_hex_str(&result));
assert_eq!(encoded_hash, bytes_to_hex_str(&get_hash(&result)))
}
#[test]
fn encode_function_with_only_struct_arg() {
let correct = hex_str_to_bytes(
"0x414bf389000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb6000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb600000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000c783df8a850f42e7f7e57013759c285caa701eb600000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let address: Address = "0xc783df8a850f42e7F7e57013759C285caa701eB6"
.parse()
.unwrap();
let tokens: Vec<Token> = vec![
address.into(),
address.into(),
500u16.into(),
address.into(),
100_000u32.into(),
100_000u32.into(),
0u8.into(),
0u8.into(),
];
let tokens = [Token::Struct(tokens)];
let sig =
"exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))";
let payload = encode_call(sig, &tokens).unwrap();
assert_eq!(correct, payload);
}
#[test]
/// This test encodes an abiV2 function call, specifically one
/// with a nontrivial struct in the header
fn encode_abiv2_function_header() {
use crate::utils::bytes_to_hex_str;
let signature = "submitLogicCall(address[],uint256[],uint256,uint8[],bytes32[],bytes32[],(uint256[],address[],uint256[],address[],address,bytes,uint256,bytes32,uint256))";
let encoded_method_id = "0x0c246c82";
let res = derive_method_id(signature).unwrap();
assert_eq!(encoded_method_id, format!("0x{}", bytes_to_hex_str(&res)));
}
#[test]
/// This test encodes an abiV2 function call, specifically one
/// with a nontrivial struct in the header
fn encode_uniswap_header() {
use crate::utils::bytes_to_hex_str;
let signature =
"exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))";
let encoded_method_id = "0x414bf389";
let res = derive_method_id(signature).unwrap();
assert_eq!(encoded_method_id, format!("0x{}", bytes_to_hex_str(&res)));
}
#[test]
fn test_args_count() {
let test_signatures = [
("testCall()", 0),
("testCall(uint256,uint256,uint256)", 3),
("updateValset((address[],uint256[],uint256,uint256,address),(address[],uint256[],uint256,uint256,address),uint8[],bytes32[],bytes32[])", 13),
("submitLogicCall(address[],uint256[],uint256,uint8[],bytes32[],bytes32[],(uint256[],address[],uint256[],address[],address,bytes,uint256,bytes32,uint256))", 15),
("updateValset((address[],uint256[],uint256,uint256,address),(address[],uint256[],uint256,uint256,address),(uint8[],bytes32[],bytes32[]))", 13),
("updateValset((address[],uint256[],uint256,uint256,address),(address[],uint256[],uint256,uint256,address),(uint8,bytes32,bytes32)[])", 13),
("submitBatch((address[],uint256[],uint256,uint256,address),(uint8,bytes32,bytes32)[],uint256[],address[],uint256[],uint256,address,uint256)", 14)
];
for (sig, count) in test_signatures.iter() {
assert_eq!(get_args_count(sig).unwrap(), *count);
}
}
}