diff --git a/crates/json-abi/src/to_sol.rs b/crates/json-abi/src/to_sol.rs index de13d1102f..4ac5b862e0 100644 --- a/crates/json-abi/src/to_sol.rs +++ b/crates/json-abi/src/to_sol.rs @@ -3,6 +3,7 @@ use crate::{ EventParam, InternalType, JsonAbi, Param, StateMutability, }; use alloc::{ + borrow::Cow, collections::{BTreeMap, BTreeSet}, string::String, vec::Vec, @@ -18,6 +19,7 @@ use core::{ pub struct ToSolConfig { print_constructors: bool, enums_as_udvt: bool, + for_sol_macro: bool, } impl Default for ToSolConfig { @@ -31,7 +33,7 @@ impl ToSolConfig { /// Creates a new configuration with default settings. #[inline] pub const fn new() -> Self { - Self { print_constructors: false, enums_as_udvt: true } + Self { print_constructors: false, enums_as_udvt: true, for_sol_macro: false } } /// Sets whether to print constructors. Default: `false`. @@ -48,6 +50,14 @@ impl ToSolConfig { self.enums_as_udvt = yes; self } + + /// Sets whether to normalize the output for the [`sol!`] macro. Default: `false`. + /// + /// [`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html + pub const fn for_sol_macro(mut self, yes: bool) -> Self { + self.for_sol_macro = yes; + self + } } pub(crate) trait ToSol { @@ -97,6 +107,27 @@ impl<'a> SolPrinter<'a> { fn indent(&mut self) { self.push_str(" "); } + + /// Normalizes `s` as a Rust identifier and pushes it to the buffer. + /// + /// See [`Self::normalize_ident`] for more details. + fn push_ident(&mut self, s: &str) { + let s = self.normalize_ident(s); + self.push_str(&s); + } + + /// Normalizes `s` as a Rust identifier. + /// + /// All Solidity identifiers are also valid Rust identifiers, except for `$`. + /// This function replaces `$` with `_` if the configuration is set to normalize for the `sol!` + /// macro. + fn normalize_ident<'b>(&self, s: &'b str) -> Cow<'b, str> { + if self.config.for_sol_macro && s.contains('$') { + Cow::Owned(s.replace('$', "_")) + } else { + Cow::Borrowed(s) + } + } } impl JsonAbi { @@ -311,19 +342,19 @@ impl ToSol for It<'_> { match self.kind { ItKind::Enum => { out.push_str("type "); - out.push_str(self.name); + out.push_ident(self.name); out.push_str(" is uint8;"); } ItKind::Udvt(ty) => { out.push_str("type "); - out.push_str(self.name); + out.push_ident(self.name); out.push_str(" is "); out.push_str(ty); out.push(';'); } ItKind::Struct(components) => { out.push_str("struct "); - out.push_str(self.name); + out.push_ident(self.name); out.push_str(" {\n"); for component in components { out.indent(); @@ -483,10 +514,17 @@ impl ToSol for AbiFunction<'_, IN> { out.print_param_location = true; } + // TODO: Enable once `#[sol(rename)]` is implemented. + // if let Some(name) = self.name { + // if out.config.for_sol_macro && name.contains('$') { + // write!(out, "#[sol(rename = \"{name}\")]").unwrap(); + // } + // } + out.push_str(self.kw.as_str()); if let Some(name) = self.name { out.push(' '); - out.push_str(name); + out.push_ident(name); } out.push('('); @@ -616,11 +654,11 @@ fn param( _ => { if let Some(contract_name) = contract_name { if contract_name != out.name { - out.push_str(contract_name); + out.push_ident(contract_name); out.push('.'); } } - out.push_str(type_name); + out.push_ident(type_name); } } @@ -639,6 +677,6 @@ fn param( } if !name.is_empty() { out.push(' '); - out.push_str(name); + out.push_ident(name); } } diff --git a/crates/json-abi/tests/abi.rs b/crates/json-abi/tests/abi.rs index c3212c227e..1592cfe38a 100644 --- a/crates/json-abi/tests/abi.rs +++ b/crates/json-abi/tests/abi.rs @@ -1,4 +1,4 @@ -use alloy_json_abi::{AbiItem, EventParam, JsonAbi, Param}; +use alloy_json_abi::{AbiItem, EventParam, JsonAbi, Param, ToSolConfig}; use pretty_assertions::assert_eq; use std::{ collections::HashMap, @@ -86,7 +86,7 @@ fn to_sol_test(path: &str, abi: &JsonAbi, run_solc: bool) { // Ignore constructors for Solc tests. abi.constructor = None; abi.dedup(); - let actual = abi.to_sol(name, None); + let actual = abi.to_sol(name, Some(ToSolConfig::new().for_sol_macro(true))); ensure_file_contents(&sol_path, &actual); @@ -98,6 +98,8 @@ fn to_sol_test(path: &str, abi: &JsonAbi, run_solc: bool) { | "UniswapV1Exchange" // https://github.com/alloy-rs/core/issues/744 | "DelegationManager" + // https://github.com/alloy-rs/core/issues/746 + | "Bootstrap" ) { return; } diff --git a/crates/json-abi/tests/abi/Bootstrap.json b/crates/json-abi/tests/abi/Bootstrap.json new file mode 100644 index 0000000000..19723dd684 --- /dev/null +++ b/crates/json-abi/tests/abi/Bootstrap.json @@ -0,0 +1 @@ +[{"type":"fallback","stateMutability":"payable"},{"type":"receive","stateMutability":"payable"},{"type":"function","name":"_getInitMSACalldata","inputs":[{"name":"$valdiators","type":"tuple[]","internalType":"struct BootstrapConfig[]","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"$executors","type":"tuple[]","internalType":"struct BootstrapConfig[]","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"_hook","type":"tuple","internalType":"struct BootstrapConfig","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"_fallbacks","type":"tuple[]","internalType":"struct BootstrapConfig[]","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"init","type":"bytes","internalType":"bytes"}],"stateMutability":"view"},{"type":"function","name":"entryPoint","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getActiveFallbackHandler","inputs":[{"name":"functionSig","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"tuple","internalType":"struct ModuleManager.FallbackHandler","components":[{"name":"handler","type":"address","internalType":"address"},{"name":"calltype","type":"bytes1","internalType":"CallType"}]}],"stateMutability":"view"},{"type":"function","name":"getActiveHook","inputs":[],"outputs":[{"name":"hook","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getExecutorsPaginated","inputs":[{"name":"cursor","type":"address","internalType":"address"},{"name":"size","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"array","type":"address[]","internalType":"address[]"},{"name":"next","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getValidatorsPaginated","inputs":[{"name":"cursor","type":"address","internalType":"address"},{"name":"size","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"array","type":"address[]","internalType":"address[]"},{"name":"next","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"initMSA","inputs":[{"name":"$valdiators","type":"tuple[]","internalType":"struct BootstrapConfig[]","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"$executors","type":"tuple[]","internalType":"struct BootstrapConfig[]","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"_hook","type":"tuple","internalType":"struct BootstrapConfig","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"_fallbacks","type":"tuple[]","internalType":"struct BootstrapConfig[]","components":[{"name":"module","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"singleInitMSA","inputs":[{"name":"validator","type":"address","internalType":"contract IModule"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"error","name":"AccountAccessUnauthorized","inputs":[]},{"type":"error","name":"CannotRemoveLastValidator","inputs":[]},{"type":"error","name":"HookAlreadyInstalled","inputs":[{"name":"currentHook","type":"address","internalType":"address"}]},{"type":"error","name":"HookPostCheckFailed","inputs":[]},{"type":"error","name":"InvalidModule","inputs":[{"name":"module","type":"address","internalType":"address"}]},{"type":"error","name":"LinkedList_EntryAlreadyInList","inputs":[{"name":"entry","type":"address","internalType":"address"}]},{"type":"error","name":"LinkedList_InvalidEntry","inputs":[{"name":"entry","type":"address","internalType":"address"}]},{"type":"error","name":"LinkedList_InvalidPage","inputs":[]},{"type":"error","name":"NoFallbackHandler","inputs":[{"name":"selector","type":"bytes4","internalType":"bytes4"}]}] \ No newline at end of file diff --git a/crates/json-abi/tests/abi/Bootstrap.sol b/crates/json-abi/tests/abi/Bootstrap.sol new file mode 100644 index 0000000000..b449f8c89f --- /dev/null +++ b/crates/json-abi/tests/abi/Bootstrap.sol @@ -0,0 +1,37 @@ +library ModuleManager { + struct FallbackHandler { + address handler; + CallType calltype; + } +} + +interface Bootstrap { + type CallType is bytes1; + struct BootstrapConfig { + address module; + bytes data; + } + + error AccountAccessUnauthorized(); + error CannotRemoveLastValidator(); + error HookAlreadyInstalled(address currentHook); + error HookPostCheckFailed(); + error InvalidModule(address module); + error LinkedList_EntryAlreadyInList(address entry); + error LinkedList_InvalidEntry(address entry); + error LinkedList_InvalidPage(); + error NoFallbackHandler(bytes4 selector); + + fallback() external payable; + + receive() external payable; + + function _getInitMSACalldata(BootstrapConfig[] memory _valdiators, BootstrapConfig[] memory _executors, BootstrapConfig memory _hook, BootstrapConfig[] memory _fallbacks) external view returns (bytes memory init); + function entryPoint() external view returns (address); + function getActiveFallbackHandler(bytes4 functionSig) external view returns (ModuleManager.FallbackHandler memory); + function getActiveHook() external view returns (address hook); + function getExecutorsPaginated(address cursor, uint256 size) external view returns (address[] memory array, address next); + 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 diff --git a/crates/json-abi/tests/abi/DollarIdentifiers.json b/crates/json-abi/tests/abi/DollarIdentifiers.json new file mode 100644 index 0000000000..0d27e2befc --- /dev/null +++ b/crates/json-abi/tests/abi/DollarIdentifiers.json @@ -0,0 +1 @@ +[{"inputs":[{"components":[{"internalType":"uint256","name":"$dField","type":"uint256"}],"internalType":"struct DollarIdentifiers.$dStruct","name":"$dStructArg","type":"tuple"},{"internalType":"enum DollarIdentifiers.$dEnum","name":"$dEnumArg","type":"uint8"},{"internalType":"DollarIdentifiers.$dUDVT","name":"$dUDVTArg","type":"uint256"}],"name":"$dFunction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"$dPublicVariable","outputs":[{"internalType":"uint256","name":"$dField","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/crates/json-abi/tests/abi/DollarIdentifiers.sol b/crates/json-abi/tests/abi/DollarIdentifiers.sol new file mode 100644 index 0000000000..3efc261437 --- /dev/null +++ b/crates/json-abi/tests/abi/DollarIdentifiers.sol @@ -0,0 +1,10 @@ +interface DollarIdentifiers { + type _dEnum is uint8; + type _dUDVT is uint256; + struct _dStruct { + uint256 _dField; + } + + function _dFunction(_dStruct memory _dStructArg, _dEnum _dEnumArg, _dUDVT _dUDVTArg) external; + function _dPublicVariable() external view returns (uint256 _dField); +} \ No newline at end of file diff --git a/crates/sol-macro-input/src/json.rs b/crates/sol-macro-input/src/json.rs index 8d451a9d5b..f9b955e88a 100644 --- a/crates/sol-macro-input/src/json.rs +++ b/crates/sol-macro-input/src/json.rs @@ -89,7 +89,7 @@ Generated by the following Solidity interface... let ast: ast::File = syn::parse2(tokens).map_err(|e| { let msg = format!( - "failed to parse ABI-generated tokens into a Solidity AST: {e}.\n\ + "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" ); @@ -105,14 +105,15 @@ Generated by the following Solidity interface... fn abi_to_sol(name: &Ident, abi: &mut JsonAbi) -> String { abi.dedup(); - abi.to_sol(&name.to_string(), Some(ToSolConfig::new().print_constructors(true))) + let config = ToSolConfig::new().print_constructors(true).for_sol_macro(true); + abi.to_sol(&name.to_string(), Some(config)) } /// Returns `sol!` tokens. pub fn tokens_for_sol(name: &Ident, sol: &str) -> Result { let mk_err = |s: &str| { let msg = format!( - "`JsonAbi::to_sol` generated invalid Rust tokens: {s}\n\ + "`JsonAbi::to_sol` generated invalid Rust tokens for `{name}`: {s}\n\ This is a bug. We would appreciate a bug report: \ https://github.com/alloy-rs/core/issues/new/choose" ); diff --git a/crates/sol-types/tests/macros/sol/json.rs b/crates/sol-types/tests/macros/sol/json.rs index 4af9b030b6..8751a598fc 100644 --- a/crates/sol-types/tests/macros/sol/json.rs +++ b/crates/sol-types/tests/macros/sol/json.rs @@ -233,3 +233,9 @@ fn balancer_v2_vault() { // fn eigenlayer_delegation_manager() { // sol!(DelegationManager, "../json-abi/tests/abi/DelegationManager.json"); // } + +// TODO: https://github.com/alloy-rs/core/issues/746 +// #[test] +// fn smartsession_bootstrap() { +// sol!(Bootstrap, "../json-abi/tests/abi/Bootstrap.json"); +// } diff --git a/scripts/check_no_std.sh b/scripts/check_no_std.sh index 3fcd1c2e89..e7d1346e57 100755 --- a/scripts/check_no_std.sh +++ b/scripts/check_no_std.sh @@ -7,7 +7,6 @@ crates=( alloy-core-sol-test alloy-dyn-abi alloy-json-abi - alloy-json-abi alloy-primitives # alloy-sol-macro # alloy-sol-macro-expander