git.delta.rocks / unique-network / refs/commits / e62b55890acd

difftreelog

source

crates/evm-coder/src/solidity.rs18.3 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! Implementation detail of [`crate::solidity_interface`] macro code-generation.18//! You should not rely on any public item from this module, as it is only intended to be used19//! by procedural macro, API and output format may be changed at any time.20//!21//! Purpose of this module is to receive solidity contract definition in module-specified22//! format, and then output string, representing interface of this contract in solidity language2324#[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::*, custom_signature::SignatureUnit};3637#[derive(Default)]38pub struct TypeCollector {39	/// Code => id40	/// id ordering is required to perform topo-sort on the resulting data41	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 collect_struct<T: StructCollect>(&self) -> String {76		self.collect(<T as StructCollect>::declaration());77		<T as StructCollect>::name()78	}79	pub fn finish(self) -> Vec<string> {80		let mut data = self.structs.into_inner().into_iter().collect::<Vec<_>>();81		data.sort_by_key(|(_, id)| Reverse(*id));82		data.into_iter().map(|(code, _)| code).collect()83	}84}8586pub trait StructCollect: 'static {87	/// Structure name.88	fn name() -> String;89	/// Structure declaration.90	fn declaration() -> String;91}9293pub trait SolidityTypeName: 'static {94	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;95	/// "simple" types are stored inline, no `memory` modifier should be used in solidity96	fn is_simple() -> bool;97	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;98	/// Specialization99	fn is_void() -> bool {100		false101	}102}103macro_rules! solidity_type_name {104    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {105        $(106            impl SolidityTypeName for $ty {107                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {108                    write!(writer, $name)109                }110				fn is_simple() -> bool {111					$simple112				}113				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {114					write!(writer, $default)115				}116            }117        )*118    };119}120121solidity_type_name! {122	uint8 => "uint8" true = "0",123	uint32 => "uint32" true = "0",124	uint64 => "uint64" true = "0",125	uint128 => "uint128" true = "0",126	uint256 => "uint256" true = "0",127	bytes4 => "bytes4" true = "bytes4(0)",128	address => "address" true = "0x0000000000000000000000000000000000000000",129	string => "string" false = "\"\"",130	bytes => "bytes" false = "hex\"\"",131	bool => "bool" true = "false",132}133impl SolidityTypeName for void {134	fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {135		Ok(())136	}137	fn is_simple() -> bool {138		true139	}140	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {141		Ok(())142	}143	fn is_void() -> bool {144		true145	}146}147148mod sealed {149	/// Not every type should be directly placed in vec.150	/// Vec encoding is not memory efficient, as every item will be padded151	/// to 32 bytes.152	/// Instead you should use specialized types (`bytes` in case of `Vec<u8>`)153	pub trait CanBePlacedInVec {}154}155156impl sealed::CanBePlacedInVec for uint256 {}157impl sealed::CanBePlacedInVec for string {}158impl sealed::CanBePlacedInVec for address {}159impl sealed::CanBePlacedInVec for EthCrossAccount {}160impl sealed::CanBePlacedInVec for Property {}161162impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {163	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {164		T::solidity_name(writer, tc)?;165		write!(writer, "[]")166	}167	fn is_simple() -> bool {168		false169	}170	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {171		write!(writer, "new ")?;172		T::solidity_name(writer, tc)?;173		write!(writer, "[](0)")174	}175}176177impl SolidityTupleType for EthCrossAccount {178	fn names(tc: &TypeCollector) -> Vec<string> {179		let mut collected = Vec::with_capacity(Self::len());180		{181			let mut out = string::new();182			address::solidity_name(&mut out, tc).expect("no fmt error");183			collected.push(out);184		}185		{186			let mut out = string::new();187			uint256::solidity_name(&mut out, tc).expect("no fmt error");188			collected.push(out);189		}190		collected191	}192193	fn len() -> usize {194		2195	}196}197198impl SolidityTypeName for EthCrossAccount {199	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {200		write!(writer, "{}", tc.collect_struct::<Self>())201	}202203	fn is_simple() -> bool {204		false205	}206207	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {208		write!(writer, "{}(", tc.collect_struct::<Self>())?;209		address::solidity_default(writer, tc)?;210		write!(writer, ",")?;211		uint256::solidity_default(writer, tc)?;212		write!(writer, ")")213	}214}215216impl StructCollect for EthCrossAccount {217	fn name() -> String {218		"EthCrossAccount".into()219	}220221	fn declaration() -> String {222		let mut str = String::new();223		writeln!(str, "/// @dev Cross account struct").unwrap();224		writeln!(str, "struct {} {{", Self::name()).unwrap();225		writeln!(str, "\taddress eth;").unwrap();226		writeln!(str, "\tuint256 sub;").unwrap();227		writeln!(str, "}}").unwrap();228		str229	}230}231232impl StructCollect for Property {233	fn name() -> String {234		"Property".into()235	}236237	fn declaration() -> String {238		let mut str = String::new();239		writeln!(str, "/// @dev Property struct").unwrap();240		writeln!(str, "struct {} {{", Self::name()).unwrap();241		writeln!(str, "\tstring key;").unwrap();242		writeln!(str, "\tbytes value;").unwrap();243		writeln!(str, "}}").unwrap();244		str245	}246}247248impl SolidityTypeName for Property {249	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {250		write!(writer, "{}", tc.collect_struct::<Self>())251	}252253	fn is_simple() -> bool {254		false255	}256257	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {258		write!(writer, "{}(", tc.collect_struct::<Self>())?;259		address::solidity_default(writer, tc)?;260		write!(writer, ",")?;261		uint256::solidity_default(writer, tc)?;262		write!(writer, ")")263	}264}265266impl SolidityTupleType for Property {267	fn names(tc: &TypeCollector) -> Vec<string> {268		let mut collected = Vec::with_capacity(Self::len());269		{270			let mut out = string::new();271			string::solidity_name(&mut out, tc).expect("no fmt error");272			collected.push(out);273		}274		{275			let mut out = string::new();276			bytes::solidity_name(&mut out, tc).expect("no fmt error");277			collected.push(out);278		}279		collected280	}281282	fn len() -> usize {283		2284	}285}286287pub trait SolidityTupleType {288	fn names(tc: &TypeCollector) -> Vec<String>;289	fn len() -> usize;290}291292macro_rules! count {293    () => (0usize);294    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));295}296297macro_rules! impl_tuples {298	($($ident:ident)+) => {299		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}300		impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) {301			fn names(tc: &TypeCollector) -> Vec<string> {302				let mut collected = Vec::with_capacity(Self::len());303				$({304					let mut out = string::new();305					$ident::solidity_name(&mut out, tc).expect("no fmt error");306					collected.push(out);307				})*;308				collected309			}310311			fn len() -> usize {312				count!($($ident)*)313			}314		}315		impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) {316			fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {317				write!(writer, "{}", tc.collect_tuple::<Self>())318			}319			fn is_simple() -> bool {320				false321			}322			#[allow(unused_assignments)]323			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {324				write!(writer, "{}(", tc.collect_tuple::<Self>())?;325				let mut first = true;326				$(327					if !first {328						write!(writer, ",")?;329					} else {330						first = false;331					}332					<$ident>::solidity_default(writer, tc)?;333				)*334				write!(writer, ")")335			}336		}337	};338}339340impl_tuples! {A}341impl_tuples! {A B}342impl_tuples! {A B C}343impl_tuples! {A B C D}344impl_tuples! {A B C D E}345impl_tuples! {A B C D E F}346impl_tuples! {A B C D E F G}347impl_tuples! {A B C D E F G H}348impl_tuples! {A B C D E F G H I}349impl_tuples! {A B C D E F G H I J}350351pub trait SolidityArguments {352	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;353	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result;354	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;355	fn is_empty(&self) -> bool {356		self.len() == 0357	}358	fn len(&self) -> usize;359}360361#[derive(Default)]362pub struct UnnamedArgument<T>(PhantomData<*const T>);363364impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {365	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {366		if !T::is_void() {367			T::solidity_name(writer, tc)?;368			if !T::is_simple() {369				write!(writer, " memory")?;370			}371			Ok(())372		} else {373			Ok(())374		}375	}376	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {377		Ok(())378	}379	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {380		T::solidity_default(writer, tc)381	}382	fn len(&self) -> usize {383		if T::is_void() {384			0385		} else {386			1387		}388	}389}390391pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);392393impl<T> NamedArgument<T> {394	pub fn new(name: &'static str) -> Self {395		Self(name, Default::default())396	}397}398399impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {400	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {401		if !T::is_void() {402			T::solidity_name(writer, tc)?;403			if !T::is_simple() {404				write!(writer, " memory")?;405			}406			write!(writer, " {}", self.0)407		} else {408			Ok(())409		}410	}411	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {412		writeln!(writer, "\t{prefix}\t{};", self.0)413	}414	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {415		T::solidity_default(writer, tc)416	}417	fn len(&self) -> usize {418		if T::is_void() {419			0420		} else {421			1422		}423	}424}425426pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);427428impl<T> SolidityEventArgument<T> {429	pub fn new(indexed: bool, name: &'static str) -> Self {430		Self(indexed, name, Default::default())431	}432}433434impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {435	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {436		if !T::is_void() {437			T::solidity_name(writer, tc)?;438			if self.0 {439				write!(writer, " indexed")?;440			}441			write!(writer, " {}", self.1)442		} else {443			Ok(())444		}445	}446	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {447		writeln!(writer, "\t{prefix}\t{};", self.1)448	}449	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {450		T::solidity_default(writer, tc)451	}452	fn len(&self) -> usize {453		if T::is_void() {454			0455		} else {456			1457		}458	}459}460461impl SolidityArguments for () {462	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {463		Ok(())464	}465	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {466		Ok(())467	}468	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {469		Ok(())470	}471	fn len(&self) -> usize {472		0473	}474}475476#[impl_for_tuples(1, 12)]477impl SolidityArguments for Tuple {478	for_tuples!( where #( Tuple: SolidityArguments ),* );479480	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {481		let mut first = true;482		for_tuples!( #(483            if !Tuple.is_empty() {484                if !first {485                    write!(writer, ", ")?;486                }487                first = false;488                Tuple.solidity_name(writer, tc)?;489            }490        )* );491		Ok(())492	}493	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {494		for_tuples!( #(495            Tuple.solidity_get(prefix, writer)?;496        )* );497		Ok(())498	}499	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {500		if self.is_empty() {501			Ok(())502		} else if self.len() == 1 {503			for_tuples!( #(504				Tuple.solidity_default(writer, tc)?;505			)* );506			Ok(())507		} else {508			write!(writer, "(")?;509			let mut first = true;510			for_tuples!( #(511				if !Tuple.is_empty() {512					if !first {513						write!(writer, ", ")?;514					}515					first = false;516					Tuple.solidity_default(writer, tc)?;517				}518			)* );519			write!(writer, ")")?;520			Ok(())521		}522	}523	fn len(&self) -> usize {524		for_tuples!( #( Tuple.len() )+* )525	}526}527528pub trait SolidityFunctions {529	fn solidity_name(530		&self,531		is_impl: bool,532		writer: &mut impl fmt::Write,533		tc: &TypeCollector,534	) -> fmt::Result;535}536537pub enum SolidityMutability {538	Pure,539	View,540	Mutable,541}542pub struct SolidityFunction<A, R> {543	pub docs: &'static [&'static str],544	pub selector: u32,545	pub hide: bool,546	pub custom_signature: SignatureUnit,547	pub name: &'static str,548	pub args: A,549	pub result: R,550	pub mutability: SolidityMutability,551	pub is_payable: bool,552}553impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {554	fn solidity_name(555		&self,556		is_impl: bool,557		writer: &mut impl fmt::Write,558		tc: &TypeCollector,559	) -> fmt::Result {560		let hide_comment = self.hide.then(|| "// ").unwrap_or("");561		for doc in self.docs {562			writeln!(writer, "\t{hide_comment}///{}", doc)?;563		}564		writeln!(565			writer,566			"\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},",567			self.selector568		)?;569		writeln!(570			writer,571			"\t{hide_comment}///  or in textual repr: {}",572			self.custom_signature.as_str().expect("bad utf-8")573		)?;574		write!(writer, "\t{hide_comment}function {}(", self.name)?;575		self.args.solidity_name(writer, tc)?;576		write!(writer, ")")?;577		if is_impl {578			write!(writer, " public")?;579		} else {580			write!(writer, " external")?;581		}582		match &self.mutability {583			SolidityMutability::Pure => write!(writer, " pure")?,584			SolidityMutability::View => write!(writer, " view")?,585			SolidityMutability::Mutable => {}586		}587		if self.is_payable {588			write!(writer, " payable")?;589		}590		if !self.result.is_empty() {591			write!(writer, " returns (")?;592			self.result.solidity_name(writer, tc)?;593			write!(writer, ")")?;594		}595		if is_impl {596			writeln!(writer, " {{")?;597			writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?;598			self.args.solidity_get(hide_comment, writer)?;599			match &self.mutability {600				SolidityMutability::Pure => {}601				SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?,602				SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?,603			}604			if !self.result.is_empty() {605				write!(writer, "\t{hide_comment}\treturn ")?;606				self.result.solidity_default(writer, tc)?;607				writeln!(writer, ";")?;608			}609			writeln!(writer, "\t{hide_comment}}}")?;610		} else {611			writeln!(writer, ";")?;612		}613		if self.hide {614			writeln!(writer, "// FORMATTING: FORCE NEWLINE")?;615		}616		Ok(())617	}618}619620#[impl_for_tuples(0, 48)]621impl SolidityFunctions for Tuple {622	for_tuples!( where #( Tuple: SolidityFunctions ),* );623624	fn solidity_name(625		&self,626		is_impl: bool,627		writer: &mut impl fmt::Write,628		tc: &TypeCollector,629	) -> fmt::Result {630		let mut first = false;631		for_tuples!( #(632            Tuple.solidity_name(is_impl, writer, tc)?;633        )* );634		Ok(())635	}636}637638pub struct SolidityInterface<F: SolidityFunctions> {639	pub docs: &'static [&'static str],640	pub selector: bytes4,641	pub name: &'static str,642	pub is: &'static [&'static str],643	pub functions: F,644}645646impl<F: SolidityFunctions> SolidityInterface<F> {647	pub fn format(648		&self,649		is_impl: bool,650		out: &mut impl fmt::Write,651		tc: &TypeCollector,652	) -> fmt::Result {653		const ZERO_BYTES: [u8; 4] = [0; 4];654		for doc in self.docs {655			writeln!(out, "///{}", doc)?;656		}657		if self.selector != ZERO_BYTES {658			writeln!(659				out,660				"/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}",661				u32::from_be_bytes(self.selector)662			)?;663		}664		if is_impl {665			write!(out, "contract ")?;666		} else {667			write!(out, "interface ")?;668		}669		write!(out, "{}", self.name)?;670		if !self.is.is_empty() {671			write!(out, " is")?;672			for (i, n) in self.is.iter().enumerate() {673				if i != 0 {674					write!(out, ",")?;675				}676				write!(out, " {}", n)?;677			}678		}679		writeln!(out, " {{")?;680		self.functions.solidity_name(is_impl, out, tc)?;681		writeln!(out, "}}")?;682		Ok(())683	}684}685686pub struct SolidityEvent<A> {687	pub name: &'static str,688	pub args: A,689}690691impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {692	fn solidity_name(693		&self,694		_is_impl: bool,695		writer: &mut impl fmt::Write,696		tc: &TypeCollector,697	) -> fmt::Result {698		write!(writer, "\tevent {}(", self.name)?;699		self.args.solidity_name(writer, tc)?;700		writeln!(writer, ");")701	}702}