solar_sema/ty/
abi.rs

1use super::{Gcx, Ty, TyKind};
2use crate::hir;
3use alloy_json_abi as json;
4use solar_ast::ElementaryType;
5use std::{fmt, ops::ControlFlow};
6
7impl<'gcx> Gcx<'gcx> {
8    /// Formats the ABI signature of a function in the form `{name}({tys},*)`.
9    pub(super) fn mk_abi_signature(
10        self,
11        name: &str,
12        tys: impl IntoIterator<Item = Ty<'gcx>>,
13    ) -> String {
14        let mut s = String::with_capacity(64);
15        s.push_str(name);
16        TyAbiPrinter::new(self, &mut s, TyAbiPrinterMode::Signature).print_tuple(tys).unwrap();
17        s
18    }
19
20    /// Returns the ABI of the given contract.
21    ///
22    /// Reference: <https://docs.soliditylang.org/en/develop/abi-spec.html>
23    pub fn contract_abi<'a>(self, id: hir::ContractId) -> Vec<json::AbiItem<'a>> {
24        let mut items = Vec::<json::AbiItem<'a>>::new();
25
26        let c = self.hir.contract(id);
27        if let Some(ctor) = c.ctor
28            && !c.is_abstract()
29        {
30            let json::Function { inputs, state_mutability, .. } = self.function_abi(ctor);
31            items.push(json::Constructor { inputs, state_mutability }.into());
32        }
33        if let Some(fallback) = c.fallback {
34            let json::Function { state_mutability, .. } = self.function_abi(fallback);
35            items.push(json::Fallback { state_mutability }.into());
36        }
37        if let Some(receive) = c.receive {
38            let json::Function { state_mutability, .. } = self.function_abi(receive);
39            items.push(json::Receive { state_mutability }.into());
40        }
41        for f in self.interface_functions(id) {
42            items.push(self.function_abi(f.id).into());
43        }
44        // TODO: Does not include referenced items: https://github.com/paradigmxyz/solar/issues/305
45        // See solc `interfaceEvents` and `interfaceErrors`.
46        for item in self.hir.contract_item_ids(id) {
47            match item {
48                hir::ItemId::Event(id) => items.push(self.event_abi(id).into()),
49                hir::ItemId::Error(id) => items.push(self.error_abi(id).into()),
50                _ => {}
51            }
52        }
53
54        // https://github.com/argotorg/solidity/blob/87d86bfba64d8b88537a4a85c1d71f521986b614/libsolidity/interface/ABI.cpp#L43-L47
55        fn cmp_key<'a>(item: &'a json::AbiItem<'_>) -> impl Ord + use<'a> {
56            (item.json_type(), item.name())
57        }
58        items.sort_by(|a, b| cmp_key(a).cmp(&cmp_key(b)));
59
60        items
61    }
62
63    fn function_abi(self, id: hir::FunctionId) -> json::Function {
64        let f = self.hir.function(id);
65        json::Function {
66            name: f.name.unwrap_or_default().to_string(),
67            inputs: f.parameters.iter().map(|&p| self.var_param_abi(p)).collect(),
68            outputs: f.returns.iter().map(|&p| self.var_param_abi(p)).collect(),
69            state_mutability: json_state_mutability(f.state_mutability),
70        }
71    }
72
73    fn event_abi(self, id: hir::EventId) -> json::Event {
74        let e = self.hir.event(id);
75        json::Event {
76            name: e.name.to_string(),
77            inputs: e.parameters.iter().map(|&p| self.event_param_abi(p)).collect(),
78            anonymous: e.anonymous,
79        }
80    }
81
82    fn error_abi(self, id: hir::ErrorId) -> json::Error {
83        let e = self.hir.error(id);
84        json::Error {
85            name: e.name.to_string(),
86            inputs: e.parameters.iter().map(|&p| self.var_param_abi(p)).collect(),
87        }
88    }
89
90    fn var_param_abi(self, id: hir::VariableId) -> json::Param {
91        let v = self.hir.variable(id);
92        let ty = self.type_of_item(id.into());
93        self.param_abi(ty, v.name.unwrap_or_default().to_string())
94    }
95
96    fn param_abi(self, ty: Ty<'gcx>, name: String) -> json::Param {
97        let ty = ty.peel_refs();
98        let struct_id = ty.visit(&mut |ty| match ty.kind {
99            TyKind::Struct(id) => ControlFlow::Break(id),
100            _ => ControlFlow::Continue(()),
101        });
102        json::Param {
103            ty: self.print_abi_param_ty(ty),
104            name,
105            components: match struct_id {
106                ControlFlow::Break(id) => self
107                    .item_fields(id)
108                    .map(|(ty, f)| self.param_abi(ty, self.item_name(f).to_string()))
109                    .collect(),
110                ControlFlow::Continue(()) => vec![],
111            },
112            internal_type: Some(json::InternalType::parse(&self.print_solc_param_ty(ty)).unwrap()),
113        }
114    }
115
116    fn event_param_abi(self, id: hir::VariableId) -> json::EventParam {
117        let json::Param { ty, name, components, internal_type } = self.var_param_abi(id);
118        let indexed = self.hir.variable(id).indexed;
119        json::EventParam { ty, name, components, internal_type, indexed }
120    }
121
122    fn print_abi_param_ty(self, ty: Ty<'gcx>) -> String {
123        let mut s = String::new();
124        TyAbiPrinter::new(self, &mut s, TyAbiPrinterMode::Abi).print(ty).unwrap();
125        s
126    }
127
128    fn print_solc_param_ty(self, ty: Ty<'gcx>) -> String {
129        let mut s = String::new();
130        TySolcPrinter::new(self, &mut s).data_locations(false).print(ty).unwrap();
131        s
132    }
133}
134
135fn json_state_mutability(s: hir::StateMutability) -> json::StateMutability {
136    match s {
137        hir::StateMutability::Pure => json::StateMutability::Pure,
138        hir::StateMutability::View => json::StateMutability::View,
139        hir::StateMutability::Payable => json::StateMutability::Payable,
140        hir::StateMutability::NonPayable => json::StateMutability::NonPayable,
141    }
142}
143
144/// Prints types as specified by the Solidity ABI.
145///
146/// Reference: <https://docs.soliditylang.org/en/latest/abi-spec.html>
147pub struct TyAbiPrinter<'gcx, W> {
148    gcx: Gcx<'gcx>,
149    buf: W,
150    mode: TyAbiPrinterMode,
151}
152
153/// [`TyAbiPrinter`] configuration.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub enum TyAbiPrinterMode {
156    /// Printing types for a function signature.
157    ///
158    /// Prints the fields of the struct in a tuple, recursively.
159    ///
160    /// Note that this will make the printer panic if it encounters a recursive struct.
161    Signature,
162    /// Printing types for a JSON ABI `type` field.
163    ///
164    /// Print the word `tuple` when encountering structs.
165    Abi,
166}
167
168impl<'gcx, W: fmt::Write> TyAbiPrinter<'gcx, W> {
169    /// Creates a new ABI printer.
170    pub fn new(gcx: Gcx<'gcx>, buf: W, mode: TyAbiPrinterMode) -> Self {
171        Self { gcx, buf, mode }
172    }
173
174    /// Returns a mutable reference to the underlying buffer.
175    pub fn buf(&mut self) -> &mut W {
176        &mut self.buf
177    }
178
179    /// Consumes the printer and returns the underlying buffer.
180    pub fn into_buf(self) -> W {
181        self.buf
182    }
183
184    /// Prints the ABI representation of `ty`.
185    pub fn print(&mut self, ty: Ty<'gcx>) -> fmt::Result {
186        match ty.kind {
187            TyKind::Elementary(ty) => ty.write_abi_str(&mut self.buf),
188            TyKind::Contract(_) => self.buf.write_str("address"),
189            TyKind::FnPtr(_) => self.buf.write_str("function"),
190            TyKind::Struct(id) => match self.mode {
191                TyAbiPrinterMode::Signature => {
192                    if self.gcx.struct_recursiveness(id).is_recursive() {
193                        assert!(
194                            self.gcx.dcx().has_errors().is_err(),
195                            "trying to print recursive struct and no error has been emitted"
196                        );
197                        write!(self.buf, "<recursive struct {}>", self.gcx.item_canonical_name(id))
198                    } else {
199                        self.print_tuple(self.gcx.struct_field_types(id).iter().copied())
200                    }
201                }
202                TyAbiPrinterMode::Abi => self.buf.write_str("tuple"),
203            },
204            TyKind::Enum(_) => self.buf.write_str("uint8"),
205            TyKind::Udvt(ty, _) => self.print(ty),
206            TyKind::Ref(ty, _loc) => self.print(ty),
207            TyKind::DynArray(ty) => {
208                self.print(ty)?;
209                self.buf.write_str("[]")
210            }
211            TyKind::Array(ty, len) => {
212                self.print(ty)?;
213                write!(self.buf, "[{len}]")
214            }
215
216            TyKind::StringLiteral(..)
217            | TyKind::IntLiteral(_)
218            | TyKind::Tuple(_)
219            | TyKind::Mapping(..)
220            | TyKind::Error(..)
221            | TyKind::Event(..)
222            | TyKind::Module(_)
223            | TyKind::BuiltinModule(_)
224            | TyKind::Type(_)
225            | TyKind::Meta(_)
226            | TyKind::Err(_) => panic!("printing unsupported type as ABI: {ty:?}"),
227        }
228    }
229
230    /// Prints `tys` in a comma-delimited parenthesized tuple.
231    pub fn print_tuple(&mut self, tys: impl IntoIterator<Item = Ty<'gcx>>) -> fmt::Result {
232        self.buf.write_str("(")?;
233        for (i, ty) in tys.into_iter().enumerate() {
234            if i > 0 {
235                self.buf.write_str(",")?;
236            }
237            self.print(ty)?;
238        }
239        self.buf.write_str(")")
240    }
241}
242
243/// Prints types as implemented in `Type::toString(bool)` in solc.
244///
245/// This is mainly used in the `internalType` field of the ABI.
246///
247/// Example: <https://github.com/argotorg/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/ast/Types.cpp#L2352-L2358>
248pub(crate) struct TySolcPrinter<'gcx, W> {
249    gcx: Gcx<'gcx>,
250    buf: W,
251    data_locations: bool,
252}
253
254impl<'gcx, W: fmt::Write> TySolcPrinter<'gcx, W> {
255    pub(crate) fn new(gcx: Gcx<'gcx>, buf: W) -> Self {
256        Self { gcx, buf, data_locations: false }
257    }
258
259    /// Whether to print data locations for reference types.
260    ///
261    /// Default: `false`.
262    pub(crate) fn data_locations(mut self, yes: bool) -> Self {
263        self.data_locations = yes;
264        self
265    }
266
267    pub(crate) fn print(&mut self, ty: Ty<'gcx>) -> fmt::Result {
268        match ty.kind {
269            TyKind::Elementary(ty) => {
270                ty.write_abi_str(&mut self.buf)?;
271                if matches!(ty, ElementaryType::Address(true)) {
272                    self.buf.write_str(" payable")?;
273                }
274                Ok(())
275            }
276            TyKind::Contract(id) => {
277                let c = self.gcx.hir.contract(id);
278                self.buf.write_str(if c.kind.is_library() { "library" } else { "contract" })?;
279                write!(self.buf, " {}", c.name)
280            }
281            TyKind::FnPtr(f) => {
282                self.print_function(None, f.parameters, f.returns, f.state_mutability, f.visibility)
283            }
284            TyKind::Struct(id) => {
285                write!(self.buf, "struct {}", self.gcx.item_canonical_name(id))
286            }
287            TyKind::Enum(id) => write!(self.buf, "enum {}", self.gcx.item_canonical_name(id)),
288            TyKind::Udvt(_, id) => write!(self.buf, "{}", self.gcx.item_canonical_name(id)),
289            TyKind::Ref(ty, loc) => {
290                self.print(ty)?;
291                if self.data_locations {
292                    write!(self.buf, " {loc}")?;
293                }
294                Ok(())
295            }
296            TyKind::DynArray(ty) => {
297                self.print(ty)?;
298                self.buf.write_str("[]")
299            }
300            TyKind::Array(ty, len) => {
301                self.print(ty)?;
302                write!(self.buf, "[{len}]")
303            }
304
305            // Internal types.
306            TyKind::StringLiteral(utf8, size) => {
307                let kind = if utf8 { "utf8" } else { "bytes" };
308                write!(self.buf, "{kind}_string_literal[{}]", size.bytes())
309            }
310            TyKind::IntLiteral(size) => {
311                write!(self.buf, "int_literal[{}]", size.bytes())
312            }
313            TyKind::Tuple(tys) => {
314                self.buf.write_str("tuple")?;
315                self.print_tuple(tys)
316            }
317            TyKind::Mapping(key, value) => {
318                self.buf.write_str("mapping(")?;
319                self.print(key)?;
320                self.buf.write_str(" => ")?;
321                self.print(value)?;
322                self.buf.write_str(")")
323            }
324            TyKind::Module(id) => {
325                let s = self.gcx.hir.source(id);
326                write!(self.buf, "module {}", s.file.name.display())
327            }
328            TyKind::BuiltinModule(b) => self.buf.write_str(b.name().as_str()),
329            TyKind::Type(ty) | TyKind::Meta(ty) => {
330                self.buf.write_str("type(")?;
331                self.print(ty)?; // TODO: `richIdentifier`
332                self.buf.write_str(")")
333            }
334            TyKind::Error(tys, id) => self.print_function_like(tys, id.into()),
335            TyKind::Event(tys, id) => self.print_function_like(tys, id.into()),
336
337            TyKind::Err(_) => self.buf.write_str("<error>"),
338        }
339    }
340
341    fn print_function_like(&mut self, parameters: &[Ty<'gcx>], id: hir::ItemId) -> fmt::Result {
342        self.print_function(
343            Some(id),
344            parameters,
345            &[],
346            hir::StateMutability::NonPayable,
347            solar_ast::Visibility::Internal,
348        )
349    }
350
351    fn print_function(
352        &mut self,
353        def: Option<hir::ItemId>,
354        parameters: &[Ty<'gcx>],
355        returns: &[Ty<'gcx>],
356        state_mutability: hir::StateMutability,
357        visibility: hir::Visibility,
358    ) -> fmt::Result {
359        self.buf.write_str("function ")?;
360        if let Some(def) = def {
361            let name = self.gcx.item_canonical_name(def);
362            write!(self.buf, "{name}")?;
363        }
364        self.print_tuple(parameters)?;
365
366        if state_mutability != hir::StateMutability::NonPayable {
367            write!(self.buf, " {state_mutability}")?;
368        }
369        if visibility == hir::Visibility::External {
370            self.buf.write_str(" external")?;
371        }
372
373        if !returns.is_empty() {
374            self.buf.write_str(" returns ")?;
375            self.print_tuple(returns)?;
376        }
377        Ok(())
378    }
379
380    fn print_tuple(&mut self, tys: &[Ty<'gcx>]) -> fmt::Result {
381        self.buf.write_str("(")?;
382        for (i, &ty) in tys.iter().enumerate() {
383            if i > 0 {
384                self.buf.write_str(",")?;
385            }
386            self.print(ty)?;
387        }
388        self.buf.write_str(")")
389    }
390}