Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions crates/codec/src/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::func::FunctionArgs;
use crate::{alloc::string::ToString, error::CodecError};
use byteorder::{ByteOrder, BE, LE};
use bytes::{Buf, BytesMut};
use core::marker::PhantomData;

// TODO: @d1r1 Investigate whether decoding the result into an uninitialized memory (e.g., using
// `MaybeUninit`) would be more efficient than initializing with `Default`.
// This could potentially reduce unnecessary memory initialization overhead in cases where
Expand Down Expand Up @@ -62,6 +62,7 @@ pub trait Encoder<B: ByteOrder, const ALIGN: usize, const SOL_MODE: bool, const
align_up::<ALIGN>(Self::HEADER_SIZE)
}
}

macro_rules! define_encoder_mode {
($name:ident, $byte_order:ty, $align:expr, $sol_mode:expr) => {
pub struct $name<T>(PhantomData<T>);
Expand Down Expand Up @@ -133,6 +134,37 @@ define_encoder_mode!(CompactABI, LE, 4, false);
// SolidityPackedABI works only for static types
define_encoder_mode!(SolidityPackedABI, BE, 1, true, static_only);

impl<T> SolidityABI<T> {
pub fn encode_function_args(value: &T, buf: &mut BytesMut) -> Result<(), CodecError>
where
T: FunctionArgs<BE, 32, true, false>,
{
value.encode_as_args(buf)
}

pub fn decode_function_args(buf: &impl Buf) -> Result<T, CodecError>
where
T: FunctionArgs<BE, 32, true, false>,
{
T::decode_as_args(buf)
}
}

impl<T> CompactABI<T> {
pub fn encode_function_args(value: &T, buf: &mut BytesMut) -> Result<(), CodecError>
where
T: FunctionArgs<LE, 4, false, false>,
{
value.encode_as_args(buf)
}

pub fn decode_function_args(buf: &impl Buf) -> Result<T, CodecError>
where
T: FunctionArgs<LE, 4, false, false>,
{
T::decode_as_args(buf)
}
}
pub trait SolidityEncoder: Encoder<BE, 32, true, false> {
const SOLIDITY_HEADER_SIZE: usize = <Self as Encoder<BE, 32, true, false>>::HEADER_SIZE;
}
Expand Down Expand Up @@ -241,7 +273,7 @@ pub(crate) fn get_aligned_slice<B: ByteOrder, const ALIGN: usize>(
// For big-endian, return slice at the end of the aligned space
aligned_offset + word_size - value_size
} else {
// For little-endian, return slice at the beginning of the aligned space
// For little-endian, return a slice at the beginning of the aligned space
aligned_offset
};

Expand Down
129 changes: 129 additions & 0 deletions crates/codec/src/func.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use crate::encoder::Encoder;
use crate::error::CodecError;
use byteorder::ByteOrder;
use bytes::{Buf, BytesMut};

/// Trait for types that can be used as function arguments
/// Only implemented for tuples
pub trait FunctionArgs<
B: ByteOrder,
const ALIGN: usize,
const SOL_MODE: bool,
const IS_STATIC: bool,
>: Encoder<B, ALIGN, SOL_MODE, IS_STATIC>
{
/// Encode without the outer tuple offset for dynamic types
fn encode_as_args(&self, buf: &mut BytesMut) -> Result<(), CodecError> {
if Self::IS_DYNAMIC {
// For dynamic types, skip the first ALIGN bytes (the tuple offset)
let mut temp_buf = BytesMut::new();
self.encode(&mut temp_buf, 0)?;

// Skip the offset and append the rest
if temp_buf.len() > ALIGN {
buf.extend_from_slice(&temp_buf[ALIGN..]);
}
Ok(())
} else {
// Static types encode normally
self.encode(buf, buf.len())
}
}

/// Decode expecting no outer tuple offset for dynamic types
fn decode_as_args(buf: &impl Buf) -> Result<Self, CodecError> {
if Self::IS_DYNAMIC {
use bytes::BufMut;
// Add the offset back for proper decoding
let mut temp_buf = BytesMut::with_capacity(ALIGN + buf.remaining());

// Add offset header
if SOL_MODE {
// Solidity: 32-byte aligned, big-endian
temp_buf.resize(ALIGN, 0);
let offset = (ALIGN as u32).to_be_bytes();
temp_buf[ALIGN - 4..ALIGN].copy_from_slice(&offset);
} else {
// Fluent: 4-byte, little-endian
temp_buf.put_u32_le(ALIGN as u32);
}

// Add the actual data
temp_buf.extend_from_slice(buf.chunk());

// Decode with the reconstructed offset
Self::decode(&temp_buf.freeze(), 0)
} else {
// Static types decode normally
Self::decode(buf, 0)
}
}
}

// Implement for all tuples - super simple!
impl<B: ByteOrder, const ALIGN: usize, const SOL_MODE: bool, const IS_STATIC: bool>
FunctionArgs<B, ALIGN, SOL_MODE, IS_STATIC> for ()
{
}

impl<T, B: ByteOrder, const ALIGN: usize, const SOL_MODE: bool, const IS_STATIC: bool>
FunctionArgs<B, ALIGN, SOL_MODE, IS_STATIC> for (T,)
where
T: Encoder<B, ALIGN, SOL_MODE, IS_STATIC>,
{
}

// Macro for Solidity mode (SOL_MODE = true)
macro_rules! impl_function_args_solidity {
($($T:ident),+) => {
impl<$($T,)+ B: ByteOrder, const ALIGN: usize, const IS_STATIC: bool>
FunctionArgs<B, ALIGN, true, IS_STATIC> for ($($T,)+)
where
$($T: Encoder<B, ALIGN, true, IS_STATIC>,)+
{}
};
}

// Macro for Compact mode (SOL_MODE = false)
macro_rules! impl_function_args_compact {
($($T:ident),+) => {
impl<$($T,)+ B: ByteOrder, const ALIGN: usize, const IS_STATIC: bool>
FunctionArgs<B, ALIGN, false, IS_STATIC> for ($($T,)+)
where
$($T: Encoder<B, ALIGN, false, IS_STATIC>,)+
{}
};
}

impl_function_args_solidity!(T0, T1);
impl_function_args_compact!(T0, T1);

impl_function_args_solidity!(T0, T1, T2);
impl_function_args_compact!(T0, T1, T2);

impl_function_args_solidity!(T0, T1, T2, T3);
impl_function_args_compact!(T0, T1, T2, T3);

impl_function_args_solidity!(T0, T1, T2, T3, T4);
impl_function_args_compact!(T0, T1, T2, T3, T4);

impl_function_args_solidity!(T0, T1, T2, T3, T4, T5);
impl_function_args_compact!(T0, T1, T2, T3, T4, T5);

impl_function_args_solidity!(T0, T1, T2, T3, T4, T5, T6);
impl_function_args_compact!(T0, T1, T2, T3, T4, T5, T6);

impl_function_args_solidity!(T0, T1, T2, T3, T4, T5, T6, T7);
impl_function_args_compact!(T0, T1, T2, T3, T4, T5, T6, T7);

impl_function_args_solidity!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
impl_function_args_compact!(T0, T1, T2, T3, T4, T5, T6, T7, T8);

impl_function_args_solidity!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_function_args_compact!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);

impl_function_args_solidity!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_function_args_compact!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);

