From d0426c6d2c0399b9529a09e2381814ac17baf526 Mon Sep 17 00:00:00 2001 From: voith Date: Tue, 9 Dec 2025 19:28:16 +0530 Subject: [PATCH 1/5] handle globals separately when emitting JSON ABI to solidity --- crates/json-abi/src/to_sol.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/json-abi/src/to_sol.rs b/crates/json-abi/src/to_sol.rs index 2e159a1d0..0614f90c8 100644 --- a/crates/json-abi/src/to_sol.rs +++ b/crates/json-abi/src/to_sol.rs @@ -220,6 +220,11 @@ impl JsonAbi { out.pop(); // trailing newline out.push('}'); + if !its.globals.is_empty() { + out.push('\n'); + fmt!(its.globals); + out.pop(); // trailing newline + } } } @@ -228,13 +233,20 @@ struct InternalTypes<'a> { name: &'a str, this_its: BTreeSet>, other: BTreeMap<&'a String, BTreeSet>>, + globals: BTreeSet>, enums_as_udvt: bool, } impl<'a> InternalTypes<'a> { #[allow(clippy::missing_const_for_fn)] fn new(name: &'a str, enums_as_udvt: bool) -> Self { - Self { name, this_its: BTreeSet::new(), other: BTreeMap::new(), enums_as_udvt } + Self { + name, + this_its: BTreeSet::new(), + other: BTreeMap::new(), + globals: BTreeSet::new(), + enums_as_udvt, + } } fn visit_abi(&mut self, abi: &'a JsonAbi) { @@ -313,7 +325,7 @@ impl<'a> InternalTypes<'a> { self.other.entry(contract).or_default().insert(it); } } else { - self.this_its.insert(it); + self.globals.insert(it); } } } From 4a2884d366e4dc69aa69770be0ca9ee484c2e1a4 Mon Sep 17 00:00:00 2001 From: voith Date: Thu, 11 Dec 2025 16:00:13 +0530 Subject: [PATCH 2/5] Improve JsonAbi printer indentation handling for globals and nested items --- crates/json-abi/src/to_sol.rs | 16 +- crates/json-abi/tests/abi/BlurExchange.sol | 72 +++--- crates/json-abi/tests/abi/Bootstrap.sol | 14 +- .../json-abi/tests/abi/DelegationManager.sol | 6 +- crates/json-abi/tests/abi/Fastlane.sol | 50 ++-- crates/json-abi/tests/abi/Seaport.sol | 230 +++++++++--------- .../json-abi/tests/abi/UniswapV3Position.sol | 14 +- crates/json-abi/tests/abi/WithStructs.sol | 58 ++--- 8 files changed, 235 insertions(+), 225 deletions(-) diff --git a/crates/json-abi/src/to_sol.rs b/crates/json-abi/src/to_sol.rs index 0614f90c8..1b3f2dc73 100644 --- a/crates/json-abi/src/to_sol.rs +++ b/crates/json-abi/src/to_sol.rs @@ -92,6 +92,9 @@ pub(crate) struct SolPrinter<'a> { /// Configuration. config: ToSolConfig, + + /// Current indentation level. + indent_level: usize, } impl Deref for SolPrinter<'_> { @@ -112,7 +115,7 @@ impl DerefMut for SolPrinter<'_> { impl<'a> SolPrinter<'a> { pub(crate) fn new(s: &'a mut String, name: &'a str, config: ToSolConfig) -> Self { - Self { s, name, print_param_location: false, config } + Self { s, name, print_param_location: false, config, indent_level: 0 } } pub(crate) fn print(&mut self, abi: &'a JsonAbi) { @@ -120,7 +123,9 @@ impl<'a> SolPrinter<'a> { } fn indent(&mut self) { - self.push_str(" "); + for _ in 0..self.indent_level { + self.push_str(" "); + } } /// Normalizes `s` as a Rust identifier and pushes it to the buffer. @@ -177,11 +182,13 @@ impl JsonAbi { out.push_str(name); out.push_str(" {\n"); let prev = core::mem::replace(&mut out.name, name); + out.indent_level += 1; for it in its { out.indent(); it.to_sol(out); out.push('\n'); } + out.indent_level -= 1; out.name = prev; out.push_str("}\n\n"); } @@ -195,6 +202,7 @@ impl JsonAbi { out.push('{'); out.push('\n'); + out.indent_level += 1; if one_contract { for (name, its) in &its.other { if its.is_empty() { @@ -220,6 +228,7 @@ impl JsonAbi { out.pop(); // trailing newline out.push('}'); + out.indent_level -= 1; if !its.globals.is_empty() { out.push('\n'); fmt!(its.globals); @@ -400,12 +409,13 @@ impl ToSol for It<'_> { out.push_str("struct "); out.push_ident(self.name); out.push_str(" {\n"); + out.indent_level += 1; for component in components { - out.indent(); out.indent(); component.to_sol(out); out.push_str(";\n"); } + out.indent_level -= 1; out.indent(); out.push('}'); } diff --git a/crates/json-abi/tests/abi/BlurExchange.sol b/crates/json-abi/tests/abi/BlurExchange.sol index fd66afa69..e80a564f6 100644 --- a/crates/json-abi/tests/abi/BlurExchange.sol +++ b/crates/json-abi/tests/abi/BlurExchange.sol @@ -1,39 +1,4 @@ interface BlurExchange { - type Side is uint8; - type SignatureVersion is uint8; - struct Execution { - Input sell; - Input buy; - } - struct Fee { - uint16 rate; - address payable recipient; - } - struct Input { - Order order; - uint8 v; - bytes32 r; - bytes32 s; - bytes extraSignature; - SignatureVersion signatureVersion; - uint256 blockNumber; - } - struct Order { - address trader; - Side side; - address matchingPolicy; - address collection; - uint256 tokenId; - uint256 amount; - address paymentToken; - uint256 price; - uint256 listingTime; - uint256 expirationTime; - Fee[] fees; - uint256 salt; - bytes extraParams; - } - event AdminChanged(address previousAdmin, address newAdmin); event BeaconUpgraded(address indexed beacon); event Closed(); @@ -95,4 +60,39 @@ interface BlurExchange { function transferOwnership(address newOwner) external; function upgradeTo(address newImplementation) external; function upgradeToAndCall(address newImplementation, bytes memory data) external payable; -} \ No newline at end of file +} +type Side is uint8; +type SignatureVersion is uint8; +struct Execution { + Input sell; + Input buy; +} +struct Fee { + uint16 rate; + address payable recipient; +} +struct Input { + Order order; + uint8 v; + bytes32 r; + bytes32 s; + bytes extraSignature; + SignatureVersion signatureVersion; + uint256 blockNumber; +} +struct Order { + address trader; + Side side; + address matchingPolicy; + address collection; + uint256 tokenId; + uint256 amount; + address paymentToken; + uint256 price; + uint256 listingTime; + uint256 expirationTime; + Fee[] fees; + uint256 salt; + bytes extraParams; +} + diff --git a/crates/json-abi/tests/abi/Bootstrap.sol b/crates/json-abi/tests/abi/Bootstrap.sol index b449f8c89..9f28bc865 100644 --- a/crates/json-abi/tests/abi/Bootstrap.sol +++ b/crates/json-abi/tests/abi/Bootstrap.sol @@ -6,12 +6,6 @@ library ModuleManager { } interface Bootstrap { - type CallType is bytes1; - struct BootstrapConfig { - address module; - bytes data; - } - error AccountAccessUnauthorized(); error CannotRemoveLastValidator(); error HookAlreadyInstalled(address currentHook); @@ -34,4 +28,10 @@ interface Bootstrap { function getValidatorsPaginated(address cursor, uint256 size) external view returns (address[] memory array, address next); function initMSA(BootstrapConfig[] memory _valdiators, BootstrapConfig[] memory _executors, BootstrapConfig memory _hook, BootstrapConfig[] memory _fallbacks) external; function singleInitMSA(address validator, bytes memory data) external; -} \ No newline at end of file +} +type CallType is bytes1; +struct BootstrapConfig { + address module; + bytes data; +} + diff --git a/crates/json-abi/tests/abi/DelegationManager.sol b/crates/json-abi/tests/abi/DelegationManager.sol index ba48a7514..68708d871 100644 --- a/crates/json-abi/tests/abi/DelegationManager.sol +++ b/crates/json-abi/tests/abi/DelegationManager.sol @@ -28,8 +28,6 @@ library ISignatureUtils { } interface DelegationManager { - type DelegatedShares is uint256; - error ActivelyDelegated(); error AllocationDelaySet(); error CallerCannotUndelegate(); @@ -128,4 +126,6 @@ interface DelegationManager { function undelegate(address staker) external returns (bytes32[] memory withdrawalRoots); function unpause(uint256 newPausedStatus) external; function updateOperatorMetadataURI(string memory metadataURI) external; -} \ No newline at end of file +} +type DelegatedShares is uint256; + diff --git a/crates/json-abi/tests/abi/Fastlane.sol b/crates/json-abi/tests/abi/Fastlane.sol index bf46652c2..a39b7c857 100644 --- a/crates/json-abi/tests/abi/Fastlane.sol +++ b/crates/json-abi/tests/abi/Fastlane.sol @@ -1,28 +1,4 @@ interface Fastlane { - type statusType is uint8; - struct Bid { - address validatorAddress; - address opportunityAddress; - address searcherContractAddress; - address searcherPayableAddress; - uint256 bidAmount; - } - struct Status { - uint128 activeAtAuction; - uint128 inactiveAtAuction; - statusType kind; - } - struct ValidatorBalanceCheckpoint { - uint256 pendingBalanceAtlastBid; - uint256 outstandingBalance; - uint128 lastWithdrawnAuction; - uint128 lastBidReceivedAuction; - } - struct ValidatorPreferences { - uint256 minAutoshipAmount; - address validatorPayableAddress; - } - event AuctionEnded(uint128 indexed auction_number); event AuctionStarted(uint128 indexed auction_number); event AuctionStarterSet(address indexed starter); @@ -94,4 +70,28 @@ interface Fastlane { function transferOwnership(address newOwner) external; function withdrawStuckERC20(address _tokenAddress) external; function withdrawStuckNativeToken(uint256 amount) external; -} \ No newline at end of file +} +type statusType is uint8; +struct Bid { + address validatorAddress; + address opportunityAddress; + address searcherContractAddress; + address searcherPayableAddress; + uint256 bidAmount; +} +struct Status { + uint128 activeAtAuction; + uint128 inactiveAtAuction; + statusType kind; +} +struct ValidatorBalanceCheckpoint { + uint256 pendingBalanceAtlastBid; + uint256 outstandingBalance; + uint128 lastWithdrawnAuction; + uint128 lastBidReceivedAuction; +} +struct ValidatorPreferences { + uint256 minAutoshipAmount; + address validatorPayableAddress; +} + diff --git a/crates/json-abi/tests/abi/Seaport.sol b/crates/json-abi/tests/abi/Seaport.sol index 8af2cb2b7..c262e88fa 100644 --- a/crates/json-abi/tests/abi/Seaport.sol +++ b/crates/json-abi/tests/abi/Seaport.sol @@ -1,118 +1,4 @@ interface Seaport { - type BasicOrderType is uint8; - type ItemType is uint8; - type OrderType is uint8; - type Side is uint8; - struct AdditionalRecipient { - uint256 amount; - address payable recipient; - } - struct AdvancedOrder { - OrderParameters parameters; - uint120 numerator; - uint120 denominator; - bytes signature; - bytes extraData; - } - struct BasicOrderParameters { - address considerationToken; - uint256 considerationIdentifier; - uint256 considerationAmount; - address payable offerer; - address zone; - address offerToken; - uint256 offerIdentifier; - uint256 offerAmount; - BasicOrderType basicOrderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 offererConduitKey; - bytes32 fulfillerConduitKey; - uint256 totalOriginalAdditionalRecipients; - AdditionalRecipient[] additionalRecipients; - bytes signature; - } - struct ConsiderationItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; - address payable recipient; - } - struct CriteriaResolver { - uint256 orderIndex; - Side side; - uint256 index; - uint256 identifier; - bytes32[] criteriaProof; - } - struct Execution { - ReceivedItem item; - address offerer; - bytes32 conduitKey; - } - struct Fulfillment { - FulfillmentComponent[] offerComponents; - FulfillmentComponent[] considerationComponents; - } - struct FulfillmentComponent { - uint256 orderIndex; - uint256 itemIndex; - } - struct OfferItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; - } - struct Order { - OrderParameters parameters; - bytes signature; - } - struct OrderComponents { - address offerer; - address zone; - OfferItem[] offer; - ConsiderationItem[] consideration; - OrderType orderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 conduitKey; - uint256 counter; - } - struct OrderParameters { - address offerer; - address zone; - OfferItem[] offer; - ConsiderationItem[] consideration; - OrderType orderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 conduitKey; - uint256 totalOriginalConsiderationItems; - } - struct ReceivedItem { - ItemType itemType; - address token; - uint256 identifier; - uint256 amount; - address payable recipient; - } - struct SpentItem { - ItemType itemType; - address token; - uint256 identifier; - uint256 amount; - } - error BadContractSignature(); error BadFraction(); error BadReturnValueFromERC20OnTransfer(address token, address from, address to, uint256 amount); @@ -184,4 +70,118 @@ interface Seaport { function matchOrders(Order[] memory, Fulfillment[] memory) external payable returns (Execution[] memory); function name() external pure returns (string memory); function validate(Order[] memory) external returns (bool); -} \ No newline at end of file +} +type BasicOrderType is uint8; +type ItemType is uint8; +type OrderType is uint8; +type Side is uint8; +struct AdditionalRecipient { + uint256 amount; + address payable recipient; +} +struct AdvancedOrder { + OrderParameters parameters; + uint120 numerator; + uint120 denominator; + bytes signature; + bytes extraData; +} +struct BasicOrderParameters { + address considerationToken; + uint256 considerationIdentifier; + uint256 considerationAmount; + address payable offerer; + address zone; + address offerToken; + uint256 offerIdentifier; + uint256 offerAmount; + BasicOrderType basicOrderType; + uint256 startTime; + uint256 endTime; + bytes32 zoneHash; + uint256 salt; + bytes32 offererConduitKey; + bytes32 fulfillerConduitKey; + uint256 totalOriginalAdditionalRecipients; + AdditionalRecipient[] additionalRecipients; + bytes signature; +} +struct ConsiderationItem { + ItemType itemType; + address token; + uint256 identifierOrCriteria; + uint256 startAmount; + uint256 endAmount; + address payable recipient; +} +struct CriteriaResolver { + uint256 orderIndex; + Side side; + uint256 index; + uint256 identifier; + bytes32[] criteriaProof; +} +struct Execution { + ReceivedItem item; + address offerer; + bytes32 conduitKey; +} +struct Fulfillment { + FulfillmentComponent[] offerComponents; + FulfillmentComponent[] considerationComponents; +} +struct FulfillmentComponent { + uint256 orderIndex; + uint256 itemIndex; +} +struct OfferItem { + ItemType itemType; + address token; + uint256 identifierOrCriteria; + uint256 startAmount; + uint256 endAmount; +} +struct Order { + OrderParameters parameters; + bytes signature; +} +struct OrderComponents { + address offerer; + address zone; + OfferItem[] offer; + ConsiderationItem[] consideration; + OrderType orderType; + uint256 startTime; + uint256 endTime; + bytes32 zoneHash; + uint256 salt; + bytes32 conduitKey; + uint256 counter; +} +struct OrderParameters { + address offerer; + address zone; + OfferItem[] offer; + ConsiderationItem[] consideration; + OrderType orderType; + uint256 startTime; + uint256 endTime; + bytes32 zoneHash; + uint256 salt; + bytes32 conduitKey; + uint256 totalOriginalConsiderationItems; +} +struct ReceivedItem { + ItemType itemType; + address token; + uint256 identifier; + uint256 amount; + address payable recipient; +} +struct SpentItem { + ItemType itemType; + address token; + uint256 identifier; + uint256 amount; +} + diff --git a/crates/json-abi/tests/abi/UniswapV3Position.sol b/crates/json-abi/tests/abi/UniswapV3Position.sol index 36f13880b..db59c824e 100644 --- a/crates/json-abi/tests/abi/UniswapV3Position.sol +++ b/crates/json-abi/tests/abi/UniswapV3Position.sol @@ -1,11 +1,11 @@ interface UniswapV3Position { - struct Range { - int24 lowerTick; - int24 upperTick; - uint24 feeTier; - } - function getLiquidityByRange(address pool_, address self_, int24 lowerTick_, int24 upperTick_) external view returns (uint128 liquidity); function getPositionId(address self_, int24 lowerTick_, int24 upperTick_) external pure returns (bytes32 positionId); function rangeExists(Range[] memory currentRanges_, Range memory range_) external pure returns (bool ok, uint256 index); -} \ No newline at end of file +} +struct Range { + int24 lowerTick; + int24 upperTick; + uint24 feeTier; +} + diff --git a/crates/json-abi/tests/abi/WithStructs.sol b/crates/json-abi/tests/abi/WithStructs.sol index 0c9acd960..4395dacc5 100644 --- a/crates/json-abi/tests/abi/WithStructs.sol +++ b/crates/json-abi/tests/abi/WithStructs.sol @@ -1,32 +1,4 @@ interface WithStructs { - type statusType is uint8; - struct Bid { - address validatorAddress; - address opportunityAddress; - address searcherContractAddress; - address searcherPayableAddress; - uint256 bidAmount; - } - struct NFToken { - address implem; - uint256 id; - } - struct Status { - uint128 activeAtAuction; - uint128 inactiveAtAuction; - statusType kind; - } - struct ValidatorBalanceCheckpoint { - uint256 pendingBalanceAtlastBid; - uint256 outstandingBalance; - uint128 lastWithdrawnAuction; - uint128 lastBidReceivedAuction; - } - struct ValidatorPreferences { - uint256 minAutoshipAmount; - address validatorPayableAddress; - } - event AuctionEnded(uint128 indexed auction_number); event AuctionStarted(uint128 indexed auction_number); event AuctionStarterSet(address indexed starter); @@ -101,4 +73,32 @@ interface WithStructs { function viewNFToken() external returns (NFToken memory nft); function withdrawStuckERC20(address _tokenAddress) external; function withdrawStuckNativeToken(uint256 amount) external; -} \ No newline at end of file +} +type statusType is uint8; +struct Bid { + address validatorAddress; + address opportunityAddress; + address searcherContractAddress; + address searcherPayableAddress; + uint256 bidAmount; +} +struct NFToken { + address implem; + uint256 id; +} +struct Status { + uint128 activeAtAuction; + uint128 inactiveAtAuction; + statusType kind; +} +struct ValidatorBalanceCheckpoint { + uint256 pendingBalanceAtlastBid; + uint256 outstandingBalance; + uint128 lastWithdrawnAuction; + uint128 lastBidReceivedAuction; +} +struct ValidatorPreferences { + uint256 minAutoshipAmount; + address validatorPayableAddress; +} + From 00b15c3c12b206a9cd268b6a2a817a55a57e201c Mon Sep 17 00:00:00 2001 From: voith Date: Fri, 12 Dec 2025 09:02:51 +0530 Subject: [PATCH 3/5] Propagate sol! derive attrs to non-interface JSON ABI items --- crates/json-abi/tests/abi/BlurExchange.sol | 1 - crates/json-abi/tests/abi/Bootstrap.sol | 1 - .../tests/abi/ContractUsingGlobals.json | 88 +++++++++ .../tests/abi/ContractUsingGlobals.sol | 21 +++ .../json-abi/tests/abi/DelegationManager.sol | 1 - crates/json-abi/tests/abi/Fastlane.sol | 1 - crates/json-abi/tests/abi/Seaport.sol | 1 - .../json-abi/tests/abi/UniswapV3Position.sol | 1 - crates/json-abi/tests/abi/WithStructs.sol | 1 - crates/sol-macro-input/src/json.rs | 169 +++++++++++------- crates/sol-types/tests/macros/sol/json.rs | 64 ++++++- 11 files changed, 268 insertions(+), 81 deletions(-) create mode 100644 crates/json-abi/tests/abi/ContractUsingGlobals.json create mode 100644 crates/json-abi/tests/abi/ContractUsingGlobals.sol diff --git a/crates/json-abi/tests/abi/BlurExchange.sol b/crates/json-abi/tests/abi/BlurExchange.sol index e80a564f6..16d2ded48 100644 --- a/crates/json-abi/tests/abi/BlurExchange.sol +++ b/crates/json-abi/tests/abi/BlurExchange.sol @@ -95,4 +95,3 @@ struct Order { uint256 salt; bytes extraParams; } - diff --git a/crates/json-abi/tests/abi/Bootstrap.sol b/crates/json-abi/tests/abi/Bootstrap.sol index 9f28bc865..c20161a1a 100644 --- a/crates/json-abi/tests/abi/Bootstrap.sol +++ b/crates/json-abi/tests/abi/Bootstrap.sol @@ -34,4 +34,3 @@ struct BootstrapConfig { address module; bytes data; } - diff --git a/crates/json-abi/tests/abi/ContractUsingGlobals.json b/crates/json-abi/tests/abi/ContractUsingGlobals.json new file mode 100644 index 000000000..bfa337389 --- /dev/null +++ b/crates/json-abi/tests/abi/ContractUsingGlobals.json @@ -0,0 +1,88 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "enum GlobalEnum", + "name": "enum_", + "type": "uint8" + } + ], + "internalType": "struct GlobalStruct", + "name": "payload", + "type": "tuple" + } + ], + "name": "GlobalError", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "enum GlobalEnum", + "name": "enum_", + "type": "uint8" + } + ], + "indexed": false, + "internalType": "struct GlobalStruct", + "name": "payload", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "GlobalUDT", + "name": "amount", + "type": "uint256" + } + ], + "name": "GlobalEvent", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum GlobalEnum", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "internalType": "struct Interface.InterfaceStruct", + "name": "data", + "type": "tuple" + } + ], + "name": "emitGlobalEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "triggerError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/crates/json-abi/tests/abi/ContractUsingGlobals.sol b/crates/json-abi/tests/abi/ContractUsingGlobals.sol new file mode 100644 index 000000000..2bfc32f4d --- /dev/null +++ b/crates/json-abi/tests/abi/ContractUsingGlobals.sol @@ -0,0 +1,21 @@ +library Interface { + struct InterfaceStruct { + GlobalEnum kind; + uint256 count; + } +} + +interface ContractUsingGlobals { + error GlobalError(GlobalStruct payload); + + event GlobalEvent(GlobalStruct payload, GlobalUDT amount); + + function emitGlobalEvent(Interface.InterfaceStruct memory data) external; + function triggerError() external pure; +} +type GlobalEnum is uint8; +type GlobalUDT is uint256; +struct GlobalStruct { + uint256 value; + GlobalEnum enum_; +} diff --git a/crates/json-abi/tests/abi/DelegationManager.sol b/crates/json-abi/tests/abi/DelegationManager.sol index 68708d871..bf622c221 100644 --- a/crates/json-abi/tests/abi/DelegationManager.sol +++ b/crates/json-abi/tests/abi/DelegationManager.sol @@ -128,4 +128,3 @@ interface DelegationManager { function updateOperatorMetadataURI(string memory metadataURI) external; } type DelegatedShares is uint256; - diff --git a/crates/json-abi/tests/abi/Fastlane.sol b/crates/json-abi/tests/abi/Fastlane.sol index a39b7c857..a3f942d25 100644 --- a/crates/json-abi/tests/abi/Fastlane.sol +++ b/crates/json-abi/tests/abi/Fastlane.sol @@ -94,4 +94,3 @@ struct ValidatorPreferences { uint256 minAutoshipAmount; address validatorPayableAddress; } - diff --git a/crates/json-abi/tests/abi/Seaport.sol b/crates/json-abi/tests/abi/Seaport.sol index c262e88fa..c3b057f85 100644 --- a/crates/json-abi/tests/abi/Seaport.sol +++ b/crates/json-abi/tests/abi/Seaport.sol @@ -184,4 +184,3 @@ struct SpentItem { uint256 identifier; uint256 amount; } - diff --git a/crates/json-abi/tests/abi/UniswapV3Position.sol b/crates/json-abi/tests/abi/UniswapV3Position.sol index db59c824e..aaabcfe9d 100644 --- a/crates/json-abi/tests/abi/UniswapV3Position.sol +++ b/crates/json-abi/tests/abi/UniswapV3Position.sol @@ -8,4 +8,3 @@ struct Range { int24 upperTick; uint24 feeTier; } - diff --git a/crates/json-abi/tests/abi/WithStructs.sol b/crates/json-abi/tests/abi/WithStructs.sol index 4395dacc5..e4c64a254 100644 --- a/crates/json-abi/tests/abi/WithStructs.sol +++ b/crates/json-abi/tests/abi/WithStructs.sol @@ -101,4 +101,3 @@ struct ValidatorPreferences { uint256 minAutoshipAmount; address validatorPayableAddress; } - diff --git a/crates/sol-macro-input/src/json.rs b/crates/sol-macro-input/src/json.rs index 13906b0ac..61a11b1a8 100644 --- a/crates/sol-macro-input/src/json.rs +++ b/crates/sol-macro-input/src/json.rs @@ -18,7 +18,15 @@ impl SolInput { let mut abi = abi.ok_or_else(|| syn::Error::new(name.span(), "ABI not found in JSON"))?; let sol = abi_to_sol(&name, &mut abi); - let mut all_tokens = tokens_for_sol(&name, &sol)?.into_iter(); + let all_tokens = tokens_for_sol(&name, &sol)?; + let mut ast: ast::File = syn::parse2(all_tokens).map_err(|e| { + let msg = format!( + "failed to parse ABI-generated tokens into a Solidity AST for `{name}`: {e}.\n\ + This is a bug. We would appreciate a bug report: \ + https://github.com/alloy-rs/core/issues/new/choose" + ); + syn::Error::new(name.span(), msg) + })?; let (inner_attrs, attrs) = attrs .into_iter() @@ -26,34 +34,6 @@ impl SolInput { let (derives, sol_derives) = extract_derive_attrs(&attrs); - let mut library_tokens_iter = all_tokens - .by_ref() - .take_while(|tt| !matches!(tt, TokenTree::Ident(id) if id == "interface")) - .skip_while(|tt| matches!(tt, TokenTree::Ident(id) if id == "library")) - .peekable(); - - let library_tokens = library_tokens_iter.by_ref(); - - let mut libraries = Vec::new(); - - while library_tokens.peek().is_some() { - let sol_library_tokens: TokenStream = std::iter::once(TokenTree::Ident(id("library"))) - .chain( - library_tokens - .take_while(|tt| !matches!(tt, TokenTree::Ident(id) if id == "library")), - ) - .collect(); - - let tokens = quote! { - #(#derives)* - #(#sol_derives)* - #sol_library_tokens - }; - - libraries.push(tokens); - } - let sol_interface_tokens: TokenStream = - std::iter::once(TokenTree::Ident(id("interface"))).chain(all_tokens).collect(); let bytecode = bytecode.map(|bytes| { let s = bytes.to_string(); quote!(bytecode = #s,) @@ -63,44 +43,34 @@ impl SolInput { quote!(deployed_bytecode = #s) }); - let attrs_iter = attrs.iter(); - let doc_str = format!( - "\n\n\ -Generated by the following Solidity interface... -```solidity -{sol} -``` - -...which was generated by the following JSON ABI: -```json -{json_s} -```", - json_s = serde_json::to_string_pretty(&abi).unwrap() - ); - let tokens = quote! { - #(#inner_attrs)* - #(#libraries)* - - #(#attrs_iter)* - #[doc = #doc_str] - #[sol(#bytecode #deployed_bytecode)] - #sol_interface_tokens + let ctx = ApplyAttrsCtx { + derives: &derives, + sol_derives: &sol_derives, + interface_attrs: &attrs, + bytecode: bytecode.as_ref(), + deployed_bytecode: deployed_bytecode.as_ref(), + sol: &sol, + abi: &abi, }; - - let ast: ast::File = syn::parse2(tokens).map_err(|e| { - let msg = format!( - "failed to parse ABI-generated tokens into a Solidity AST for `{name}`: {e}.\n\ - This is a bug. We would appreciate a bug report: \ - https://github.com/alloy-rs/core/issues/new/choose" - ); - syn::Error::new(name.span(), msg) - })?; + apply_attrs_to_items(&mut ast.items, &ctx); + ast.attrs.extend(inner_attrs); let kind = SolInputKind::Sol(ast); Ok(SolInput { attrs, path, kind }) } } +/// Shared context for applying user attributes to ABI-derived items. +struct ApplyAttrsCtx<'a> { + derives: &'a [&'a syn::Attribute], + sol_derives: &'a [&'a syn::Attribute], + interface_attrs: &'a [syn::Attribute], + bytecode: Option<&'a TokenStream>, + deployed_bytecode: Option<&'a TokenStream>, + sol: &'a str, + abi: &'a JsonAbi, +} + // doesn't parse Json fn abi_to_sol(name: &Ident, abi: &mut JsonAbi) -> String { @@ -153,11 +123,75 @@ fn extract_derive_attrs(attrs: &[syn::Attribute]) -> (Vec<&syn::Attribute>, Vec< }) } -#[inline] -#[track_caller] -fn id(s: impl AsRef) -> Ident { - // Ident::new panics on Rust keywords and `r#` prefixes - syn::parse_str(s.as_ref()).unwrap() +/// Applies derive/`sol` attributes to ABI-derived items. +/// +/// - Non-interface contracts, structs, enums, and UDVTs get the user-specified derive and `sol` +/// attributes cloned onto them. +/// - The single interface gets the outer attributes, a generated doc (including the original +/// Solidity/JSON ABI), and the `#[sol(bytecode = ..., deployed_bytecode = ...)]` attribute. +fn apply_attrs_to_items(items: &mut [ast::Item], ctx: &ApplyAttrsCtx<'_>) { + for item in items { + match item { + ast::Item::Contract(contract) if contract.kind.is_interface() => { + apply_interface_attrs(contract, ctx); + } + ast::Item::Contract(contract) => { + extend_attrs(&mut contract.attrs, ctx.derives, ctx.sol_derives); + } + ast::Item::Struct(strukt) => { + extend_attrs(&mut strukt.attrs, ctx.derives, ctx.sol_derives); + } + ast::Item::Udt(udt) => { + extend_attrs(&mut udt.attrs, ctx.derives, ctx.sol_derives); + } + // Globals from `to_sol` are only structs, UDVTs; enums are flattened to `uint8`, + // while errors/functions/events,etc are emitted in the interface. + _ => debug_assert!(false, "unexpected global item type"), + } + } +} + +/// Merge user outer attrs with generated docs/metadata for the sole interface. +fn apply_interface_attrs(contract: &mut ast::ItemContract, ctx: &ApplyAttrsCtx<'_>) { + let bytecode = ctx.bytecode; + let deployed_bytecode = ctx.deployed_bytecode; + let doc_str = format!( + "\n\n\ +Generated by the following Solidity interface...\ +```solidity\ +{sol}\ +```\ +\n\ +...which was generated by the following JSON ABI:\ +```json\ +{json_s}\ +```", + sol = ctx.sol, + json_s = serde_json::to_string_pretty(ctx.abi).unwrap(), + ); + let doc_attr: syn::Attribute = syn::parse_quote!(#[doc = #doc_str]); + let sol_attr: syn::Attribute = syn::parse_quote!(#[sol(#bytecode #deployed_bytecode)]); + + let mut merged = ctx.interface_attrs.to_vec(); + merged.push(doc_attr); + merged.push(sol_attr); + contract.attrs = merged; +} + +/// Clone user-specified `derive`/`sol(...)` attributes onto the given item. +/// Used for globals (structs/UDVTs) and non-interface contracts emitted by `to_sol`. +fn extend_attrs( + attrs: &mut Vec, + derives: &[&syn::Attribute], + sol_derives: &[&syn::Attribute], +) { + attrs.reserve(derives.len() + sol_derives.len()); + for attr in derives { + attrs.push((*attr).clone()); + } + for attr in sol_derives { + attrs.push((*attr).clone()); + } } #[cfg(test)] @@ -165,6 +199,11 @@ mod tests { use super::*; use std::path::{Path, PathBuf}; + fn id(s: impl AsRef) -> Ident { + // Ident::new panics on Rust keywords and `r#` prefixes + syn::parse_str(s.as_ref()).unwrap() + } + #[test] #[cfg_attr(miri, ignore = "no fs")] fn abi() { diff --git a/crates/sol-types/tests/macros/sol/json.rs b/crates/sol-types/tests/macros/sol/json.rs index 9ca56b08d..64b8c5a90 100644 --- a/crates/sol-types/tests/macros/sol/json.rs +++ b/crates/sol-types/tests/macros/sol/json.rs @@ -47,15 +47,18 @@ fn large_array() { #[test] fn seaport() { - sol!( - #[derive(Debug, PartialEq, Eq)] - Seaport, - "../json-abi/tests/abi/Seaport.json" - ); - use Seaport::*; + mod container { + use super::*; + sol!( + #[derive(Debug, PartialEq, Eq)] + Seaport, + "../json-abi/tests/abi/Seaport.json" + ); + } + use container::*; // Constructor with a single argument - let _ = constructorCall { conduitController: Address::ZERO }; + let _ = Seaport::constructorCall { conduitController: Address::ZERO }; // BasicOrderType is a uint8 UDVT let o1 = BasicOrderType::from(0u8); @@ -105,7 +108,11 @@ fn aggregation_router_v5() { #[test] fn uniswap_v3_position() { // https://etherscan.io/address/0x8638fbd429b19249bb3bcf3ec72d07a657e49642#code - sol!(UniswapV3Position, "../json-abi/tests/abi/UniswapV3Position.json"); + mod container { + use super::*; + sol!(UniswapV3Position, "../json-abi/tests/abi/UniswapV3Position.json"); + } + use container::*; let _ = UniswapV3Position::getLiquidityByRangeCall { pool_: Address::ZERO, @@ -169,7 +176,12 @@ fn gnosis_safe() { // https://github.com/alloy-rs/core/issues/371 #[test] fn blur_exchange() { - sol!(BlurExchange, "../json-abi/tests/abi/BlurExchange.json"); + mod container { + use super::*; + sol!(BlurExchange, "../json-abi/tests/abi/BlurExchange.json"); + } + use container::*; + let BlurExchange::NAMECall {} = BlurExchange::NAMECall {}; let BlurExchange::NAMEReturn { _0: _ } = BlurExchange::NAMEReturn { _0: String::new() }; } @@ -283,3 +295,37 @@ fn ignore_unlinked_bytecode_attr() { let _ = AnotherUnlinked::addCall { a: U256::ZERO, b: U256::ZERO }; } + +// Ensure globals stay in the outer namespace and interface/contract items stay nested. +#[test] +fn contract_with_globals() { + mod container { + use super::*; + sol!( + #[derive(Debug, PartialEq, Eq)] + ContractUsingGlobals, + "../json-abi/tests/abi/ContractUsingGlobals.json" + ); + } + + // Globals should be available at the module scope, not under ContractWithGlobals. + let _udt: container::GlobalUDT = container::GlobalUDT::from(U256::from(123u64)); + let enm: container::GlobalEnum = container::GlobalEnum::from(1u8); + let gs = container::GlobalStruct { value: U256::from(1), enum_: enm.clone().into() }; + + // GlobalEnum is flattened to a UDVT over uint8 + let raw = enm.clone().into_underlying(); + assert_eq!(raw, 1u8); + + // Interface-scoped struct should live under the interface namespace. + let _iface_struct = + container::Interface::InterfaceStruct { kind: enm.into(), count: U256::from(2u64) }; + + // Events/errors belong to the contract (interface) namespace. + let _event_sig = ::SIGNATURE; + let _error_sig = ::SIGNATURE; + + // Basic equality and Debug derive on globals. + assert_eq!(gs, gs); + let _ = format!("{gs:?}"); +} From 02a9723039ac8ab0568c5c2d9d2ebf96badb22ef Mon Sep 17 00:00:00 2001 From: voith Date: Tue, 16 Dec 2025 17:23:35 +0530 Subject: [PATCH 4/5] fix(json-abi): add regression test and adjust abi-to-sol formatting --- crates/json-abi/src/to_sol.rs | 2 ++ crates/json-abi/tests/abi/BlurExchange.sol | 2 +- crates/json-abi/tests/abi/Bootstrap.sol | 2 +- .../tests/abi/ContractUsingGlobals.sol | 2 +- .../json-abi/tests/abi/DelegationManager.sol | 2 +- crates/json-abi/tests/abi/Fastlane.sol | 2 +- crates/json-abi/tests/abi/Handler.json | 29 +++++++++++++++++++ crates/json-abi/tests/abi/Handler.sol | 12 ++++++++ crates/json-abi/tests/abi/Seaport.sol | 2 +- .../json-abi/tests/abi/UniswapV3Position.sol | 2 +- crates/json-abi/tests/abi/WithStructs.sol | 2 +- crates/sol-types/tests/macros/sol/json.rs | 23 +++++++++++++++ 12 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 crates/json-abi/tests/abi/Handler.json create mode 100644 crates/json-abi/tests/abi/Handler.sol diff --git a/crates/json-abi/src/to_sol.rs b/crates/json-abi/src/to_sol.rs index 1b3f2dc73..7d955d986 100644 --- a/crates/json-abi/src/to_sol.rs +++ b/crates/json-abi/src/to_sol.rs @@ -229,10 +229,12 @@ impl JsonAbi { out.push('}'); out.indent_level -= 1; + if !its.globals.is_empty() { out.push('\n'); fmt!(its.globals); out.pop(); // trailing newline + out.pop(); // trailing newline } } } diff --git a/crates/json-abi/tests/abi/BlurExchange.sol b/crates/json-abi/tests/abi/BlurExchange.sol index 16d2ded48..a6469a3e9 100644 --- a/crates/json-abi/tests/abi/BlurExchange.sol +++ b/crates/json-abi/tests/abi/BlurExchange.sol @@ -94,4 +94,4 @@ struct Order { Fee[] fees; uint256 salt; bytes extraParams; -} +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Bootstrap.sol b/crates/json-abi/tests/abi/Bootstrap.sol index c20161a1a..eb4a433e3 100644 --- a/crates/json-abi/tests/abi/Bootstrap.sol +++ b/crates/json-abi/tests/abi/Bootstrap.sol @@ -33,4 +33,4 @@ type CallType is bytes1; struct BootstrapConfig { address module; bytes data; -} +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/ContractUsingGlobals.sol b/crates/json-abi/tests/abi/ContractUsingGlobals.sol index 2bfc32f4d..fe4fbe08c 100644 --- a/crates/json-abi/tests/abi/ContractUsingGlobals.sol +++ b/crates/json-abi/tests/abi/ContractUsingGlobals.sol @@ -18,4 +18,4 @@ type GlobalUDT is uint256; struct GlobalStruct { uint256 value; GlobalEnum enum_; -} +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/DelegationManager.sol b/crates/json-abi/tests/abi/DelegationManager.sol index bf622c221..0df250b5c 100644 --- a/crates/json-abi/tests/abi/DelegationManager.sol +++ b/crates/json-abi/tests/abi/DelegationManager.sol @@ -127,4 +127,4 @@ interface DelegationManager { function unpause(uint256 newPausedStatus) external; function updateOperatorMetadataURI(string memory metadataURI) external; } -type DelegatedShares is uint256; +type DelegatedShares is uint256; \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Fastlane.sol b/crates/json-abi/tests/abi/Fastlane.sol index a3f942d25..58ef1ec7b 100644 --- a/crates/json-abi/tests/abi/Fastlane.sol +++ b/crates/json-abi/tests/abi/Fastlane.sol @@ -93,4 +93,4 @@ struct ValidatorBalanceCheckpoint { struct ValidatorPreferences { uint256 minAutoshipAmount; address validatorPayableAddress; -} +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Handler.json b/crates/json-abi/tests/abi/Handler.json new file mode 100644 index 000000000..1fc54bdab --- /dev/null +++ b/crates/json-abi/tests/abi/Handler.json @@ -0,0 +1,29 @@ +[ + { + "type": "function", + "name": "handle", + "inputs": [ + { + "name": "foobar", + "type": "tuple", + "internalType": "struct IHandler.FooBar", + "components": [ + { + "name": "foo", + "type": "tuple", + "internalType": "struct Foo", + "components": [ + { + "name": "newNumber", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Handler.sol b/crates/json-abi/tests/abi/Handler.sol new file mode 100644 index 000000000..3eef9762a --- /dev/null +++ b/crates/json-abi/tests/abi/Handler.sol @@ -0,0 +1,12 @@ +library IHandler { + struct FooBar { + Foo foo; + } +} + +interface Handler { + function handle(IHandler.FooBar memory foobar) external; +} +struct Foo { + uint256 newNumber; +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Seaport.sol b/crates/json-abi/tests/abi/Seaport.sol index c3b057f85..6d8f603e5 100644 --- a/crates/json-abi/tests/abi/Seaport.sol +++ b/crates/json-abi/tests/abi/Seaport.sol @@ -183,4 +183,4 @@ struct SpentItem { address token; uint256 identifier; uint256 amount; -} +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/UniswapV3Position.sol b/crates/json-abi/tests/abi/UniswapV3Position.sol index aaabcfe9d..67d477008 100644 --- a/crates/json-abi/tests/abi/UniswapV3Position.sol +++ b/crates/json-abi/tests/abi/UniswapV3Position.sol @@ -7,4 +7,4 @@ struct Range { int24 lowerTick; int24 upperTick; uint24 feeTier; -} +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/WithStructs.sol b/crates/json-abi/tests/abi/WithStructs.sol index e4c64a254..2329e99e1 100644 --- a/crates/json-abi/tests/abi/WithStructs.sol +++ b/crates/json-abi/tests/abi/WithStructs.sol @@ -100,4 +100,4 @@ struct ValidatorBalanceCheckpoint { struct ValidatorPreferences { uint256 minAutoshipAmount; address validatorPayableAddress; -} +} \ No newline at end of file diff --git a/crates/sol-types/tests/macros/sol/json.rs b/crates/sol-types/tests/macros/sol/json.rs index 64b8c5a90..35382f1aa 100644 --- a/crates/sol-types/tests/macros/sol/json.rs +++ b/crates/sol-types/tests/macros/sol/json.rs @@ -329,3 +329,26 @@ fn contract_with_globals() { assert_eq!(gs, gs); let _ = format!("{gs:?}"); } + +#[test] +fn handler() { + mod container { + use super::*; + sol!(Handler, "../json-abi/tests/abi/Handler.json"); + } + use container::*; + + // Foo is a global struct, available at module scope. + let foo = Foo { newNumber: U256::from(1u64) }; + + // FooBar lives under the IHandler library namespace. + let foobar = IHandler::FooBar { foo }; + + // Calling the interface should accept the namespaced FooBar. + let call = Handler::handleCall { foobar }; + // Encode to ensure the generated types/namespaces are valid (should not panic). + let _encoded = call.abi_encode(); + + // Check the function selector/signature. + assert_eq!(Handler::handleCall::SIGNATURE, "handle(((uint256)))"); +} From 237c0f8d938a7f484db99dfc81b942bc075c6dcc Mon Sep 17 00:00:00 2001 From: voith Date: Wed, 17 Dec 2025 22:08:02 +0530 Subject: [PATCH 5/5] feat(sol-macro-input): add `#[sol(standalone_globals)]` to opt into root-level globals from JSON ABI --- crates/json-abi/src/to_sol.rs | 20 +- crates/json-abi/tests/abi/BlurExchange.sol | 69 +++--- crates/json-abi/tests/abi/Bootstrap.sol | 11 +- .../tests/abi/ContractUsingGlobals.sol | 13 +- .../json-abi/tests/abi/DelegationManager.sol | 5 +- crates/json-abi/tests/abi/Fastlane.sol | 47 ++-- crates/json-abi/tests/abi/Handler.sol | 7 +- crates/json-abi/tests/abi/Seaport.sol | 227 +++++++++--------- .../json-abi/tests/abi/UniswapV3Position.sol | 11 +- crates/json-abi/tests/abi/WithStructs.sol | 55 ++--- crates/sol-macro-input/src/attr.rs | 10 + crates/sol-macro-input/src/json.rs | 16 +- crates/sol-macro/src/lib.rs | 2 + crates/sol-types/tests/macros/sol/json.rs | 37 ++- 14 files changed, 282 insertions(+), 248 deletions(-) diff --git a/crates/json-abi/src/to_sol.rs b/crates/json-abi/src/to_sol.rs index 7d955d986..01013b0ba 100644 --- a/crates/json-abi/src/to_sol.rs +++ b/crates/json-abi/src/to_sol.rs @@ -21,6 +21,7 @@ pub struct ToSolConfig { enums_as_udvt: bool, for_sol_macro: bool, one_contract: bool, + standalone_globals: bool, } impl Default for ToSolConfig { @@ -39,6 +40,7 @@ impl ToSolConfig { enums_as_udvt: true, for_sol_macro: false, one_contract: false, + standalone_globals: false, } } @@ -73,6 +75,13 @@ impl ToSolConfig { self.one_contract = yes; self } + + /// Sets whether globals should be emitted at the root of the output. + /// Default: `false`. + pub const fn standalone_globals(mut self, yes: bool) -> Self { + self.standalone_globals = yes; + self + } } pub(crate) trait ToSol { @@ -168,7 +177,8 @@ impl JsonAbi { }; } - let mut its = InternalTypes::new(out.name, out.config.enums_as_udvt); + let mut its = + InternalTypes::new(out.name, out.config.enums_as_udvt, out.config.standalone_globals); its.visit_abi(self); let one_contract = out.config.one_contract; @@ -246,17 +256,19 @@ struct InternalTypes<'a> { other: BTreeMap<&'a String, BTreeSet>>, globals: BTreeSet>, enums_as_udvt: bool, + standalone_globals: bool, } impl<'a> InternalTypes<'a> { #[allow(clippy::missing_const_for_fn)] - fn new(name: &'a str, enums_as_udvt: bool) -> Self { + fn new(name: &'a str, enums_as_udvt: bool, standalone_globals: bool) -> Self { Self { name, this_its: BTreeSet::new(), other: BTreeMap::new(), globals: BTreeSet::new(), enums_as_udvt, + standalone_globals, } } @@ -335,8 +347,10 @@ impl<'a> InternalTypes<'a> { } else { self.other.entry(contract).or_default().insert(it); } - } else { + } else if self.standalone_globals { self.globals.insert(it); + } else { + self.this_its.insert(it); } } } diff --git a/crates/json-abi/tests/abi/BlurExchange.sol b/crates/json-abi/tests/abi/BlurExchange.sol index a6469a3e9..fd66afa69 100644 --- a/crates/json-abi/tests/abi/BlurExchange.sol +++ b/crates/json-abi/tests/abi/BlurExchange.sol @@ -1,4 +1,39 @@ interface BlurExchange { + type Side is uint8; + type SignatureVersion is uint8; + struct Execution { + Input sell; + Input buy; + } + struct Fee { + uint16 rate; + address payable recipient; + } + struct Input { + Order order; + uint8 v; + bytes32 r; + bytes32 s; + bytes extraSignature; + SignatureVersion signatureVersion; + uint256 blockNumber; + } + struct Order { + address trader; + Side side; + address matchingPolicy; + address collection; + uint256 tokenId; + uint256 amount; + address paymentToken; + uint256 price; + uint256 listingTime; + uint256 expirationTime; + Fee[] fees; + uint256 salt; + bytes extraParams; + } + event AdminChanged(address previousAdmin, address newAdmin); event BeaconUpgraded(address indexed beacon); event Closed(); @@ -60,38 +95,4 @@ interface BlurExchange { function transferOwnership(address newOwner) external; function upgradeTo(address newImplementation) external; function upgradeToAndCall(address newImplementation, bytes memory data) external payable; -} -type Side is uint8; -type SignatureVersion is uint8; -struct Execution { - Input sell; - Input buy; -} -struct Fee { - uint16 rate; - address payable recipient; -} -struct Input { - Order order; - uint8 v; - bytes32 r; - bytes32 s; - bytes extraSignature; - SignatureVersion signatureVersion; - uint256 blockNumber; -} -struct Order { - address trader; - Side side; - address matchingPolicy; - address collection; - uint256 tokenId; - uint256 amount; - address paymentToken; - uint256 price; - uint256 listingTime; - uint256 expirationTime; - Fee[] fees; - uint256 salt; - bytes extraParams; } \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Bootstrap.sol b/crates/json-abi/tests/abi/Bootstrap.sol index eb4a433e3..b449f8c89 100644 --- a/crates/json-abi/tests/abi/Bootstrap.sol +++ b/crates/json-abi/tests/abi/Bootstrap.sol @@ -6,6 +6,12 @@ library ModuleManager { } interface Bootstrap { + type CallType is bytes1; + struct BootstrapConfig { + address module; + bytes data; + } + error AccountAccessUnauthorized(); error CannotRemoveLastValidator(); error HookAlreadyInstalled(address currentHook); @@ -28,9 +34,4 @@ interface Bootstrap { function getValidatorsPaginated(address cursor, uint256 size) external view returns (address[] memory array, address next); function initMSA(BootstrapConfig[] memory _valdiators, BootstrapConfig[] memory _executors, BootstrapConfig memory _hook, BootstrapConfig[] memory _fallbacks) external; function singleInitMSA(address validator, bytes memory data) external; -} -type CallType is bytes1; -struct BootstrapConfig { - address module; - bytes data; } \ No newline at end of file diff --git a/crates/json-abi/tests/abi/ContractUsingGlobals.sol b/crates/json-abi/tests/abi/ContractUsingGlobals.sol index fe4fbe08c..21c8f97c5 100644 --- a/crates/json-abi/tests/abi/ContractUsingGlobals.sol +++ b/crates/json-abi/tests/abi/ContractUsingGlobals.sol @@ -6,16 +6,17 @@ library Interface { } interface ContractUsingGlobals { + type GlobalEnum is uint8; + type GlobalUDT is uint256; + struct GlobalStruct { + uint256 value; + GlobalEnum enum_; + } + error GlobalError(GlobalStruct payload); event GlobalEvent(GlobalStruct payload, GlobalUDT amount); function emitGlobalEvent(Interface.InterfaceStruct memory data) external; function triggerError() external pure; -} -type GlobalEnum is uint8; -type GlobalUDT is uint256; -struct GlobalStruct { - uint256 value; - GlobalEnum enum_; } \ No newline at end of file diff --git a/crates/json-abi/tests/abi/DelegationManager.sol b/crates/json-abi/tests/abi/DelegationManager.sol index 0df250b5c..ba48a7514 100644 --- a/crates/json-abi/tests/abi/DelegationManager.sol +++ b/crates/json-abi/tests/abi/DelegationManager.sol @@ -28,6 +28,8 @@ library ISignatureUtils { } interface DelegationManager { + type DelegatedShares is uint256; + error ActivelyDelegated(); error AllocationDelaySet(); error CallerCannotUndelegate(); @@ -126,5 +128,4 @@ interface DelegationManager { function undelegate(address staker) external returns (bytes32[] memory withdrawalRoots); function unpause(uint256 newPausedStatus) external; function updateOperatorMetadataURI(string memory metadataURI) external; -} -type DelegatedShares is uint256; \ No newline at end of file +} \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Fastlane.sol b/crates/json-abi/tests/abi/Fastlane.sol index 58ef1ec7b..bf46652c2 100644 --- a/crates/json-abi/tests/abi/Fastlane.sol +++ b/crates/json-abi/tests/abi/Fastlane.sol @@ -1,4 +1,28 @@ interface Fastlane { + type statusType is uint8; + struct Bid { + address validatorAddress; + address opportunityAddress; + address searcherContractAddress; + address searcherPayableAddress; + uint256 bidAmount; + } + struct Status { + uint128 activeAtAuction; + uint128 inactiveAtAuction; + statusType kind; + } + struct ValidatorBalanceCheckpoint { + uint256 pendingBalanceAtlastBid; + uint256 outstandingBalance; + uint128 lastWithdrawnAuction; + uint128 lastBidReceivedAuction; + } + struct ValidatorPreferences { + uint256 minAutoshipAmount; + address validatorPayableAddress; + } + event AuctionEnded(uint128 indexed auction_number); event AuctionStarted(uint128 indexed auction_number); event AuctionStarterSet(address indexed starter); @@ -70,27 +94,4 @@ interface Fastlane { function transferOwnership(address newOwner) external; function withdrawStuckERC20(address _tokenAddress) external; function withdrawStuckNativeToken(uint256 amount) external; -} -type statusType is uint8; -struct Bid { - address validatorAddress; - address opportunityAddress; - address searcherContractAddress; - address searcherPayableAddress; - uint256 bidAmount; -} -struct Status { - uint128 activeAtAuction; - uint128 inactiveAtAuction; - statusType kind; -} -struct ValidatorBalanceCheckpoint { - uint256 pendingBalanceAtlastBid; - uint256 outstandingBalance; - uint128 lastWithdrawnAuction; - uint128 lastBidReceivedAuction; -} -struct ValidatorPreferences { - uint256 minAutoshipAmount; - address validatorPayableAddress; } \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Handler.sol b/crates/json-abi/tests/abi/Handler.sol index 3eef9762a..9296b3de9 100644 --- a/crates/json-abi/tests/abi/Handler.sol +++ b/crates/json-abi/tests/abi/Handler.sol @@ -5,8 +5,9 @@ library IHandler { } interface Handler { + struct Foo { + uint256 newNumber; + } + function handle(IHandler.FooBar memory foobar) external; -} -struct Foo { - uint256 newNumber; } \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Seaport.sol b/crates/json-abi/tests/abi/Seaport.sol index 6d8f603e5..8af2cb2b7 100644 --- a/crates/json-abi/tests/abi/Seaport.sol +++ b/crates/json-abi/tests/abi/Seaport.sol @@ -1,4 +1,118 @@ interface Seaport { + type BasicOrderType is uint8; + type ItemType is uint8; + type OrderType is uint8; + type Side is uint8; + struct AdditionalRecipient { + uint256 amount; + address payable recipient; + } + struct AdvancedOrder { + OrderParameters parameters; + uint120 numerator; + uint120 denominator; + bytes signature; + bytes extraData; + } + struct BasicOrderParameters { + address considerationToken; + uint256 considerationIdentifier; + uint256 considerationAmount; + address payable offerer; + address zone; + address offerToken; + uint256 offerIdentifier; + uint256 offerAmount; + BasicOrderType basicOrderType; + uint256 startTime; + uint256 endTime; + bytes32 zoneHash; + uint256 salt; + bytes32 offererConduitKey; + bytes32 fulfillerConduitKey; + uint256 totalOriginalAdditionalRecipients; + AdditionalRecipient[] additionalRecipients; + bytes signature; + } + struct ConsiderationItem { + ItemType itemType; + address token; + uint256 identifierOrCriteria; + uint256 startAmount; + uint256 endAmount; + address payable recipient; + } + struct CriteriaResolver { + uint256 orderIndex; + Side side; + uint256 index; + uint256 identifier; + bytes32[] criteriaProof; + } + struct Execution { + ReceivedItem item; + address offerer; + bytes32 conduitKey; + } + struct Fulfillment { + FulfillmentComponent[] offerComponents; + FulfillmentComponent[] considerationComponents; + } + struct FulfillmentComponent { + uint256 orderIndex; + uint256 itemIndex; + } + struct OfferItem { + ItemType itemType; + address token; + uint256 identifierOrCriteria; + uint256 startAmount; + uint256 endAmount; + } + struct Order { + OrderParameters parameters; + bytes signature; + } + struct OrderComponents { + address offerer; + address zone; + OfferItem[] offer; + ConsiderationItem[] consideration; + OrderType orderType; + uint256 startTime; + uint256 endTime; + bytes32 zoneHash; + uint256 salt; + bytes32 conduitKey; + uint256 counter; + } + struct OrderParameters { + address offerer; + address zone; + OfferItem[] offer; + ConsiderationItem[] consideration; + OrderType orderType; + uint256 startTime; + uint256 endTime; + bytes32 zoneHash; + uint256 salt; + bytes32 conduitKey; + uint256 totalOriginalConsiderationItems; + } + struct ReceivedItem { + ItemType itemType; + address token; + uint256 identifier; + uint256 amount; + address payable recipient; + } + struct SpentItem { + ItemType itemType; + address token; + uint256 identifier; + uint256 amount; + } + error BadContractSignature(); error BadFraction(); error BadReturnValueFromERC20OnTransfer(address token, address from, address to, uint256 amount); @@ -70,117 +184,4 @@ interface Seaport { function matchOrders(Order[] memory, Fulfillment[] memory) external payable returns (Execution[] memory); function name() external pure returns (string memory); function validate(Order[] memory) external returns (bool); -} -type BasicOrderType is uint8; -type ItemType is uint8; -type OrderType is uint8; -type Side is uint8; -struct AdditionalRecipient { - uint256 amount; - address payable recipient; -} -struct AdvancedOrder { - OrderParameters parameters; - uint120 numerator; - uint120 denominator; - bytes signature; - bytes extraData; -} -struct BasicOrderParameters { - address considerationToken; - uint256 considerationIdentifier; - uint256 considerationAmount; - address payable offerer; - address zone; - address offerToken; - uint256 offerIdentifier; - uint256 offerAmount; - BasicOrderType basicOrderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 offererConduitKey; - bytes32 fulfillerConduitKey; - uint256 totalOriginalAdditionalRecipients; - AdditionalRecipient[] additionalRecipients; - bytes signature; -} -struct ConsiderationItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; - address payable recipient; -} -struct CriteriaResolver { - uint256 orderIndex; - Side side; - uint256 index; - uint256 identifier; - bytes32[] criteriaProof; -} -struct Execution { - ReceivedItem item; - address offerer; - bytes32 conduitKey; -} -struct Fulfillment { - FulfillmentComponent[] offerComponents; - FulfillmentComponent[] considerationComponents; -} -struct FulfillmentComponent { - uint256 orderIndex; - uint256 itemIndex; -} -struct OfferItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; -} -struct Order { - OrderParameters parameters; - bytes signature; -} -struct OrderComponents { - address offerer; - address zone; - OfferItem[] offer; - ConsiderationItem[] consideration; - OrderType orderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 conduitKey; - uint256 counter; -} -struct OrderParameters { - address offerer; - address zone; - OfferItem[] offer; - ConsiderationItem[] consideration; - OrderType orderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 conduitKey; - uint256 totalOriginalConsiderationItems; -} -struct ReceivedItem { - ItemType itemType; - address token; - uint256 identifier; - uint256 amount; - address payable recipient; -} -struct SpentItem { - ItemType itemType; - address token; - uint256 identifier; - uint256 amount; } \ No newline at end of file diff --git a/crates/json-abi/tests/abi/UniswapV3Position.sol b/crates/json-abi/tests/abi/UniswapV3Position.sol index 67d477008..36f13880b 100644 --- a/crates/json-abi/tests/abi/UniswapV3Position.sol +++ b/crates/json-abi/tests/abi/UniswapV3Position.sol @@ -1,10 +1,11 @@ interface UniswapV3Position { + struct Range { + int24 lowerTick; + int24 upperTick; + uint24 feeTier; + } + function getLiquidityByRange(address pool_, address self_, int24 lowerTick_, int24 upperTick_) external view returns (uint128 liquidity); function getPositionId(address self_, int24 lowerTick_, int24 upperTick_) external pure returns (bytes32 positionId); function rangeExists(Range[] memory currentRanges_, Range memory range_) external pure returns (bool ok, uint256 index); -} -struct Range { - int24 lowerTick; - int24 upperTick; - uint24 feeTier; } \ No newline at end of file diff --git a/crates/json-abi/tests/abi/WithStructs.sol b/crates/json-abi/tests/abi/WithStructs.sol index 2329e99e1..0c9acd960 100644 --- a/crates/json-abi/tests/abi/WithStructs.sol +++ b/crates/json-abi/tests/abi/WithStructs.sol @@ -1,4 +1,32 @@ interface WithStructs { + type statusType is uint8; + struct Bid { + address validatorAddress; + address opportunityAddress; + address searcherContractAddress; + address searcherPayableAddress; + uint256 bidAmount; + } + struct NFToken { + address implem; + uint256 id; + } + struct Status { + uint128 activeAtAuction; + uint128 inactiveAtAuction; + statusType kind; + } + struct ValidatorBalanceCheckpoint { + uint256 pendingBalanceAtlastBid; + uint256 outstandingBalance; + uint128 lastWithdrawnAuction; + uint128 lastBidReceivedAuction; + } + struct ValidatorPreferences { + uint256 minAutoshipAmount; + address validatorPayableAddress; + } + event AuctionEnded(uint128 indexed auction_number); event AuctionStarted(uint128 indexed auction_number); event AuctionStarterSet(address indexed starter); @@ -73,31 +101,4 @@ interface WithStructs { function viewNFToken() external returns (NFToken memory nft); function withdrawStuckERC20(address _tokenAddress) external; function withdrawStuckNativeToken(uint256 amount) external; -} -type statusType is uint8; -struct Bid { - address validatorAddress; - address opportunityAddress; - address searcherContractAddress; - address searcherPayableAddress; - uint256 bidAmount; -} -struct NFToken { - address implem; - uint256 id; -} -struct Status { - uint128 activeAtAuction; - uint128 inactiveAtAuction; - statusType kind; -} -struct ValidatorBalanceCheckpoint { - uint256 pendingBalanceAtlastBid; - uint256 outstandingBalance; - uint128 lastWithdrawnAuction; - uint128 lastBidReceivedAuction; -} -struct ValidatorPreferences { - uint256 minAutoshipAmount; - address validatorPayableAddress; } \ No newline at end of file diff --git a/crates/sol-macro-input/src/attr.rs b/crates/sol-macro-input/src/attr.rs index 00fe838b7..a42bd511f 100644 --- a/crates/sol-macro-input/src/attr.rs +++ b/crates/sol-macro-input/src/attr.rs @@ -114,6 +114,10 @@ pub struct SolAttrs { /// Ignore unlinked bytecode /// `#[sol(ignore_unlinked)]` pub ignore_unlinked: Option, + + /// Emit globals at the root when generating from JSON ABI. + /// `#[sol(standalone_globals)]` + pub standalone_globals: Option, } impl SolAttrs { @@ -206,6 +210,7 @@ impl SolAttrs { type_check => lit()?, ignore_unlinked => bool()?, + standalone_globals => bool()?, }; Ok(()) })?; @@ -242,6 +247,7 @@ impl SolAttrs { merge_opt(&mut a.deployed_bytecode, &b.deployed_bytecode); merge_opt(&mut a.type_check, &b.type_check); merge_opt(&mut a.ignore_unlinked, &b.ignore_unlinked); + merge_opt(&mut a.standalone_globals, &b.standalone_globals); } } @@ -466,6 +472,10 @@ mod tests { #[sol(rpc = true)] => Ok(sol_attrs! { rpc: true }), #[sol(rpc = false)] => Ok(sol_attrs! { rpc: false }), + #[sol(standalone_globals)] => Ok(sol_attrs! { standalone_globals: true }), + #[sol(standalone_globals = true)] => Ok(sol_attrs! { standalone_globals: true }), + #[sol(standalone_globals = false)] => Ok(sol_attrs! { standalone_globals: false }), + #[sol(alloy_sol_types)] => Err("expected `=`"), #[sol(alloy_sol_types = alloy_core::sol_types)] => Ok(sol_attrs! { alloy_sol_types: parse_quote!(alloy_core::sol_types) }), #[sol(alloy_sol_types = ::alloy_core::sol_types)] => Ok(sol_attrs! { alloy_sol_types: parse_quote!(::alloy_core::sol_types) }), diff --git a/crates/sol-macro-input/src/json.rs b/crates/sol-macro-input/src/json.rs index 61a11b1a8..c90ccb930 100644 --- a/crates/sol-macro-input/src/json.rs +++ b/crates/sol-macro-input/src/json.rs @@ -1,4 +1,4 @@ -use crate::{SolInput, SolInputKind}; +use crate::{SolAttrs, SolInput, SolInputKind}; use alloy_json_abi::{ContractObject, JsonAbi, ToSolConfig}; use proc_macro2::{Ident, TokenStream, TokenTree}; use quote::quote; @@ -17,7 +17,13 @@ impl SolInput { }; let mut abi = abi.ok_or_else(|| syn::Error::new(name.span(), "ABI not found in JSON"))?; - let sol = abi_to_sol(&name, &mut abi); + let (sol_attrs, _) = SolAttrs::parse(&attrs)?; + let standalone_globals = sol_attrs.standalone_globals.unwrap_or(false); + let config = ToSolConfig::new() + .print_constructors(true) + .for_sol_macro(true) + .standalone_globals(standalone_globals); + let sol = abi_to_sol(&name, &mut abi, config); let all_tokens = tokens_for_sol(&name, &sol)?; let mut ast: ast::File = syn::parse2(all_tokens).map_err(|e| { let msg = format!( @@ -73,9 +79,8 @@ struct ApplyAttrsCtx<'a> { // doesn't parse Json -fn abi_to_sol(name: &Ident, abi: &mut JsonAbi) -> String { +fn abi_to_sol(name: &Ident, abi: &mut JsonAbi, config: ToSolConfig) -> String { abi.dedup(); - let config = ToSolConfig::new().print_constructors(true).for_sol_macro(true); abi.to_sol(&name.to_string(), Some(config)) } @@ -228,7 +233,8 @@ mod tests { let name = Path::new(path).file_stem().unwrap().to_str().unwrap(); let name_id = id(name); - let sol = abi_to_sol(&name_id, &mut abi); + let config = ToSolConfig::new().print_constructors(true).for_sol_macro(true); + let sol = abi_to_sol(&name_id, &mut abi, config); let tokens = match tokens_for_sol(&name_id, &sol) { Ok(tokens) => tokens, Err(e) => { diff --git a/crates/sol-macro/src/lib.rs b/crates/sol-macro/src/lib.rs index 42fbece7c..d1b1044e0 100644 --- a/crates/sol-macro/src/lib.rs +++ b/crates/sol-macro/src/lib.rs @@ -158,6 +158,8 @@ use syn::parse_macro_input; /// - `type_check = ` (UDVT only): specifies a function to be used to check an User /// Defined Type. /// - `ignore_unlinked [ = ]`: ignores unlinked bytecode in contract artifacts. +/// - `standalone_globals [ = ]` (JSON ABI only): emits global structs/UDVTs at the +/// root of the generated Solidity instead of nesting them under the interface/contract. /// /// ### Structs and enums /// diff --git a/crates/sol-types/tests/macros/sol/json.rs b/crates/sol-types/tests/macros/sol/json.rs index 35382f1aa..8739eaabc 100644 --- a/crates/sol-types/tests/macros/sol/json.rs +++ b/crates/sol-types/tests/macros/sol/json.rs @@ -47,18 +47,15 @@ fn large_array() { #[test] fn seaport() { - mod container { - use super::*; - sol!( - #[derive(Debug, PartialEq, Eq)] - Seaport, - "../json-abi/tests/abi/Seaport.json" - ); - } - use container::*; + sol!( + #[derive(Debug, PartialEq, Eq)] + Seaport, + "../json-abi/tests/abi/Seaport.json" + ); + use Seaport::*; // Constructor with a single argument - let _ = Seaport::constructorCall { conduitController: Address::ZERO }; + let _ = constructorCall { conduitController: Address::ZERO }; // BasicOrderType is a uint8 UDVT let o1 = BasicOrderType::from(0u8); @@ -108,11 +105,7 @@ fn aggregation_router_v5() { #[test] fn uniswap_v3_position() { // https://etherscan.io/address/0x8638fbd429b19249bb3bcf3ec72d07a657e49642#code - mod container { - use super::*; - sol!(UniswapV3Position, "../json-abi/tests/abi/UniswapV3Position.json"); - } - use container::*; + sol!(UniswapV3Position, "../json-abi/tests/abi/UniswapV3Position.json"); let _ = UniswapV3Position::getLiquidityByRangeCall { pool_: Address::ZERO, @@ -176,12 +169,7 @@ fn gnosis_safe() { // https://github.com/alloy-rs/core/issues/371 #[test] fn blur_exchange() { - mod container { - use super::*; - sol!(BlurExchange, "../json-abi/tests/abi/BlurExchange.json"); - } - use container::*; - + sol!(BlurExchange, "../json-abi/tests/abi/BlurExchange.json"); let BlurExchange::NAMECall {} = BlurExchange::NAMECall {}; let BlurExchange::NAMEReturn { _0: _ } = BlurExchange::NAMEReturn { _0: String::new() }; } @@ -303,6 +291,7 @@ fn contract_with_globals() { use super::*; sol!( #[derive(Debug, PartialEq, Eq)] + #[sol(standalone_globals)] ContractUsingGlobals, "../json-abi/tests/abi/ContractUsingGlobals.json" ); @@ -334,7 +323,11 @@ fn contract_with_globals() { fn handler() { mod container { use super::*; - sol!(Handler, "../json-abi/tests/abi/Handler.json"); + sol!( + #[sol(standalone_globals)] + Handler, + "../json-abi/tests/abi/Handler.json" + ); } use container::*;