Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add constructorCall to sol! #493

Merged
merged 2 commits into from
Jan 23, 2024
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
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 @@ -69,6 +69,7 @@ impl ToSol for JsonAbi {
fmt!(its.0);
fmt!(self.errors());
fmt!(self.events());
fmt!(self.constructor());
fmt!(self.fallback);
fmt!(self.receive);
fmt!(self.functions());
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 @@ -84,6 +84,8 @@ interface AggregationRouterV5 {
event OrderFilledRFQ(bytes32 orderHash, uint256 makingAmount);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

constructor(address weth);

receive() external payable;

function advanceNonce(uint8 amount) external;
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 @@ -52,6 +52,8 @@ interface BlurExchange {
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event Upgraded(address indexed implementation);

constructor();

function FEE_TYPEHASH() external view returns (bytes32);
function INVERSE_BASIS_POINT() external view returns (uint256);
function NAME() external view returns (string memory);
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
Expand Up @@ -7,6 +7,8 @@ interface GaugeController {
event NewTypeWeight(int128 type_id, uint256 time, uint256 weight, uint256 total_weight);
event VoteForGauge(uint256 time, address user, address gauge_addr, uint256 weight);

constructor(address _token, address _voting_escrow);

function add_gauge(address addr, int128 gauge_type, uint256 weight) external;
function add_type(string memory _name, uint256 weight) external;
function admin() external view returns (address);
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
Expand Up @@ -14,6 +14,8 @@ interface GnosisSafe {
event RemovedOwner(address owner);
event SignMsg(bytes32 indexed msgHash);

constructor();

fallback() external payable;

function NAME() external view returns (string memory);
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
Expand Up @@ -15,6 +15,8 @@ interface Junkyard {
event PricesChange(uint256, uint256);
event Unpaused(address account);

constructor(address[] jkdPayees, uint256[] jkdShares, address _gateway, address _gasReceiver);

receive() external payable;

function GAS_RECEIVER() external view returns (address);
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
Expand Up @@ -8,6 +8,8 @@ interface LiquidityGaugeV4 {
event UpdateLiquidityLimit(address user, uint256 original_balance, uint256 original_supply, uint256 working_balance, uint256 working_supply);
event Withdraw(address indexed provider, uint256 value);

constructor();

function SDT() external view returns (address);
function accept_transfer_ownership() external;
function add_reward(address _reward_token, address _distributor) external;
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 @@ -165,6 +165,8 @@ interface Seaport {
event OrderValidated(bytes32 orderHash, OrderParameters orderParameters);
event OrdersMatched(bytes32[] orderHashes);

constructor(address conduitController);

receive() external payable;

function cancel(OrderComponents[] memory orders) external returns (bool cancelled);
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,6 +1,8 @@
interface UniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint256);

constructor(address _feeToSetter);

function allPairs(uint256) external view returns (address);
function allPairsLength() external view returns (uint256);
function createPair(address tokenA, address tokenB) external returns (address pair);
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,6 +1,8 @@
interface UniswapV2FactoryWithMigrator {
event PairCreated(address indexed token0, address indexed token1, address pair, uint256);

constructor(address _feeToSetter);

function allPairs(uint256) external view returns (address);
function allPairsLength() external view returns (uint256);
function createPair(address tokenA, address tokenB) external returns (address pair);
Expand Down
2 changes: 2 additions & 0 deletions crates/json-abi/tests/abi/ZRXToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ interface ZRXToken {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
event Transfer(address indexed _from, address indexed _to, uint256 _value);

constructor();

function allowance(address _owner, address _spender) external returns (uint256);
function approve(address _spender, uint256 _value) external returns (bool);
function balanceOf(address _owner) external returns (uint256);
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
Expand Up @@ -5,6 +5,8 @@ interface ZeroXExchange {
event Fill(address indexed makerAddress, address indexed feeRecipientAddress, address takerAddress, address senderAddress, uint256 makerAssetFilledAmount, uint256 takerAssetFilledAmount, uint256 makerFeePaid, uint256 takerFeePaid, bytes32 indexed orderHash, bytes makerAssetData, bytes takerAssetData);
event SignatureValidatorApproval(address indexed signerAddress, address indexed validatorAddress, bool approved);

constructor(bytes _zrxAssetData);

function EIP712_DOMAIN_HASH() external view returns (bytes32);
function VERSION() external view returns (string memory);
function ZRX_ASSET_DATA() external view returns (bytes memory);
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);
}

if name.is_none() {
// 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
35 changes: 35 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,38 @@ 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> {
crate::abi::encode_sequence(&self.tokenize())
}
}
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
Loading