@@ -13,6 +13,8 @@ use core::{
1313 ops:: { Deref , DerefMut } ,
1414} ;
1515
16+ type EnumDefinitions = BTreeMap < String , Vec < String > > ;
17+
1618/// Configuration for [`JsonAbi::to_sol`].
1719#[ derive( Clone , Debug ) ]
1820#[ allow( missing_copy_implementations) ] // Future-proofing
@@ -21,6 +23,7 @@ pub struct ToSolConfig {
2123 enums_as_udvt : bool ,
2224 for_sol_macro : bool ,
2325 one_contract : bool ,
26+ enum_definitions : EnumDefinitions ,
2427}
2528
2629impl Default for ToSolConfig {
@@ -39,6 +42,7 @@ impl ToSolConfig {
3942 enums_as_udvt : true ,
4043 for_sol_macro : false ,
4144 one_contract : false ,
45+ enum_definitions : EnumDefinitions :: new ( ) ,
4246 }
4347 }
4448
@@ -49,14 +53,25 @@ impl ToSolConfig {
4953 self
5054 }
5155
52- /// Sets whether to print `enum`s as user-defined value types (UDVTs) instead of `uint8`.
53- /// Default: `true`.
56+ /// Sets whether to preserve enum types instead of printing them as `uint8`. Default: `true`.
57+ ///
58+ /// Enums with a supplied definition are printed as enum declarations; otherwise they are
59+ /// printed as `uint8` user-defined value types. If disabled, supplied definitions are ignored.
5460 #[ inline]
5561 pub const fn enums_as_udvt ( mut self , yes : bool ) -> Self {
5662 self . enums_as_udvt = yes;
5763 self
5864 }
5965
66+ /// Sets supplemental enum definitions used when formatting the ABI.
67+ pub fn enum_definitions (
68+ mut self ,
69+ definitions : impl IntoIterator < Item = ( String , Vec < String > ) > ,
70+ ) -> Self {
71+ self . enum_definitions = definitions. into_iter ( ) . collect ( ) ;
72+ self
73+ }
74+
6075 /// Sets whether to normalize the output for the [`sol!`] macro. Default: `false`.
6176 ///
6277 /// [`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html
@@ -163,7 +178,13 @@ impl JsonAbi {
163178 } ;
164179 }
165180
166- let mut its = InternalTypes :: new ( out. name , out. config . enums_as_udvt ) ;
181+ let enum_definitions = core:: mem:: take ( & mut out. config . enum_definitions ) ;
182+ let mut its = InternalTypes :: new (
183+ out. name ,
184+ out. config . enums_as_udvt ,
185+ out. config . one_contract ,
186+ & enum_definitions,
187+ ) ;
167188 its. visit_abi ( self ) ;
168189
169190 let one_contract = out. config . one_contract ;
@@ -224,17 +245,31 @@ impl JsonAbi {
224245}
225246
226247/// Recursively collects internal structs, enums, and UDVTs from an ABI's items.
227- struct InternalTypes < ' a > {
248+ struct InternalTypes < ' a , ' e > {
228249 name : & ' a str ,
229- this_its : BTreeSet < It < ' a > > ,
230- other : BTreeMap < & ' a String , BTreeSet < It < ' a > > > ,
250+ this_its : BTreeSet < It < ' a , ' e > > ,
251+ other : BTreeMap < & ' a String , BTreeSet < It < ' a , ' e > > > ,
231252 enums_as_udvt : bool ,
253+ one_contract : bool ,
254+ enums : & ' e EnumDefinitions ,
232255}
233256
234- impl < ' a > InternalTypes < ' a > {
257+ impl < ' a , ' e > InternalTypes < ' a , ' e > {
235258 #[ 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 }
259+ fn new (
260+ name : & ' a str ,
261+ enums_as_udvt : bool ,
262+ one_contract : bool ,
263+ enums : & ' e EnumDefinitions ,
264+ ) -> Self {
265+ Self {
266+ name,
267+ this_its : BTreeSet :: new ( ) ,
268+ other : BTreeMap :: new ( ) ,
269+ enums_as_udvt,
270+ one_contract,
271+ enums,
272+ }
238273 }
239274
240275 fn visit_abi ( & mut self , abi : & ' a JsonAbi ) {
@@ -288,7 +323,17 @@ impl<'a> InternalTypes<'a> {
288323 }
289324 Some ( InternalType :: Enum { contract, ty } ) => {
290325 if self . enums_as_udvt {
291- self . extend_one ( contract, It :: new ( ty, ItKind :: Enum ) ) ;
326+ let name = ty. split ( '[' ) . next ( ) . unwrap ( ) ;
327+ let name = name. rsplit ( '.' ) . next ( ) . unwrap ( ) ;
328+ let variants = if self . enums . is_empty ( ) {
329+ None
330+ } else if let Some ( contract) = contract {
331+ self . enums . get ( & format ! ( "{contract}.{name}" ) ) . map ( Vec :: as_slice)
332+ } else {
333+ self . enums . get ( name) . map ( Vec :: as_slice)
334+ }
335+ . filter ( |variants| ( 1 ..=256 ) . contains ( & variants. len ( ) ) ) ;
336+ self . extend_one ( contract, It :: new ( ty, ItKind :: Enum ( variants) ) ) ;
292337 }
293338 }
294339 Some ( it @ InternalType :: Other { contract, ty } ) => {
@@ -304,50 +349,89 @@ impl<'a> InternalTypes<'a> {
304349 }
305350 }
306351
307- fn extend_one ( & mut self , contract : & ' a Option < String > , it : It < ' a > ) {
352+ fn extend_one ( & mut self , contract : & ' a Option < String > , it : It < ' a , ' e > ) {
353+ if self . one_contract && matches ! ( it. kind, ItKind :: Enum ( _) ) {
354+ if let Some ( items) = self . other . values_mut ( ) . find ( |items| items. contains ( & it) ) {
355+ Self :: insert_item ( items, it) ;
356+ return ;
357+ }
358+ if self . this_its . contains ( & it) {
359+ Self :: insert_item ( & mut self . this_its , it) ;
360+ return ;
361+ }
362+ }
363+
308364 let contract = contract. as_ref ( ) ;
309- if let Some ( contract) = contract {
365+ let items = if let Some ( contract) = contract {
310366 if contract == self . name {
311- self . this_its . insert ( it ) ;
367+ & mut self . this_its
312368 } else {
313- self . other . entry ( contract) . or_default ( ) . insert ( it ) ;
369+ self . other . entry ( contract) . or_default ( )
314370 }
315371 } else {
316- self . this_its . insert ( it) ;
372+ & mut self . this_its
373+ } ;
374+ Self :: insert_item ( items, it) ;
375+ }
376+
377+ fn insert_item ( items : & mut BTreeSet < It < ' a , ' e > > , it : It < ' a , ' e > ) {
378+ if let ItKind :: Enum ( variants) = it. kind {
379+ let variants = items. take ( & it) . map_or ( variants, |existing| {
380+ let ItKind :: Enum ( existing) = existing. kind else { unreachable ! ( ) } ;
381+ match ( existing, variants) {
382+ ( Some ( existing) , Some ( variants) ) if existing == variants => Some ( existing) ,
383+ _ => None ,
384+ }
385+ } ) ;
386+ items. insert ( It :: new ( it. name , ItKind :: Enum ( variants) ) ) ;
387+ } else {
388+ items. insert ( it) ;
317389 }
318390 }
319391}
320392
321393/// An internal ABI type.
322394#[ derive( PartialEq , Eq , PartialOrd , Ord ) ]
323- struct It < ' a > {
395+ struct It < ' a , ' e > {
324396 // kind must come before name for `Ord`
325- kind : ItKind < ' a > ,
397+ kind : ItKind < ' a , ' e > ,
326398 name : & ' a str ,
327399}
328400
329- #[ derive( PartialEq , Eq ) ]
330- enum ItKind < ' a > {
331- Enum ,
401+ enum ItKind < ' a , ' e > {
402+ Enum ( Option < & ' e [ String ] > ) ,
332403 Udvt ( & ' a str ) ,
333404 Struct ( & ' a Vec < Param > ) ,
334405}
335406
407+ impl PartialEq for ItKind < ' _ , ' _ > {
408+ fn eq ( & self , other : & Self ) -> bool {
409+ matches ! (
410+ ( self , other) ,
411+ ( Self :: Enum ( _) , Self :: Enum ( _) )
412+ | ( Self :: Udvt ( _) , Self :: Udvt ( _) )
413+ | ( Self :: Struct ( _) , Self :: Struct ( _) )
414+ )
415+ }
416+ }
417+
418+ impl Eq for ItKind < ' _ , ' _ > { }
419+
336420// implemented manually because `Param: !Ord`
337- impl PartialOrd for ItKind < ' _ > {
421+ impl PartialOrd for ItKind < ' _ , ' _ > {
338422 #[ inline]
339423 fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
340424 Some ( self . cmp ( other) )
341425 }
342426}
343427
344- impl Ord for ItKind < ' _ > {
428+ impl Ord for ItKind < ' _ , ' _ > {
345429 #[ inline]
346430 fn cmp ( & self , other : & Self ) -> Ordering {
347431 match ( self , other) {
348- ( Self :: Enum , Self :: Enum ) => Ordering :: Equal ,
349- ( Self :: Enum , _) => Ordering :: Less ,
350- ( _, Self :: Enum ) => Ordering :: Greater ,
432+ ( Self :: Enum ( _ ) , Self :: Enum ( _ ) ) => Ordering :: Equal ,
433+ ( Self :: Enum ( _ ) , _) => Ordering :: Less ,
434+ ( _, Self :: Enum ( _ ) ) => Ordering :: Greater ,
351435
352436 ( Self :: Udvt ( _) , Self :: Udvt ( _) ) => Ordering :: Equal ,
353437 ( Self :: Udvt ( _) , _) => Ordering :: Less ,
@@ -358,9 +442,9 @@ impl Ord for ItKind<'_> {
358442 }
359443}
360444
361- impl < ' a > It < ' a > {
445+ impl < ' a , ' e > It < ' a , ' e > {
362446 #[ inline]
363- fn new ( ty_name : & ' a str , kind : ItKind < ' a > ) -> Self {
447+ fn new ( ty_name : & ' a str , kind : ItKind < ' a , ' e > ) -> Self {
364448 Self {
365449 kind,
366450 // `ty_name` might be an array, we just want the identifier
@@ -369,10 +453,22 @@ impl<'a> It<'a> {
369453 }
370454}
371455
372- impl ToSol for It < ' _ > {
456+ impl ToSol for It < ' _ , ' _ > {
373457 fn to_sol ( & self , out : & mut SolPrinter < ' _ > ) {
374- match self . kind {
375- ItKind :: Enum => {
458+ match & self . kind {
459+ ItKind :: Enum ( Some ( variants) ) => {
460+ out. push_str ( "enum " ) ;
461+ out. push_ident ( self . name ) ;
462+ out. push_str ( " { " ) ;
463+ for ( i, variant) in variants. iter ( ) . enumerate ( ) {
464+ if i > 0 {
465+ out. push_str ( ", " ) ;
466+ }
467+ out. push_ident ( variant) ;
468+ }
469+ out. push_str ( " }" ) ;
470+ }
471+ ItKind :: Enum ( None ) => {
376472 out. push_str ( "type " ) ;
377473 out. push_ident ( self . name ) ;
378474 out. push_str ( " is uint8;" ) ;
@@ -388,7 +484,7 @@ impl ToSol for It<'_> {
388484 out. push_str ( "struct " ) ;
389485 out. push_ident ( self . name ) ;
390486 out. push_str ( " {\n " ) ;
391- for component in components {
487+ for component in components. iter ( ) {
392488 out. indent ( ) ;
393489 out. indent ( ) ;
394490 component. to_sol ( out) ;
0 commit comments