Skip to content
50 changes: 44 additions & 6 deletions crates/json-abi/src/to_sol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -39,6 +40,7 @@ impl ToSolConfig {
enums_as_udvt: true,
for_sol_macro: false,
one_contract: false,
standalone_globals: false,
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -92,6 +101,9 @@ pub(crate) struct SolPrinter<'a> {

/// Configuration.
config: ToSolConfig,

/// Current indentation level.
indent_level: usize,
}

impl Deref for SolPrinter<'_> {
Expand All @@ -112,15 +124,17 @@ 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) {
abi.to_sol_root(self);
}

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.
Expand Down Expand Up @@ -163,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;
Expand All @@ -177,11 +192,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");
}
Expand All @@ -195,6 +212,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() {
Expand All @@ -220,6 +238,14 @@ impl JsonAbi {
out.pop(); // trailing newline

out.push('}');
out.indent_level -= 1;
Comment thread
voith marked this conversation as resolved.

if !its.globals.is_empty() {
out.push('\n');
fmt!(its.globals);
out.pop(); // trailing newline
Comment thread
voith marked this conversation as resolved.
out.pop(); // trailing newline
}
}
}

Expand All @@ -228,13 +254,22 @@ struct InternalTypes<'a> {
name: &'a str,
this_its: BTreeSet<It<'a>>,
other: BTreeMap<&'a String, BTreeSet<It<'a>>>,
globals: BTreeSet<It<'a>>,
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 {
Self { name, this_its: BTreeSet::new(), other: BTreeMap::new(), enums_as_udvt }
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,
}
}

fn visit_abi(&mut self, abi: &'a JsonAbi) {
Expand Down Expand Up @@ -312,6 +347,8 @@ impl<'a> InternalTypes<'a> {
} else {
self.other.entry(contract).or_default().insert(it);
}
} else if self.standalone_globals {
self.globals.insert(it);
} else {
self.this_its.insert(it);
}
Expand Down Expand Up @@ -388,12 +425,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('}');
}
Expand Down
88 changes: 88 additions & 0 deletions crates/json-abi/tests/abi/ContractUsingGlobals.json
Comment thread
voith marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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"
}
]
22 changes: 22 additions & 0 deletions crates/json-abi/tests/abi/ContractUsingGlobals.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
library Interface {
struct InterfaceStruct {
GlobalEnum kind;
uint256 count;
}
}

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;
}
29 changes: 29 additions & 0 deletions crates/json-abi/tests/abi/Handler.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
13 changes: 13 additions & 0 deletions crates/json-abi/tests/abi/Handler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
library IHandler {
struct FooBar {
Foo foo;
}
}

interface Handler {
struct Foo {
uint256 newNumber;
}

function handle(IHandler.FooBar memory foobar) external;
}
10 changes: 10 additions & 0 deletions crates/sol-macro-input/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ pub struct SolAttrs {
/// Ignore unlinked bytecode
/// `#[sol(ignore_unlinked)]`
pub ignore_unlinked: Option<bool>,

/// Emit globals at the root when generating from JSON ABI.
/// `#[sol(standalone_globals)]`
pub standalone_globals: Option<bool>,
}

impl SolAttrs {
Expand Down Expand Up @@ -206,6 +210,7 @@ impl SolAttrs {

type_check => lit()?,
ignore_unlinked => bool()?,
standalone_globals => bool()?,
};
Ok(())
})?;
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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) }),
Expand Down
Loading