Skip to content

Instantly share code, notes, and snippets.

@0xAlcibiades
Created October 10, 2024 22:59
Show Gist options
  • Save 0xAlcibiades/4b8c32af8e30212f428670a586507a61 to your computer and use it in GitHub Desktop.
Save 0xAlcibiades/4b8c32af8e30212f428670a586507a61 to your computer and use it in GitHub Desktop.
//! 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