git.delta.rocks / unique-network / refs/commits / 15e4512df81d

difftreelog

source

crates/evm-coder/src/solidity.rs17.1 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	str::from_utf8,34};35use impl_trait_for_tuples::impl_for_tuples;36use crate::{types::*, custom_signature::FunctionSignature};3738#[derive(Default)]39pub struct TypeCollector {40	/// Code => id41	/// id ordering is required to perform topo-sort on the resulting data42	structs: RefCell<BTreeMap<string, usize>>,43	anonymous: RefCell<BTreeMap<Vec<string>, usize>>,44	id: Cell<usize>,45}46impl TypeCollector {47	pub fn new() -> Self {48		Self::default()49	}50	pub fn collect(&self, item: string) {51		let id = self.next_id();52		self.structs.borrow_mut().insert(item, id);53	}54	pub fn next_id(&self) -> usize {55		let v = self.id.get();56		self.id.set(v + 1);57		v58	}59	pub fn collect_tuple<T: SolidityTupleType>(&self) -> String {60		let names = T::names(self);61		if let Some(id) = self.anonymous.borrow().get(&names).cloned() {62			return format!("Tuple{}", id);63		}64		let id = self.next_id();65		let mut str = String::new();66		writeln!(str, "/// @dev anonymous struct").unwrap();67		writeln!(str, "struct Tuple{} {{", id).unwrap();68		for (i, name) in names.iter().enumerate() {69			writeln!(str, "\t{} field_{};", name, i).unwrap();70		}71		writeln!(str, "}}").unwrap();72		self.collect(str);73		self.anonymous.borrow_mut().insert(names, id);74		format!("Tuple{}", id)75	}76	pub fn collect_struct<T: StructCollect>(&self) -> String {77		self.collect(<T as StructCollect>::declaration());78		<T as StructCollect>::name()79	}80	pub fn finish(self) -> Vec<string> {81		let mut data = self.structs.into_inner().into_iter().collect::<Vec<_>>();82		data.sort_by_key(|(_, id)| Reverse(*id));83		data.into_iter().map(|(code, _)| code).collect()84	}85}8687pub trait StructCollect: 'static {88	/// Structure name.89	fn name() -> String;90	/// Structure declaration.91	fn declaration() -> String;92}9394pub trait SolidityTypeName: 'static {95	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;96	/// "simple" types are stored inline, no `memory` modifier should be used in solidity97	fn is_simple() -> bool;98	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;99	/// Specialization100	fn is_void() -> bool {101		false102	}103}104macro_rules! solidity_type_name {105    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {106        $(107            impl SolidityTypeName for $ty {108                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {109                    write!(writer, $name)110                }111				fn is_simple() -> bool {112					$simple113				}114				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {115					write!(writer, $default)116				}117            }118        )*119    };120}121122solidity_type_name! {123	uint8 => "uint8" true = "0",124	uint32 => "uint32" true = "0",125	uint64 => "uint64" true = "0",126	uint128 => "uint128" true = "0",127	uint256 => "uint256" true = "0",128	bytes4 => "bytes4" true = "bytes4(0)",129	address => "address" true = "0x0000000000000000000000000000000000000000",130	string => "string" false = "\"\"",131	bytes => "bytes" false = "hex\"\"",132	bool => "bool" true = "false",133}134impl SolidityTypeName for void {135	fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {136		Ok(())137	}138	fn is_simple() -> bool {139		true140	}141	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {142		Ok(())143	}144	fn is_void() -> bool {145		true146	}147}148149mod sealed {150	/// Not every type should be directly placed in vec.151	/// Vec encoding is not memory efficient, as every item will be padded152	/// to 32 bytes.153	/// Instead you should use specialized types (`bytes` in case of `Vec<u8>`)154	pub trait CanBePlacedInVec {}155}156157impl sealed::CanBePlacedInVec for uint256 {}158impl sealed::CanBePlacedInVec for string {}159impl sealed::CanBePlacedInVec for address {}160impl sealed::CanBePlacedInVec for EthCrossAccount {}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}197impl SolidityTypeName for EthCrossAccount {198	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {199		write!(writer, "{}", tc.collect_struct::<Self>())200	}201202	fn is_simple() -> bool {203		false204	}205206	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {207		write!(writer, "{}(", tc.collect_tuple::<Self>())?;208		address::solidity_default(writer, tc)?;209		write!(writer, ",")?;210		uint256::solidity_default(writer, tc)?;211		write!(writer, ")")212	}213}214215impl StructCollect for EthCrossAccount {216	fn name() -> String {217		"EthCrossAccount".into()218	}219220	fn declaration() -> String {221		let mut str = String::new();222		writeln!(str, "/// @dev Cross account struct").unwrap();223		writeln!(str, "struct {} {{", Self::name()).unwrap();224		writeln!(str, "\taddress eth;").unwrap();225		writeln!(str, "\tuint256 sub;").unwrap();226		writeln!(str, "}}").unwrap();227		str228	}229}230231pub trait SolidityTupleType {232	fn names(tc: &TypeCollector) -> Vec<String>;233	fn len() -> usize;234}235236macro_rules! count {237    () => (0usize);238    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));239}240241macro_rules! impl_tuples {242	($($ident:ident)+) => {243		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}244		impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) {245			fn names(tc: &TypeCollector) -> Vec<string> {246				let mut collected = Vec::with_capacity(Self::len());247				$({248					let mut out = string::new();249					$ident::solidity_name(&mut out, tc).expect("no fmt error");250					collected.push(out);251				})*;252				collected253			}254255			fn len() -> usize {256				count!($($ident)*)257			}258		}259		impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) {260			fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {261				write!(writer, "{}", tc.collect_tuple::<Self>())262			}263			fn is_simple() -> bool {264				false265			}266			#[allow(unused_assignments)]267			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {268				write!(writer, "{}(", tc.collect_tuple::<Self>())?;269				let mut first = true;270				$(271					if !first {272						write!(writer, ",")?;273					} else {274						first = false;275					}276					<$ident>::solidity_default(writer, tc)?;277				)*278				write!(writer, ")")279			}280		}281	};282}283284impl_tuples! {A}285impl_tuples! {A B}286impl_tuples! {A B C}287impl_tuples! {A B C D}288impl_tuples! {A B C D E}289impl_tuples! {A B C D E F}290impl_tuples! {A B C D E F G}291impl_tuples! {A B C D E F G H}292impl_tuples! {A B C D E F G H I}293impl_tuples! {A B C D E F G H I J}294295pub trait SolidityArguments {296	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;297	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result;298	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;299	fn is_empty(&self) -> bool {300		self.len() == 0301	}302	fn len(&self) -> usize;303}304305#[derive(Default)]306pub struct UnnamedArgument<T>(PhantomData<*const T>);307308impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {309	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {310		if !T::is_void() {311			T::solidity_name(writer, tc)?;312			if !T::is_simple() {313				write!(writer, " memory")?;314			}315			Ok(())316		} else {317			Ok(())318		}319	}320	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {321		Ok(())322	}323	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {324		T::solidity_default(writer, tc)325	}326	fn len(&self) -> usize {327		if T::is_void() {328			0329		} else {330			1331		}332	}333}334335pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);336337impl<T> NamedArgument<T> {338	pub fn new(name: &'static str) -> Self {339		Self(name, Default::default())340	}341}342343impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {344	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {345		if !T::is_void() {346			T::solidity_name(writer, tc)?;347			if !T::is_simple() {348				write!(writer, " memory")?;349			}350			write!(writer, " {}", self.0)351		} else {352			Ok(())353		}354	}355	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {356		writeln!(writer, "\t{prefix}\t{};", self.0)357	}358	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {359		T::solidity_default(writer, tc)360	}361	fn len(&self) -> usize {362		if T::is_void() {363			0364		} else {365			1366		}367	}368}369370pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);371372impl<T> SolidityEventArgument<T> {373	pub fn new(indexed: bool, name: &'static str) -> Self {374		Self(indexed, name, Default::default())375	}376}377378impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {379	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {380		if !T::is_void() {381			T::solidity_name(writer, tc)?;382			if self.0 {383				write!(writer, " indexed")?;384			}385			write!(writer, " {}", self.1)386		} else {387			Ok(())388		}389	}390	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {391		writeln!(writer, "\t{prefix}\t{};", self.1)392	}393	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {394		T::solidity_default(writer, tc)395	}396	fn len(&self) -> usize {397		if T::is_void() {398			0399		} else {400			1401		}402	}403}404405impl SolidityArguments for () {406	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {407		Ok(())408	}409	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {410		Ok(())411	}412	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {413		Ok(())414	}415	fn len(&self) -> usize {416		0417	}418}419420#[impl_for_tuples(1, 12)]421impl SolidityArguments for Tuple {422	for_tuples!( where #( Tuple: SolidityArguments ),* );423424	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {425		let mut first = true;426		for_tuples!( #(427            if !Tuple.is_empty() {428                if !first {429                    write!(writer, ", ")?;430                }431                first = false;432                Tuple.solidity_name(writer, tc)?;433            }434        )* );435		Ok(())436	}437	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {438		for_tuples!( #(439            Tuple.solidity_get(prefix, writer)?;440        )* );441		Ok(())442	}443	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {444		if self.is_empty() {445			Ok(())446		} else if self.len() == 1 {447			for_tuples!( #(448				Tuple.solidity_default(writer, tc)?;449			)* );450			Ok(())451		} else {452			write!(writer, "(")?;453			let mut first = true;454			for_tuples!( #(455				if !Tuple.is_empty() {456					if !first {457						write!(writer, ", ")?;458					}459					first = false;460					Tuple.solidity_default(writer, tc)?;461				}462			)* );463			write!(writer, ")")?;464			Ok(())465		}466	}467	fn len(&self) -> usize {468		for_tuples!( #( Tuple.len() )+* )469	}470}471472pub trait SolidityFunctions {473	fn solidity_name(474		&self,475		is_impl: bool,476		writer: &mut impl fmt::Write,477		tc: &TypeCollector,478	) -> fmt::Result;479}480481pub enum SolidityMutability {482	Pure,483	View,484	Mutable,485}486pub struct SolidityFunction<A, R> {487	pub docs: &'static [&'static str],488	pub selector_str: &'static str,489	pub selector: u32,490	pub hide: bool,491	pub custom_signature: FunctionSignature,492	pub name: &'static str,493	pub args: A,494	pub result: R,495	pub mutability: SolidityMutability,496	pub is_payable: bool,497}498impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {499	fn solidity_name(500		&self,501		is_impl: bool,502		writer: &mut impl fmt::Write,503		tc: &TypeCollector,504	) -> fmt::Result {505		let hide_comment = self.hide.then(|| "// ").unwrap_or("");506		for doc in self.docs {507			writeln!(writer, "\t{hide_comment}///{}", doc)?;508		}509		writeln!(510			writer,511			"\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},",512			self.selector513		)?;514		writeln!(515			writer,516			"\t{hide_comment}///  or in textual repr: {}",517			// from_utf8(self.custom_signature.as_str()).expect("bad utf8")518			self.selector_str519		)?;520		write!(writer, "\t{hide_comment}function {}(", self.name)?;521		self.args.solidity_name(writer, tc)?;522		write!(writer, ")")?;523		if is_impl {524			write!(writer, " public")?;525		} else {526			write!(writer, " external")?;527		}528		match &self.mutability {529			SolidityMutability::Pure => write!(writer, " pure")?,530			SolidityMutability::View => write!(writer, " view")?,531			SolidityMutability::Mutable => {}532		}533		if self.is_payable {534			write!(writer, " payable")?;535		}536		if !self.result.is_empty() {537			write!(writer, " returns (")?;538			self.result.solidity_name(writer, tc)?;539			write!(writer, ")")?;540		}541		if is_impl {542			writeln!(writer, " {{")?;543			writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?;544			self.args.solidity_get(hide_comment, writer)?;545			match &self.mutability {546				SolidityMutability::Pure => {}547				SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?,548				SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?,549			}550			if !self.result.is_empty() {551				write!(writer, "\t{hide_comment}\treturn ")?;552				self.result.solidity_default(writer, tc)?;553				writeln!(writer, ";")?;554			}555			writeln!(writer, "\t{hide_comment}}}")?;556		} else {557			writeln!(writer, ";")?;558		}559		if self.hide {560			writeln!(writer, "// FORMATTING: FORCE NEWLINE")?;561		}562		Ok(())563	}564}565566#[impl_for_tuples(0, 48)]567impl SolidityFunctions for Tuple {568	for_tuples!( where #( Tuple: SolidityFunctions ),* );569570	fn solidity_name(571		&self,572		is_impl: bool,573		writer: &mut impl fmt::Write,574		tc: &TypeCollector,575	) -> fmt::Result {576		let mut first = false;577		for_tuples!( #(578            Tuple.solidity_name(is_impl, writer, tc)?;579        )* );580		Ok(())581	}582}583584pub struct SolidityInterface<F: SolidityFunctions> {585	pub docs: &'static [&'static str],586	pub selector: bytes4,587	pub name: &'static str,588	pub is: &'static [&'static str],589	pub functions: F,590}591592impl<F: SolidityFunctions> SolidityInterface<F> {593	pub fn format(594		&self,595		is_impl: bool,596		out: &mut impl fmt::Write,597		tc: &TypeCollector,598	) -> fmt::Result {599		const ZERO_BYTES: [u8; 4] = [0; 4];600		for doc in self.docs {601			writeln!(out, "///{}", doc)?;602		}603		if self.selector != ZERO_BYTES {604			writeln!(605				out,606				"/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}",607				u32::from_be_bytes(self.selector)608			)?;609		}610		if is_impl {611			write!(out, "contract ")?;612		} else {613			write!(out, "interface ")?;614		}615		write!(out, "{}", self.name)?;616		if !self.is.is_empty() {617			write!(out, " is")?;618			for (i, n) in self.is.iter().enumerate() {619				if i != 0 {620					write!(out, ",")?;621				}622				write!(out, " {}", n)?;623			}624		}625		writeln!(out, " {{")?;626		self.functions.solidity_name(is_impl, out, tc)?;627		writeln!(out, "}}")?;628		Ok(())629	}630}631632pub struct SolidityEvent<A> {633	pub name: &'static str,634	pub args: A,635}636637impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {638	fn solidity_name(639		&self,640		_is_impl: bool,641		writer: &mut impl fmt::Write,642		tc: &TypeCollector,643	) -> fmt::Result {644		write!(writer, "\tevent {}(", self.name)?;645		self.args.solidity_name(writer, tc)?;646		writeln!(writer, ");")647	}648}