123456789101112131415161718192021222324#[cfg(not(feature = "std"))]25use alloc::{string::String, vec::Vec, collections::BTreeMap, format};26#[cfg(feature = "std")]27use std::collections::BTreeMap;28use core::{29 fmt::{self, Write},30 marker::PhantomData,31 cell::{Cell, RefCell},32 cmp::Reverse,33};34use impl_trait_for_tuples::impl_for_tuples;35use crate::types::*;3637#[derive(Default)]38pub struct TypeCollector {39 40 41 structs: RefCell<BTreeMap<string, usize>>,42 anonymous: RefCell<BTreeMap<Vec<string>, usize>>,43 id: Cell<usize>,44}45impl TypeCollector {46 pub fn new() -> Self {47 Self::default()48 }49 pub fn collect(&self, item: string) {50 let id = self.next_id();51 self.structs.borrow_mut().insert(item, id);52 }53 pub fn next_id(&self) -> usize {54 let v = self.id.get();55 self.id.set(v + 1);56 v57 }58 pub fn collect_tuple<T: SolidityTupleType>(&self) -> String {59 let names = T::names(self);60 if let Some(id) = self.anonymous.borrow().get(&names).cloned() {61 return format!("Tuple{}", id);62 }63 let id = self.next_id();64 let mut str = String::new();65 writeln!(str, "/// @dev anonymous struct").unwrap();66 writeln!(str, "struct Tuple{} {{", id).unwrap();67 for (i, name) in names.iter().enumerate() {68 writeln!(str, "\t{} field_{};", name, i).unwrap();69 }70 writeln!(str, "}}").unwrap();71 self.collect(str);72 self.anonymous.borrow_mut().insert(names, id);73 format!("Tuple{}", id)74 }75 pub fn finish(self) -> Vec<string> {76 let mut data = self.structs.into_inner().into_iter().collect::<Vec<_>>();77 data.sort_by_key(|(_, id)| Reverse(*id));78 data.into_iter().map(|(code, _)| code).collect()79 }80}8182pub trait SolidityTypeName: 'static {83 fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;84 85 fn is_simple() -> bool;86 fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;87 88 fn is_void() -> bool {89 false90 }91}92macro_rules! solidity_type_name {93 ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {94 $(95 impl SolidityTypeName for $ty {96 fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {97 write!(writer, $name)98 }99 fn is_simple() -> bool {100 $simple101 }102 fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {103 write!(writer, $default)104 }105 }106 )*107 };108}109110solidity_type_name! {111 uint8 => "uint8" true = "0",112 uint32 => "uint32" true = "0",113 uint64 => "uint64" true = "0",114 uint128 => "uint128" true = "0",115 uint256 => "uint256" true = "0",116 bytes4 => "bytes4" true = "bytes4(0)",117 address => "address" true = "0x0000000000000000000000000000000000000000",118 string => "string" false = "\"\"",119 bytes => "bytes" false = "hex\"\"",120 bool => "bool" true = "false",121}122impl SolidityTypeName for void {123 fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {124 Ok(())125 }126 fn is_simple() -> bool {127 true128 }129 fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {130 Ok(())131 }132 fn is_void() -> bool {133 true134 }135}136137mod sealed {138 139 140 141 142 pub trait CanBePlacedInVec {}143}144145impl sealed::CanBePlacedInVec for uint256 {}146impl sealed::CanBePlacedInVec for string {}147impl sealed::CanBePlacedInVec for address {}148149impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {150 fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {151 T::solidity_name(writer, tc)?;152 write!(writer, "[]")153 }154 fn is_simple() -> bool {155 false156 }157 fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {158 write!(writer, "new ")?;159 T::solidity_name(writer, tc)?;160 write!(writer, "[](0)")161 }162}163164pub trait SolidityTupleType {165 fn names(tc: &TypeCollector) -> Vec<String>;166 fn len() -> usize;167}168169macro_rules! count {170 () => (0usize);171 ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));172}173174macro_rules! impl_tuples {175 ($($ident:ident)+) => {176 impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}177 impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) {178 fn names(tc: &TypeCollector) -> Vec<string> {179 let mut collected = Vec::with_capacity(Self::len());180 $({181 let mut out = string::new();182 $ident::solidity_name(&mut out, tc).expect("no fmt error");183 collected.push(out);184 })*;185 collected186 }187188 fn len() -> usize {189 count!($($ident)*)190 }191 }192 impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) {193 fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {194 write!(writer, "{}", tc.collect_tuple::<Self>())195 }196 fn is_simple() -> bool {197 false198 }199 #[allow(unused_assignments)]200 fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {201 write!(writer, "{}(", tc.collect_tuple::<Self>())?;202 let mut first = true;203 $(204 if !first {205 write!(writer, ",")?;206 } else {207 first = false;208 }209 <$ident>::solidity_default(writer, tc)?;210 )*211 write!(writer, ")")212 }213 }214 };215}216217impl_tuples! {A}218impl_tuples! {A B}219impl_tuples! {A B C}220impl_tuples! {A B C D}221impl_tuples! {A B C D E}222impl_tuples! {A B C D E F}223impl_tuples! {A B C D E F G}224impl_tuples! {A B C D E F G H}225impl_tuples! {A B C D E F G H I}226impl_tuples! {A B C D E F G H I J}227228pub trait SolidityArguments {229 fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;230 fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result;231 fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;232 fn is_empty(&self) -> bool {233 self.len() == 0234 }235 fn len(&self) -> usize;236}237238#[derive(Default)]239pub struct UnnamedArgument<T>(PhantomData<*const T>);240241impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {242 fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {243 if !T::is_void() {244 T::solidity_name(writer, tc)?;245 if !T::is_simple() {246 write!(writer, " memory")?;247 }248 Ok(())249 } else {250 Ok(())251 }252 }253 fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {254 Ok(())255 }256 fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {257 T::solidity_default(writer, tc)258 }259 fn len(&self) -> usize {260 if T::is_void() {261 0262 } else {263 1264 }265 }266}267268pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);269270impl<T> NamedArgument<T> {271 pub fn new(name: &'static str) -> Self {272 Self(name, Default::default())273 }274}275276impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {277 fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {278 if !T::is_void() {279 T::solidity_name(writer, tc)?;280 if !T::is_simple() {281 write!(writer, " memory")?;282 }283 write!(writer, " {}", self.0)284 } else {285 Ok(())286 }287 }288 fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {289 writeln!(writer, "\t{prefix}\t{};", self.0)290 }291 fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {292 T::solidity_default(writer, tc)293 }294 fn len(&self) -> usize {295 if T::is_void() {296 0297 } else {298 1299 }300 }301}302303pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);304305impl<T> SolidityEventArgument<T> {306 pub fn new(indexed: bool, name: &'static str) -> Self {307 Self(indexed, name, Default::default())308 }309}310311impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {312 fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {313 if !T::is_void() {314 T::solidity_name(writer, tc)?;315 if self.0 {316 write!(writer, " indexed")?;317 }318 write!(writer, " {}", self.1)319 } else {320 Ok(())321 }322 }323 fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {324 writeln!(writer, "\t{prefix}\t{};", self.1)325 }326 fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {327 T::solidity_default(writer, tc)328 }329 fn len(&self) -> usize {330 if T::is_void() {331 0332 } else {333 1334 }335 }336}337338impl SolidityArguments for () {339 fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {340 Ok(())341 }342 fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {343 Ok(())344 }345 fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {346 Ok(())347 }348 fn len(&self) -> usize {349 0350 }351}352353#[impl_for_tuples(1, 12)]354impl SolidityArguments for Tuple {355 for_tuples!( where #( Tuple: SolidityArguments ),* );356357 fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {358 let mut first = true;359 for_tuples!( #(360 if !Tuple.is_empty() {361 if !first {362 write!(writer, ", ")?;363 }364 first = false;365 Tuple.solidity_name(writer, tc)?;366 }367 )* );368 Ok(())369 }370 fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {371 for_tuples!( #(372 Tuple.solidity_get(prefix, writer)?;373 )* );374 Ok(())375 }376 fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {377 if self.is_empty() {378 Ok(())379 } else if self.len() == 1 {380 for_tuples!( #(381 Tuple.solidity_default(writer, tc)?;382 )* );383 Ok(())384 } else {385 write!(writer, "(")?;386 let mut first = true;387 for_tuples!( #(388 if !Tuple.is_empty() {389 if !first {390 write!(writer, ", ")?;391 }392 first = false;393 Tuple.solidity_default(writer, tc)?;394 }395 )* );396 write!(writer, ")")?;397 Ok(())398 }399 }400 fn len(&self) -> usize {401 for_tuples!( #( Tuple.len() )+* )402 }403}404405pub trait SolidityFunctions {406 fn solidity_name(407 &self,408 is_impl: bool,409 writer: &mut impl fmt::Write,410 tc: &TypeCollector,411 ) -> fmt::Result;412}413414pub enum SolidityMutability {415 Pure,416 View,417 Mutable,418}419pub struct SolidityFunction<A, R> {420 pub docs: &'static [&'static str],421 pub selector_str: &'static str,422 pub selector: u32,423 pub hide: bool,424 pub name: &'static str,425 pub args: A,426 pub result: R,427 pub mutability: SolidityMutability,428 pub is_payable: bool,429}430impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {431 fn solidity_name(432 &self,433 is_impl: bool,434 writer: &mut impl fmt::Write,435 tc: &TypeCollector,436 ) -> fmt::Result {437 let hide_comment = self.hide.then(|| "// ").unwrap_or("");438 for doc in self.docs {439 writeln!(writer, "\t{hide_comment}///{}", doc)?;440 }441 writeln!(442 writer,443 "\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},",444 self.selector445 )?;446 writeln!(447 writer,448 "\t{hide_comment}/// or in textual repr: {}",449 self.selector_str450 )?;451 write!(writer, "\t{hide_comment}function {}(", self.name)?;452 self.args.solidity_name(writer, tc)?;453 write!(writer, ")")?;454 if is_impl {455 write!(writer, " public")?;456 } else {457 write!(writer, " external")?;458 }459 match &self.mutability {460 SolidityMutability::Pure => write!(writer, " pure")?,461 SolidityMutability::View => write!(writer, " view")?,462 SolidityMutability::Mutable => {}463 }464 if self.is_payable {465 write!(writer, " payable")?;466 }467 if !self.result.is_empty() {468 write!(writer, " returns (")?;469 self.result.solidity_name(writer, tc)?;470 write!(writer, ")")?;471 }472 if is_impl {473 writeln!(writer, " {{")?;474 writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?;475 self.args.solidity_get(hide_comment, writer)?;476 match &self.mutability {477 SolidityMutability::Pure => {}478 SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?,479 SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?,480 }481 if !self.result.is_empty() {482 write!(writer, "\t{hide_comment}\treturn ")?;483 self.result.solidity_default(writer, tc)?;484 writeln!(writer, ";")?;485 }486 writeln!(writer, "\t{hide_comment}}}")?;487 } else {488 writeln!(writer, ";")?;489 }490 if self.hide {491 writeln!(writer, "// FORMATTING: FORCE NEWLINE")?;492 }493 Ok(())494 }495}496497#[impl_for_tuples(0, 48)]498impl SolidityFunctions for Tuple {499 for_tuples!( where #( Tuple: SolidityFunctions ),* );500501 fn solidity_name(502 &self,503 is_impl: bool,504 writer: &mut impl fmt::Write,505 tc: &TypeCollector,506 ) -> fmt::Result {507 let mut first = false;508 for_tuples!( #(509 Tuple.solidity_name(is_impl, writer, tc)?;510 )* );511 Ok(())512 }513}514515pub struct SolidityInterface<F: SolidityFunctions> {516 pub docs: &'static [&'static str],517 pub selector: bytes4,518 pub name: &'static str,519 pub is: &'static [&'static str],520 pub functions: F,521}522523impl<F: SolidityFunctions> SolidityInterface<F> {524 pub fn format(525 &self,526 is_impl: bool,527 out: &mut impl fmt::Write,528 tc: &TypeCollector,529 ) -> fmt::Result {530 const ZERO_BYTES: [u8; 4] = [0; 4];531 for doc in self.docs {532 writeln!(out, "///{}", doc)?;533 }534 if self.selector != ZERO_BYTES {535 writeln!(536 out,537 "/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}",538 u32::from_be_bytes(self.selector)539 )?;540 }541 if is_impl {542 write!(out, "contract ")?;543 } else {544 write!(out, "interface ")?;545 }546 write!(out, "{}", self.name)?;547 if !self.is.is_empty() {548 write!(out, " is")?;549 for (i, n) in self.is.iter().enumerate() {550 if i != 0 {551 write!(out, ",")?;552 }553 write!(out, " {}", n)?;554 }555 }556 writeln!(out, " {{")?;557 self.functions.solidity_name(is_impl, out, tc)?;558 writeln!(out, "}}")?;559 Ok(())560 }561}562563pub struct SolidityEvent<A> {564 pub name: &'static str,565 pub args: A,566}567568impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {569 fn solidity_name(570 &self,571 _is_impl: bool,572 writer: &mut impl fmt::Write,573 tc: &TypeCollector,574 ) -> fmt::Result {575 write!(writer, "\tevent {}(", self.name)?;576 self.args.solidity_name(writer, tc)?;577 writeln!(writer, ");")578 }579}