Created
October 10, 2024 22:59
-
-
Save 0xAlcibiades/4b8c32af8e30212f428670a586507a61 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! THIS IS A GREAT EVIL THAT MUST BE DESTROYED necessitated by the current | |
//! limitations of the `alloy` crate. | |
//! | |
//! This module provides a wrapper for the `SolCall` trait from the `alloy` crate. | |
//! Its main purpose is to create an object-safe interface for `SolCall` implementations, | |
//! allowing for dynamic dispatch and easier handling of different Solidity function calls | |
//! in a uniform manner. | |
//! | |
//! ## Motivation | |
//! | |
//! The current implementation of `SolCall` in the `alloy` crate has several limitations: | |
//! | |
//! 1. It is not object-safe, preventing the use of dynamic dispatch. | |
//! 2. It has associated types, making it difficult to use generically across different implementations. | |
//! 3. The `sol!` macro doesn't generate an enum that implements `SolCall` for all generated calls. | |
//! | |
//! These limitations make it challenging to work with multiple `SolCall` implementations | |
//! polymorphically, especially when trying to encode or decode different types of calls. | |
//! | |
//! ## Current Approach | |
//! | |
//! This wrapper provides a workaround by: | |
//! | |
//! 1. Defining an object-safe `SolCallWrapper` trait. | |
//! 2. Implementing this trait for all types that implement `SolCall`. | |
//! 3. Using type erasure (via `Box<dyn Any>`) for associated types. | |
//! | |
//! This allows for dynamic dispatch and the ability to store different `SolCall` | |
//! implementations in homogeneous collections (e.g., `Vec<Box<dyn SolCallWrapper>>`). | |
//! | |
//! ## Limitations | |
//! | |
//! While this approach allows for more flexible usage of `SolCall` implementations, it comes with some drawbacks: | |
//! | |
//! 1. Runtime type checking and potential for runtime errors. | |
//! 2. Performance overhead due to dynamic dispatch and type erasure. | |
//! 3. Loss of static type information for associated types. | |
//! | |
//! ## Future Improvements | |
//! | |
//! Ideally, this wrapper should be a temporary solution. Future improvements to the `alloy` crate could include: | |
//! | |
//! 1. Making `SolCall` object-safe, if possible. | |
//! 2. Implementing a `DynSolCall` that can be converted to and from concrete `SolCall` implementations. | |
//! 3. Enhancing the `sol!` macro to generate a `SolCallEnum` that implements `SolCall` and can dispatch to concrete implementations. | |
//! 4. Implementing a trait similar to `enum_dispatch` for `SolCall` to allow for efficient polymorphic usage without dynamic dispatch. | |
//! | |
//! Until such improvements are made, this wrapper provides a workable solution for | |
//! handling multiple `SolCall` implementations in a more flexible manner. | |
use alloy::sol_types::{SolCall, SolType, Result}; | |
use std::any::{Any, TypeId}; | |
/// A trait that wraps the functionality of `SolCall`, providing an object-safe interface. | |
/// | |
/// This trait allows for dynamic dispatch of `SolCall` implementations, enabling | |
/// polymorphic usage of different Solidity function calls. | |
pub trait SolCallWrapper { | |
/// Returns the ABI signature of the Solidity function. | |
fn signature(&self) -> &'static str; | |
/// Returns the 4-byte selector of the Solidity function. | |
fn selector(&self) -> [u8; 4]; | |
/// Returns the size of the ABI-encoded call data in bytes, without the selector. | |
fn abi_encoded_size(&self) -> usize; | |
/// ABI-encodes the call data, including the selector. | |
fn abi_encode(&self) -> Vec<u8>; | |
/// ABI-encodes the call data into the provided buffer, without the selector. | |
fn abi_encode_raw(&self, out: &mut Vec<u8>); | |
/// Tokenizes the call data. | |
fn tokenize(&self) -> Box<dyn Any + '_>; | |
/// Creates a new instance of the `SolCall` implementation from ABI-encoded parameters. | |
/// | |
/// This method is not object-safe and requires `Self: Sized`. | |
fn new_boxed(tuple: Box<dyn Any>) -> Result<Box<dyn SolCallWrapper>> where Self: Sized; | |
/// Decodes the ABI-encoded return data. | |
/// | |
/// This method is not object-safe and requires `Self: Sized`. | |
fn abi_decode_returns_boxed(data: &[u8], validate: bool) -> Result<Box<dyn Any>> where Self: Sized; | |
/// Decodes the ABI-encoded call data, without the selector. | |
/// | |
/// This method is not object-safe and requires `Self: Sized`. | |
fn abi_decode_raw_boxed(data: &[u8], validate: bool) -> Result<Box<dyn SolCallWrapper>> where Self: Sized; | |
/// Decodes the ABI-encoded call data, including the selector. | |
/// | |
/// This method is not object-safe and requires `Self: Sized`. | |
fn abi_decode_boxed(data: &[u8], validate: bool) -> Result<Box<dyn SolCallWrapper>> where Self: Sized; | |
fn as_sol_call<T: SolCall + 'static>(&self) -> Option<&T> where Self: Sized; | |
/// ABI-encodes the return data. | |
/// | |
/// This method is not object-safe and requires `Self: Sized`. | |
fn abi_encode_returns_boxed(e: &dyn Any) -> Result<Vec<u8>> where Self: Sized; | |
/// Returns the `TypeId` of the parameters type. | |
fn parameters_type_id(&self) -> TypeId; | |
/// Returns the `TypeId` of the token type. | |
fn token_type_id(&self) -> TypeId; | |
/// Returns the `TypeId` of the return type. | |
fn return_type_id(&self) -> TypeId; | |
/// Returns the `TypeId` of the return tuple type. | |
fn return_tuple_type_id(&self) -> TypeId; | |
/// Returns the `TypeId` of the return token type. | |
fn return_token_type_id(&self) -> TypeId; | |
} | |
trait AsAny { | |
fn as_any(&self) -> &dyn Any; | |
} | |
impl<T: 'static> AsAny for T { | |
fn as_any(&self) -> &dyn Any { | |
self | |
} | |
} | |
/// Implementation of `SolCallWrapper` for any type `T` that implements `SolCall`. | |
impl<T: SolCall + 'static> SolCallWrapper for T { | |
fn signature(&self) -> &'static str { | |
// Return the static signature string from the SolCall implementation | |
T::SIGNATURE | |
} | |
fn selector(&self) -> [u8; 4] { | |
// Return the 4-byte selector from the SolCall implementation | |
T::SELECTOR | |
} | |
fn abi_encoded_size(&self) -> usize { | |
// Delegate to the abi_encoded_size method of the SolCall implementation | |
self.abi_encoded_size() | |
} | |
fn abi_encode(&self) -> Vec<u8> { | |
// Delegate to the abi_encode method of the SolCall implementation | |
self.abi_encode() | |
} | |
fn abi_encode_raw(&self, out: &mut Vec<u8>) { | |
// Delegate to the abi_encode_raw method of the SolCall implementation | |
self.abi_encode_raw(out) | |
} | |
fn tokenize(&self) -> Box<dyn Any + '_> { | |
// Box the result of tokenize to erase the specific return type | |
Box::new(self.tokenize()) | |
} | |
fn new_boxed(tuple: Box<dyn Any>) -> Result<Box<dyn SolCallWrapper>> where Self: Sized { | |
// Downcast the Any type to the specific parameter type expected by the SolCall implementation | |
let tuple = tuple.downcast::<<T::Parameters<'_> as SolType>::RustType>() | |
.map_err(|_| alloy::sol_types::Error::custom("Type mismatch for Parameters"))?; | |
// Create a new instance of the SolCall implementation and box it as a SolCallWrapper | |
Ok(Box::new(T::new(*tuple))) | |
} | |
fn abi_decode_returns_boxed(data: &[u8], validate: bool) -> Result<Box<dyn Any>> where Self: Sized { | |
// Decode the return data and box the result to erase the specific return type | |
Ok(Box::new(T::abi_decode_returns(data, validate)?)) | |
} | |
fn abi_decode_raw_boxed(data: &[u8], validate: bool) -> Result<Box<dyn SolCallWrapper>> where Self: Sized { | |
// Decode the raw call data and box the result as a SolCallWrapper | |
Ok(Box::new(T::abi_decode_raw(data, validate)?)) | |
} | |
fn abi_decode_boxed(data: &[u8], validate: bool) -> Result<Box<dyn SolCallWrapper>> where Self: Sized { | |
// Decode the full call data (including selector) and box the result as a SolCallWrapper | |
Ok(Box::new(T::abi_decode(data, validate)?)) | |
} | |
fn as_sol_call<U: SolCall + 'static>(&self) -> Option<&U> where Self: Sized { | |
self.as_any().downcast_ref::<U>() | |
} | |
fn abi_encode_returns_boxed(e: &dyn Any) -> Result<Vec<u8>> where Self: Sized { | |
// Downcast the Any type to the specific return tuple type expected by the SolCall implementation | |
let e = e.downcast_ref::<<T::ReturnTuple<'_> as SolType>::RustType>() | |
.ok_or_else(|| alloy::sol_types::Error::custom("Type mismatch for ReturnTuple"))?; | |
// Encode the return data | |
Ok(T::abi_encode_returns(e)) | |
} | |
fn parameters_type_id(&self) -> TypeId { | |
// Return the TypeId of the Parameters associated type | |
TypeId::of::<T::Parameters<'_>>() | |
} | |
fn token_type_id(&self) -> TypeId { | |
// Return the TypeId of the Token associated type | |
TypeId::of::<T::Token<'_>>() | |
} | |
fn return_type_id(&self) -> TypeId { | |
// Return the TypeId of the Return associated type | |
TypeId::of::<T::Return>() | |
} | |
fn return_tuple_type_id(&self) -> TypeId { | |
// Return the TypeId of the ReturnTuple associated type | |
TypeId::of::<T::ReturnTuple<'_>>() | |
} | |
fn return_token_type_id(&self) -> TypeId { | |
// Return the TypeId of the ReturnToken associated type | |
TypeId::of::<T::ReturnToken<'_>>() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment