Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 19 additions & 1 deletion crates/json-abi/src/to_sol.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
item::{Error, Event, Fallback, Function, Receive},
item::{Constructor, Error, Event, Fallback, Function, Receive},
EventParam, InternalType, JsonAbi, Param, StateMutability,
};
use alloc::{collections::BTreeSet, string::String, vec::Vec};
Expand Down Expand Up @@ -67,6 +67,7 @@ impl ToSol for JsonAbi {
let mut its = InternalTypes::new();
its.visit_abi(self);
fmt!(its.0);
fmt!(self.constructor());
fmt!(self.errors());
fmt!(self.events());
fmt!(self.fallback);
Expand Down Expand Up @@ -233,6 +234,21 @@ impl ToSol for It<'_> {
}
}

impl ToSol for Constructor {
fn to_sol(&self, out: &mut SolPrinter<'_>) {
AbiFunction::<'_, Param> {
kw: AbiFunctionKw::Constructor,
name: None,
inputs: &self.inputs,
visibility: None,
state_mutability: Some(self.state_mutability),
anonymous: false,
outputs: &[],
}
.to_sol(out);
}
}

impl ToSol for Event {
fn to_sol(&self, out: &mut SolPrinter<'_>) {
AbiFunction::<'_, EventParam> {
Expand Down Expand Up @@ -319,6 +335,7 @@ struct AbiFunction<'a, IN> {
}

enum AbiFunctionKw {
Constructor,
Function,
Fallback,
Receive,
Expand All @@ -330,6 +347,7 @@ impl AbiFunctionKw {
#[inline]
const fn as_str(&self) -> &'static str {
match self {
Self::Constructor => "constructor",
Self::Function => "function",
Self::Fallback => "fallback",
Self::Receive => "receive",
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/AggregationRouterV5.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ interface AggregationRouterV5 {
uint256 flags;
}

constructor(address weth);

error AccessDenied();
error AdvanceNonceFailed();
error AlreadyFilled();
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/BlurExchange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ interface BlurExchange {
bytes extraParams;
}

constructor();

event AdminChanged(address previousAdmin, address newAdmin);
event BeaconUpgraded(address indexed beacon);
event Closed();
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/DoubleExponentInterestSetter.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
interface DoubleExponentInterestSetter {
constructor((uint128, uint128) params);

function getCoefficients() external view returns (uint256[] memory);
function getInterestRate(address, uint256 borrowWei, uint256 supplyWei) external view returns ((uint256,) memory);
function getMaxAPR() external view returns (uint256);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/GaugeController.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
interface GaugeController {
constructor(address _token, address _voting_escrow);

event AddType(string name, int128 type_id);
event ApplyOwnership(address admin);
event CommitOwnership(address admin);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/GnosisSafe.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
interface GnosisSafe {
type Operation is uint8;

constructor();

event AddedOwner(address owner);
event ApproveHash(bytes32 indexed approvedHash, address indexed owner);
event ChangedMasterCopy(address masterCopy);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/Junkyard.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
interface Junkyard {
constructor(address[] jkdPayees, uint256[] jkdShares, address _gateway, address _gasReceiver);

error InvalidAddress();
error InvalidAddressString();
error NotApprovedByGateway();
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/LiquidityGaugeV4.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
interface LiquidityGaugeV4 {
constructor();

event ApplyOwnership(address admin);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
event CommitOwnership(address admin);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/Seaport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ interface Seaport {
uint256 amount;
}

constructor(address conduitController);

error BadContractSignature();
error BadFraction();
error BadReturnValueFromERC20OnTransfer(address token, address from, address to, uint256 amount);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/UniswapV2Factory.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
interface UniswapV2Factory {
constructor(address _feeToSetter);

event PairCreated(address indexed token0, address indexed token1, address pair, uint256);

function allPairs(uint256) external view returns (address);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/UniswapV2FactoryWithMigrator.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
interface UniswapV2FactoryWithMigrator {
constructor(address _feeToSetter);

event PairCreated(address indexed token0, address indexed token1, address pair, uint256);

function allPairs(uint256) external view returns (address);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/ZeroXExchange.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
interface ZeroXExchange {
constructor(bytes _zrxAssetData);

event AssetProxyRegistered(bytes4 id, address assetProxy);
event Cancel(address indexed makerAddress, address indexed feeRecipientAddress, address senderAddress, bytes32 indexed orderHash, bytes makerAssetData, bytes takerAssetData);
event CancelUpTo(address indexed makerAddress, address indexed senderAddress, uint256 orderEpoch);
Expand Down
63 changes: 59 additions & 4 deletions crates/sol-macro/src/expand/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

use super::{expand_fields, expand_from_into_tuples, expand_tokenize, expand_tuple_types, ExpCtxt};
use crate::attr;
use ast::ItemFunction;
use ast::{FunctionKind, ItemFunction};
use proc_macro2::TokenStream;
use quote::quote;
use quote::{format_ident, quote};
use syn::Result;

/// Expands an [`ItemFunction`]:
Expand All @@ -24,10 +24,17 @@ use syn::Result;
/// }
/// ```
pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenStream> {
let ItemFunction { attrs, parameters, returns, name: Some(_), .. } = function else {
// ignore functions without names (constructors, modifiers...)
let ItemFunction { attrs, parameters, returns, name, kind, .. } = function;

if matches!(kind, FunctionKind::Constructor(_)) {
return expand_constructor(cx, function);
}

let Option::Some(_) = name else {
// ignore functions without names (modifiers...)
return Ok(quote!());
};

let returns = returns.as_ref().map(|r| &r.returns).unwrap_or_default();

cx.assert_resolved(parameters)?;
Expand Down Expand Up @@ -143,3 +150,51 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenS
};
Ok(tokens)
}

fn expand_constructor(cx: &ExpCtxt<'_>, constructor: &ItemFunction) -> Result<TokenStream> {
let ItemFunction { attrs, parameters, .. } = constructor;

let (sol_attrs, call_attrs) = crate::attr::SolAttrs::parse(attrs)?;
let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
let call_name = format_ident!("constructorCall");
let call_fields = expand_fields(parameters);
let call_tuple = expand_tuple_types(parameters.types()).0;
let converts = expand_from_into_tuples(&call_name, parameters);
let tokenize_impl = expand_tokenize(parameters);

let call_doc = docs.then(|| {
attr::mk_doc(format!(
"Constructor`.\n\
```solidity\n{constructor}\n```"
))
});

let tokens = quote! {
#(#call_attrs)*
#call_doc
#[allow(non_camel_case_types, non_snake_case)]
#[derive(Clone)]
pub struct #call_name {
#(#call_fields),*
}

const _: () = {
{ #converts }

#[automatically_derived]
impl ::alloy_sol_types::SolConstructor for #call_name {
type Parameters<'a> = #call_tuple;
type Token<'a> = <Self::Parameters<'a> as ::alloy_sol_types::SolType>::Token<'a>;

fn new<'a>(tuple: <Self::Parameters<'a> as ::alloy_sol_types::SolType>::RustType) -> Self {
tuple.into()
}

fn tokenize(&self) -> Self::Token<'_> {
#tokenize_impl
}
}
};
};
Ok(tokens)
}
4 changes: 2 additions & 2 deletions crates/sol-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ mod impl_core;
mod types;
pub use types::{
data_type as sol_data, decode_revert_reason, ContractError, EventTopic, GenericContractError,
GenericRevertReason, Panic, PanicKind, Revert, Selectors, SolCall, SolEnum, SolError, SolEvent,
SolEventInterface, SolInterface, SolStruct, SolType, SolValue, TopicList,
GenericRevertReason, Panic, PanicKind, Revert, Selectors, SolCall, SolConstructor, SolEnum,
SolError, SolEvent, SolEventInterface, SolInterface, SolStruct, SolType, SolValue, TopicList,
};

pub mod utils;
Expand Down
38 changes: 38 additions & 0 deletions crates/sol-types/src/types/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,41 @@ pub trait SolCall: Sized {
crate::abi::encode_sequence(&e.stv_to_tokens())
}
}

/// A Solidity constructor.
pub trait SolConstructor: Sized {
/// The underlying tuple type which represents this type's arguments.
///
/// If this type has no arguments, this will be the unit type `()`.
type Parameters<'a>: SolType<Token<'a> = Self::Token<'a>>;

/// The arguments' corresponding [TokenSeq] type.
type Token<'a>: TokenSeq<'a>;

/// Convert from the tuple type used for ABI encoding and decoding.
fn new(tuple: <Self::Parameters<'_> as SolType>::RustType) -> Self;

/// Tokenize the call's arguments.
fn tokenize(&self) -> Self::Token<'_>;

/// The size of the encoded data in bytes.
#[inline]
fn abi_encoded_size(&self) -> usize {
if let Some(size) = <Self::Parameters<'_> as SolType>::ENCODED_SIZE {
return size;
}

// `total_words` includes the first dynamic offset which we ignore.
let offset = <<Self::Parameters<'_> as SolType>::Token<'_> as Token>::DYNAMIC as usize * 32;
(self.tokenize().total_words() * Word::len_bytes()).saturating_sub(offset)
}

/// ABI encode the call to the given buffer.
#[inline]
fn abi_encode(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.abi_encoded_size());
out.reserve(self.abi_encoded_size());
out.extend(crate::abi::encode_sequence(&self.tokenize()));
out
}
}
2 changes: 1 addition & 1 deletion crates/sol-types/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod event;
pub use event::{EventTopic, SolEvent, TopicList};

mod function;
pub use function::SolCall;
pub use function::{SolCall, SolConstructor};

mod interface;
pub use interface::{
Expand Down
26 changes: 19 additions & 7 deletions crates/sol-types/tests/doctests/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use alloy_primitives::{address, hex, U256};
use alloy_sol_types::{sol, SolCall, SolInterface};
use alloy_sol_types::{sol, SolCall, SolConstructor, SolInterface};

sol! {
/// Interface of the ERC20 standard as defined in [the EIP].
///
/// [the EIP]: https://eips.ethereum.org/EIPS/eip-20
#[derive(Debug, PartialEq, Eq)]
interface IERC20 {
contract ERC20 {
constructor(string name, string symbol);

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

Expand All @@ -20,21 +22,31 @@ sol! {
}

#[test]
fn contracts() {
fn constructor() {
let constructor_args =
ERC20::constructorCall::new((String::from("Wrapped Ether"), String::from("WETH")))
.abi_encode();
let constructor_args_expected = hex!("00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d577261707065642045746865720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045745544800000000000000000000000000000000000000000000000000000000");

assert_eq!(constructor_args.as_slice(), constructor_args_expected);
}

#[test]
fn transfer() {
// random mainnet ERC20 transfer
// https://etherscan.io/tx/0x947332ff624b5092fb92e8f02cdbb8a50314e861a4b39c29a286b3b75432165e
let data = hex!(
"a9059cbb"
"0000000000000000000000008bc47be1e3abbaba182069c89d08a61fa6c2b292"
"0000000000000000000000000000000000000000000000000000000253c51700"
);
let expected = IERC20::transferCall {
let expected = ERC20::transferCall {
to: address!("8bc47be1e3abbaba182069c89d08a61fa6c2b292"),
amount: U256::from(9995360000_u64),
};

assert_eq!(data[..4], IERC20::transferCall::SELECTOR);
let decoded = IERC20::IERC20Calls::abi_decode(&data, true).unwrap();
assert_eq!(decoded, IERC20::IERC20Calls::transfer(expected));
assert_eq!(data[..4], ERC20::transferCall::SELECTOR);
let decoded = ERC20::ERC20Calls::abi_decode(&data, true).unwrap();
assert_eq!(decoded, ERC20::ERC20Calls::transfer(expected));
assert_eq!(decoded.abi_encode(), data);
}
3 changes: 3 additions & 0 deletions crates/sol-types/tests/macros/sol/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ fn seaport() {
sol!(Seaport, "../json-abi/tests/abi/Seaport.json");
use Seaport::*;

// Constructor with a single argument
let _ = constructorCall { conduitController: Address::ZERO };

// BasicOrderType is a uint8 UDVT
let _ = BasicOrderType::from(0u8);

Expand Down