alloy_json_abi/
to_sol.rs

1use crate::{
2    item::{Constructor, Error, Event, Fallback, Function, Receive},
3    EventParam, InternalType, JsonAbi, Param, StateMutability,
4};
5use alloc::{
6    borrow::Cow,
7    collections::{BTreeMap, BTreeSet},
8    string::String,
9    vec::Vec,
10};
11use core::{
12    cmp::Ordering,
13    ops::{Deref, DerefMut},
14};
15
16/// Configuration for [`JsonAbi::to_sol`].
17#[derive(Clone, Debug)]
18#[allow(missing_copy_implementations)] // Future-proofing
19pub struct ToSolConfig {
20    print_constructors: bool,
21    enums_as_udvt: bool,
22    for_sol_macro: bool,
23    one_contract: bool,
24}
25
26impl Default for ToSolConfig {
27    #[inline]
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl ToSolConfig {
34    /// Creates a new configuration with default settings.
35    #[inline]
36    pub const fn new() -> Self {
37        Self {
38            print_constructors: false,
39            enums_as_udvt: true,
40            for_sol_macro: false,
41            one_contract: false,
42        }
43    }
44
45    /// Sets whether to print constructors. Default: `false`.
46    #[inline]
47    pub const fn print_constructors(mut self, yes: bool) -> Self {
48        self.print_constructors = yes;
49        self
50    }
51
52    /// Sets whether to print `enum`s as user-defined value types (UDVTs) instead of `uint8`.
53    /// Default: `true`.
54    #[inline]
55    pub const fn enums_as_udvt(mut self, yes: bool) -> Self {
56        self.enums_as_udvt = yes;
57        self
58    }
59
60    /// Sets whether to normalize the output for the [`sol!`] macro. Default: `false`.
61    ///
62    /// [`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html
63    pub const fn for_sol_macro(mut self, yes: bool) -> Self {
64        self.for_sol_macro = yes;
65        self
66    }
67
68    /// If set to `true`, any types part of some other interface/library are
69    /// generated in one contract. Default is false.
70    ///
71    /// This breaks if there are structs with the same name in different interfaces.
72    pub const fn one_contract(mut self, yes: bool) -> Self {
73        self.one_contract = yes;
74        self
75    }
76}
77
78pub(crate) trait ToSol {
79    fn to_sol(&self, out: &mut SolPrinter<'_>);
80}
81
82pub(crate) struct SolPrinter<'a> {
83    /// The buffer to write to.
84    s: &'a mut String,
85
86    /// The name of the current library/interface being printed.
87    name: &'a str,
88
89    /// Whether to emit `memory` when printing parameters.
90    /// This is set to `true` when printing functions so that we emit valid Solidity.
91    print_param_location: bool,
92
93    /// Configuration.
94    config: ToSolConfig,
95}
96
97impl Deref for SolPrinter<'_> {
98    type Target = String;
99
100    #[inline]
101    fn deref(&self) -> &Self::Target {
102        self.s
103    }
104}
105
106impl DerefMut for SolPrinter<'_> {
107    #[inline]
108    fn deref_mut(&mut self) -> &mut Self::Target {
109        self.s
110    }
111}
112
113impl<'a> SolPrinter<'a> {
114    pub(crate) fn new(s: &'a mut String, name: &'a str, config: ToSolConfig) -> Self {
115        Self { s, name, print_param_location: false, config }
116    }
117
118    pub(crate) fn print(&mut self, abi: &'a JsonAbi) {
119        abi.to_sol_root(self);
120    }
121
122    fn indent(&mut self) {
123        self.push_str("    ");
124    }
125
126    /// Normalizes `s` as a Rust identifier and pushes it to the buffer.
127    ///
128    /// See [`Self::normalize_ident`] for more details.
129    fn push_ident(&mut self, s: &str) {
130        let s = self.normalize_ident(s);
131        self.push_str(&s);
132    }
133
134    /// Normalizes `s` as a Rust identifier.
135    ///
136    /// All Solidity identifiers are also valid Rust identifiers, except for `$`.
137    /// This function replaces `$` with `_` if the configuration is set to normalize for the `sol!`
138    /// macro.
139    fn normalize_ident<'b>(&self, s: &'b str) -> Cow<'b, str> {
140        if self.config.for_sol_macro && s.contains('$') {
141            Cow::Owned(s.replace('$', "_"))
142        } else {
143            Cow::Borrowed(s)
144        }
145    }
146}
147
148impl JsonAbi {
149    #[allow(unknown_lints, for_loops_over_fallibles)]
150    fn to_sol_root<'a>(&'a self, out: &mut SolPrinter<'a>) {
151        macro_rules! fmt {
152            ($iter:expr) => {
153                let mut any = false;
154                for x in $iter {
155                    any = true;
156                    out.indent();
157                    x.to_sol(out);
158                    out.push('\n');
159                }
160                if any {
161                    out.push('\n');
162                }
163            };
164        }
165
166        let mut its = InternalTypes::new(out.name, out.config.enums_as_udvt);
167        its.visit_abi(self);
168
169        let one_contract = out.config.one_contract;
170
171        if !one_contract {
172            for (name, its) in &its.other {
173                if its.is_empty() {
174                    continue;
175                }
176                out.push_str("library ");
177                out.push_str(name);
178                out.push_str(" {\n");
179                let prev = core::mem::replace(&mut out.name, name);
180                for it in its {
181                    out.indent();
182                    it.to_sol(out);
183                    out.push('\n');
184                }
185                out.name = prev;
186                out.push_str("}\n\n");
187            }
188        }
189
190        out.push_str("interface ");
191        if !out.name.is_empty() {
192            out.s.push_str(out.name);
193            out.push(' ');
194        }
195        out.push('{');
196        out.push('\n');
197
198        if one_contract {
199            for (name, its) in &its.other {
200                if its.is_empty() {
201                    continue;
202                }
203
204                out.indent();
205                out.push_str("// Types from `");
206                out.push_str(name);
207                out.push_str("`\n");
208                fmt!(its);
209            }
210        }
211        fmt!(its.this_its);
212        fmt!(self.errors());
213        fmt!(self.events());
214        if out.config.print_constructors {
215            fmt!(self.constructor());
216        }
217        fmt!(self.fallback);
218        fmt!(self.receive);
219        fmt!(self.functions());
220        out.pop(); // trailing newline
221
222        out.push('}');
223    }
224}
225
226/// Recursively collects internal structs, enums, and UDVTs from an ABI's items.
227struct InternalTypes<'a> {
228    name: &'a str,
229    this_its: BTreeSet<It<'a>>,
230    other: BTreeMap<&'a String, BTreeSet<It<'a>>>,
231    enums_as_udvt: bool,
232}
233
234impl<'a> InternalTypes<'a> {
235    #[allow(clippy::missing_const_for_fn)]
236    fn new(name: &'a str, enums_as_udvt: bool) -> Self {
237        Self { name, this_its: BTreeSet::new(), other: BTreeMap::new(), enums_as_udvt }
238    }
239
240    fn visit_abi(&mut self, abi: &'a JsonAbi) {
241        if let Some(constructor) = &abi.constructor {
242            self.visit_params(&constructor.inputs);
243        }
244        for function in abi.functions() {
245            self.visit_params(&function.inputs);
246            self.visit_params(&function.outputs);
247        }
248        for error in abi.errors() {
249            self.visit_params(&error.inputs);
250        }
251        for event in abi.events() {
252            self.visit_event_params(&event.inputs);
253        }
254    }
255
256    fn visit_params(&mut self, params: &'a [Param]) {
257        for param in params {
258            self.visit_param(param);
259        }
260    }
261
262    fn visit_param(&mut self, param: &'a Param) {
263        self.extend(param.internal_type.as_ref(), &param.components, &param.ty);
264        self.visit_params(&param.components);
265    }
266
267    fn visit_event_params(&mut self, params: &'a [EventParam]) {
268        for param in params {
269            self.visit_event_param(param);
270        }
271    }
272
273    fn visit_event_param(&mut self, param: &'a EventParam) {
274        self.extend(param.internal_type.as_ref(), &param.components, &param.ty);
275        self.visit_params(&param.components);
276    }
277
278    fn extend(
279        &mut self,
280        internal_type: Option<&'a InternalType>,
281        components: &'a Vec<Param>,
282        real_ty: &'a str,
283    ) {
284        match internal_type {
285            None | Some(InternalType::AddressPayable(_) | InternalType::Contract(_)) => {}
286            Some(InternalType::Struct { contract, ty }) => {
287                self.extend_one(contract, It::new(ty, ItKind::Struct(components)));
288            }
289            Some(InternalType::Enum { contract, ty }) => {
290                if self.enums_as_udvt {
291                    self.extend_one(contract, It::new(ty, ItKind::Enum));
292                }
293            }
294            Some(it @ InternalType::Other { contract, ty }) => {
295                // `Other` is a UDVT if it's not a basic Solidity type.
296                if let Some(it) = it.other_specifier() {
297                    if it.try_basic_solidity().is_err() {
298                        let ty = ty.split('[').next().unwrap();
299                        let real_ty = real_ty.split('[').next().unwrap();
300                        self.extend_one(contract, It::new(ty, ItKind::Udvt(real_ty)));
301                    }
302                }
303            }
304        }
305    }
306
307    fn extend_one(&mut self, contract: &'a Option<String>, it: It<'a>) {
308        let contract = contract.as_ref();
309        if let Some(contract) = contract {
310            if contract == self.name {
311                self.this_its.insert(it);
312            } else {
313                self.other.entry(contract).or_default().insert(it);
314            }
315        } else {
316            self.this_its.insert(it);
317        }
318    }
319}
320
321/// An internal ABI type.
322#[derive(PartialEq, Eq, PartialOrd, Ord)]
323struct It<'a> {
324    // kind must come before name for `Ord`
325    kind: ItKind<'a>,
326    name: &'a str,
327}
328
329#[derive(PartialEq, Eq)]
330enum ItKind<'a> {
331    Enum,
332    Udvt(&'a str),
333    Struct(&'a Vec<Param>),
334}
335
336// implemented manually because `Param: !Ord`
337impl PartialOrd for ItKind<'_> {
338    #[inline]
339    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
340        Some(self.cmp(other))
341    }
342}
343
344impl Ord for ItKind<'_> {
345    #[inline]
346    fn cmp(&self, other: &Self) -> Ordering {
347        match (self, other) {
348            (Self::Enum, Self::Enum) => Ordering::Equal,
349            (Self::Enum, _) => Ordering::Less,
350            (_, Self::Enum) => Ordering::Greater,
351
352            (Self::Udvt(_), Self::Udvt(_)) => Ordering::Equal,
353            (Self::Udvt(_), _) => Ordering::Less,
354            (_, Self::Udvt(_)) => Ordering::Greater,
355
356            (Self::Struct(_), Self::Struct(_)) => Ordering::Equal,
357        }
358    }
359}
360
361impl<'a> It<'a> {
362    #[inline]
363    fn new(ty_name: &'a str, kind: ItKind<'a>) -> Self {
364        Self {
365            kind,
366            // `ty_name` might be an array, we just want the identifier
367            name: ty_name.split('[').next().unwrap(),
368        }
369    }
370}
371
372impl ToSol for It<'_> {
373    fn to_sol(&self, out: &mut SolPrinter<'_>) {
374        match self.kind {
375            ItKind::Enum => {
376                out.push_str("type ");
377                out.push_ident(self.name);
378                out.push_str(" is uint8;");
379            }
380            ItKind::Udvt(ty) => {
381                out.push_str("type ");
382                out.push_ident(self.name);
383                out.push_str(" is ");
384                out.push_str(ty);
385                out.push(';');
386            }
387            ItKind::Struct(components) => {
388                out.push_str("struct ");
389                out.push_ident(self.name);
390                out.push_str(" {\n");
391                for component in components {
392                    out.indent();
393                    out.indent();
394                    component.to_sol(out);
395                    out.push_str(";\n");
396                }
397                out.indent();
398                out.push('}');
399            }
400        }
401    }
402}
403
404impl ToSol for Constructor {
405    fn to_sol(&self, out: &mut SolPrinter<'_>) {
406        AbiFunction::<'_, Param> {
407            kw: AbiFunctionKw::Constructor,
408            name: None,
409            inputs: &self.inputs,
410            visibility: None,
411            state_mutability: Some(self.state_mutability),
412            anonymous: false,
413            outputs: &[],
414        }
415        .to_sol(out);
416    }
417}
418
419impl ToSol for Event {
420    fn to_sol(&self, out: &mut SolPrinter<'_>) {
421        AbiFunction::<'_, EventParam> {
422            kw: AbiFunctionKw::Event,
423            name: Some(&self.name),
424            inputs: &self.inputs,
425            visibility: None,
426            state_mutability: None,
427            anonymous: self.anonymous,
428            outputs: &[],
429        }
430        .to_sol(out);
431    }
432}
433
434impl ToSol for Error {
435    fn to_sol(&self, out: &mut SolPrinter<'_>) {
436        AbiFunction::<'_, Param> {
437            kw: AbiFunctionKw::Error,
438            name: Some(&self.name),
439            inputs: &self.inputs,
440            visibility: None,
441            state_mutability: None,
442            anonymous: false,
443            outputs: &[],
444        }
445        .to_sol(out);
446    }
447}
448
449impl ToSol for Fallback {
450    fn to_sol(&self, out: &mut SolPrinter<'_>) {
451        AbiFunction::<'_, Param> {
452            kw: AbiFunctionKw::Fallback,
453            name: None,
454            inputs: &[],
455            visibility: Some(Visibility::External),
456            state_mutability: Some(self.state_mutability),
457            anonymous: false,
458            outputs: &[],
459        }
460        .to_sol(out);
461    }
462}
463
464impl ToSol for Receive {
465    fn to_sol(&self, out: &mut SolPrinter<'_>) {
466        AbiFunction::<'_, Param> {
467            kw: AbiFunctionKw::Receive,
468            name: None,
469            inputs: &[],
470            visibility: Some(Visibility::External),
471            state_mutability: Some(self.state_mutability),
472            anonymous: false,
473            outputs: &[],
474        }
475        .to_sol(out);
476    }
477}
478
479impl ToSol for Function {
480    fn to_sol(&self, out: &mut SolPrinter<'_>) {
481        AbiFunction::<'_, Param> {
482            kw: AbiFunctionKw::Function,
483            name: Some(&self.name),
484            inputs: &self.inputs,
485            visibility: Some(Visibility::External),
486            state_mutability: Some(self.state_mutability),
487            anonymous: false,
488            outputs: &self.outputs,
489        }
490        .to_sol(out);
491    }
492}
493
494struct AbiFunction<'a, IN> {
495    kw: AbiFunctionKw,
496    name: Option<&'a str>,
497    inputs: &'a [IN],
498    visibility: Option<Visibility>,
499    state_mutability: Option<StateMutability>,
500    anonymous: bool,
501    outputs: &'a [Param],
502}
503
504enum AbiFunctionKw {
505    Constructor,
506    Function,
507    Fallback,
508    Receive,
509    Error,
510    Event,
511}
512
513impl AbiFunctionKw {
514    #[inline]
515    const fn as_str(&self) -> &'static str {
516        match self {
517            Self::Constructor => "constructor",
518            Self::Function => "function",
519            Self::Fallback => "fallback",
520            Self::Receive => "receive",
521            Self::Error => "error",
522            Self::Event => "event",
523        }
524    }
525}
526
527enum Visibility {
528    External,
529}
530
531impl Visibility {
532    #[inline]
533    const fn as_str(&self) -> &'static str {
534        match self {
535            Self::External => "external",
536        }
537    }
538}
539
540impl<IN: ToSol> ToSol for AbiFunction<'_, IN> {
541    fn to_sol(&self, out: &mut SolPrinter<'_>) {
542        if matches!(
543            self.kw,
544            AbiFunctionKw::Function | AbiFunctionKw::Fallback | AbiFunctionKw::Receive
545        ) {
546            out.print_param_location = true;
547        }
548
549        // TODO: Enable once `#[sol(rename)]` is implemented.
550        // if let Some(name) = self.name {
551        //     if out.config.for_sol_macro && name.contains('$') {
552        //         write!(out, "#[sol(rename = \"{name}\")]").unwrap();
553        //     }
554        // }
555
556        out.push_str(self.kw.as_str());
557        if let Some(name) = self.name {
558            out.push(' ');
559            out.push_ident(name);
560        }
561
562        out.push('(');
563        for (i, input) in self.inputs.iter().enumerate() {
564            if i > 0 {
565                out.push_str(", ");
566            }
567            input.to_sol(out);
568        }
569        out.push(')');
570
571        if let Some(visibility) = &self.visibility {
572            out.push(' ');
573            out.push_str(visibility.as_str());
574        }
575
576        if let Some(state_mutability) = self.state_mutability {
577            if let Some(state_mutability) = state_mutability.as_str() {
578                out.push(' ');
579                out.push_str(state_mutability);
580            }
581        }
582
583        if !self.outputs.is_empty() {
584            out.push_str(" returns (");
585            for (i, output) in self.outputs.iter().enumerate() {
586                if i > 0 {
587                    out.push_str(", ");
588                }
589                output.to_sol(out);
590            }
591            out.push(')');
592        }
593
594        if self.anonymous {
595            out.push_str(" anonymous");
596        }
597
598        out.push(';');
599
600        out.print_param_location = false;
601    }
602}
603
604impl ToSol for Param {
605    fn to_sol(&self, out: &mut SolPrinter<'_>) {
606        param(&self.ty, self.internal_type.as_ref(), false, &self.name, &self.components, out);
607    }
608}
609
610impl ToSol for EventParam {
611    fn to_sol(&self, out: &mut SolPrinter<'_>) {
612        param(
613            &self.ty,
614            self.internal_type.as_ref(),
615            self.indexed,
616            &self.name,
617            &self.components,
618            out,
619        );
620    }
621}
622
623fn param(
624    type_name: &str,
625    internal_type: Option<&InternalType>,
626    indexed: bool,
627    name: &str,
628    components: &[Param],
629    out: &mut SolPrinter<'_>,
630) {
631    let mut contract_name = None::<&str>;
632    let mut type_name = type_name;
633    let storage;
634    if let Some(it) = internal_type {
635        (contract_name, type_name) = match it {
636            InternalType::Contract(s) => {
637                let ty = if let Some(start) = s.find('[') {
638                    storage = format!("address{}", &s[start..]);
639                    &storage
640                } else {
641                    "address"
642                };
643                (None, ty)
644            }
645            InternalType::Enum { .. } if !out.config.enums_as_udvt => (None, "uint8"),
646            InternalType::AddressPayable(ty) => (None, &ty[..]),
647            InternalType::Struct { contract, ty }
648            | InternalType::Enum { contract, ty }
649            | InternalType::Other { contract, ty } => {
650                if !out.config.one_contract {
651                    (contract.as_deref(), &ty[..])
652                } else {
653                    (None, &ty[..])
654                }
655            }
656        };
657    };
658
659    match type_name.strip_prefix("tuple") {
660        // This condition is met only for JSON ABIs emitted by Solc 0.4.X which don't contain
661        // `internalType` fields and instead all structs are emitted as unnamed tuples.
662        // See https://github.com/alloy-rs/core/issues/349
663        Some(rest) if rest.is_empty() || rest.starts_with('[') => {
664            // note: this does not actually emit valid Solidity because there are no inline
665            // tuple types `(T, U, V, ...)`, but it's valid for `sol!`.
666            out.push('(');
667            // Don't emit `memory` for tuple components because `sol!` can't parse them.
668            let prev = core::mem::replace(&mut out.print_param_location, false);
669            for (i, component) in components.iter().enumerate() {
670                if i > 0 {
671                    out.push_str(", ");
672                }
673                param(
674                    &component.ty,
675                    component.internal_type.as_ref(), // this is probably always None
676                    false,
677                    "", // don't emit names in types
678                    &component.components,
679                    out,
680                );
681            }
682            out.print_param_location = prev;
683            // trailing comma for single-element tuples
684            if components.len() == 1 {
685                out.push(',');
686            }
687            out.push(')');
688            // could be array sizes
689            out.push_str(rest);
690        }
691        // primitive type
692        _ => {
693            if let Some(contract_name) = contract_name {
694                if contract_name != out.name {
695                    out.push_ident(contract_name);
696                    out.push('.');
697                }
698            }
699            out.push_ident(type_name);
700        }
701    }
702
703    // add `memory` if required (functions)
704    let is_memory = match type_name {
705        // `bytes`, `string`, `T[]`, `T[N]`, tuple/custom type
706        "bytes" | "string" => true,
707        s => s.ends_with(']') || !components.is_empty(),
708    };
709    if out.print_param_location && is_memory {
710        out.push_str(" memory");
711    }
712
713    if indexed {
714        out.push_str(" indexed");
715    }
716    if !name.is_empty() {
717        out.push(' ');
718        out.push_ident(name);
719    }
720}