impl_function_args_solidity!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl_function_args_compact!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
1 change: 1 addition & 0 deletions crates/codec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod empty;
pub mod encoder;
mod error;
mod evm;
mod func;
mod hash;
mod primitive;
mod tuple;
Expand Down
8 changes: 8 additions & 0 deletions crates/codec/src/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,14 @@ impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7; 0, 1, 2, 3, 4, 5, 6; true);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7; 0, 1, 2, 3, 4, 5, 6; false);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8; 0, 1, 2, 3, 4, 5, 6, 7; true);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8; 0, 1, 2, 3, 4, 5, 6, 7; false);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9; 0, 1, 2, 3, 4, 5, 6, 7, 8; true);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9; 0, 1, 2, 3, 4, 5, 6, 7, 8; false);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9; true);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9; false);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; true);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; false);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11; true);
impl_encoder_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11; false);

#[cfg(test)]
mod tests {
Expand Down
93 changes: 93 additions & 0 deletions crates/codec/tests/roundtrip/func.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use super::*;

#[test]
fn test_solidity_function_args_dynamic() {
// Test that dynamic tuple encoding skips the outer offset
let args = (Bytes::from("hello"), U256::from(42));

let mut buf_normal = BytesMut::new();
let mut buf_func = BytesMut::new();

// Normal encoding (with tuple offset)
SolidityABI::encode(&args, &mut buf_normal, 0).unwrap();

// Function args encoding (without tuple offset)
SolidityABI::encode_function_args(&args, &mut buf_func).unwrap();

// Function args should be 32 bytes shorter (no tuple offset)
assert_eq!(buf_func.len(), buf_normal.len() - 32);
assert_eq!(&buf_func[..], &buf_normal[32..]);

// Decode and verify
let decoded: (Bytes, U256) = SolidityABI::decode_function_args(&buf_func).unwrap();
assert_eq!(decoded, args);
}

#[test]
fn test_solidity_function_args_static() {
// Test that static tuple encoding remains the same
let args = (U256::from(100), Address::ZERO, 42u32);

let mut buf_normal = BytesMut::new();
let mut buf_func = BytesMut::new();

// Both encodings should be identical for static types
SolidityABI::encode(&args, &mut buf_normal, 0).unwrap();
SolidityABI::encode_function_args(&args, &mut buf_func).unwrap();

assert_eq!(buf_normal, buf_func);

// Decode and verify
let decoded: (U256, Address, u32) = SolidityABI::decode_function_args(&buf_func).unwrap();
assert_eq!(decoded, args);
}

#[test]
fn test_compact_function_args_dynamic() {
// Test CompactABI with dynamic types
let args = (vec![1u8, 2, 3], "test".to_string());

let mut buf_normal = BytesMut::new();
let mut buf_func = BytesMut::new();

// Normal encoding (with tuple offset)
CompactABI::encode(&args, &mut buf_normal, 0).unwrap();

// Function args encoding (without tuple offset)
CompactABI::encode_function_args(&args, &mut buf_func).unwrap();

// Function args should be 4 bytes shorter (no tuple offset)
assert_eq!(buf_func.len(), buf_normal.len() - 4);
assert_eq!(&buf_func[..], &buf_normal[4..]);

// Decode and verify
let decoded: (Vec<u8>, String) = CompactABI::decode_function_args(&buf_func).unwrap();
assert_eq!(decoded, args);
}

#[test]
fn test_empty_and_single_args() {
// Empty tuple
let empty = ();
let mut buf = BytesMut::new();

SolidityABI::encode_function_args(&empty, &mut buf).unwrap();
assert_eq!(buf.len(), 0);

let decoded: () = SolidityABI::decode_function_args(&buf).unwrap();
assert_eq!(decoded, empty);

// Single dynamic arg
let single = (Bytes::from("data"),);
buf.clear();

SolidityABI::encode_function_args(&single, &mut buf).unwrap();

// Should skip the tuple wrapper offset
let mut buf_normal = BytesMut::new();
SolidityABI::encode(&single, &mut buf_normal, 0).unwrap();
assert_eq!(&buf[..], &buf_normal[32..]);

let decoded: (Bytes,) = SolidityABI::decode_function_args(&buf).unwrap();
assert_eq!(decoded, single);
}
1 change: 1 addition & 0 deletions crates/codec/tests/roundtrip/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ use fluentbase_codec::{

mod structs;
mod tuples;
mod func;
Loading
Loading