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

difftreelog

source

crates/evm-coder/src/solidity.rs17.0 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}147148pub mod 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 Property {}160161impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {162	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {163		T::solidity_name(writer, tc)?;164		write!(writer, "[]")165	}166	fn is_simple() -> bool {167		false168	}169	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {170		write!(writer, "new ")?;171		T::solidity_name(writer, tc)?;172		write!(writer, "[](0)")173	}174}175176impl StructCollect for Property {177	fn name() -> String {178		"Property".into()179	}180181	fn declaration() -> String {182		let mut str = String::new();183		writeln!(str, "/// @dev Property struct").unwrap();184		writeln!(str, "struct {} {{", Self::name()).unwrap();185		writeln!(str, "\tstring key;").unwrap();186		writeln!(str, "\tbytes value;").unwrap();187		writeln!(str, "}}").unwrap();188		str189	}190}191192impl SolidityTypeName for Property {193	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {194		write!(writer, "{}", tc.collect_struct::<Self>())195	}196197	fn is_simple() -> bool {198		false199	}200201	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {202		write!(writer, "{}(", tc.collect_struct::<Self>())?;203		address::solidity_default(writer, tc)?;204		write!(writer, ",")?;205		uint256::solidity_default(writer, tc)?;206		write!(writer, ")")207	}208}209210impl SolidityTupleType for Property {211	fn names(tc: &TypeCollector) -> Vec<string> {212		let mut collected = Vec::with_capacity(Self::len());213		{214			let mut out = string::new();215			string::solidity_name(&mut out, tc).expect("no fmt error");216			collected.push(out);217		}218		{219			let mut out = string::new();220			bytes::solidity_name(&mut out, tc).expect("no fmt error");221			collected.push(out);222		}223		collected224	}225226	fn len() -> usize {227		2228	}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: u32,489	pub hide: bool,490	pub custom_signature: SignatureUnit,491	pub name: &'static str,492	pub args: A,493	pub result: R,494	pub mutability: SolidityMutability,495	pub is_payable: bool,496}497impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {498	fn solidity_name(499		&self,500		is_impl: bool,501		writer: &mut impl fmt::Write,502		tc: &TypeCollector,503	) -> fmt::Result {504		let hide_comment = self.hide.then(|| "// ").unwrap_or("");505		for doc in self.docs {506			writeln!(writer, "\t{hide_comment}///{}", doc)?;507		}508		writeln!(509			writer,510			"\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},",511			self.selector512		)?;513		writeln!(514			writer,515			"\t{hide_comment}///  or in textual repr: {}",516			self.custom_signature.as_str().expect("bad utf-8")517		)?;518		write!(writer, "\t{hide_comment}function {}(", self.name)?;519		self.args.solidity_name(writer, tc)?;520		write!(writer, ")")?;521		if is_impl {522			write!(writer, " public")?;523		} else {524			write!(writer, " external")?;525		}526		match &self.mutability {527			SolidityMutability::Pure => write!(writer, " pure")?,528			SolidityMutability::View => write!(writer, " view")?,529			SolidityMutability::Mutable => {}530		}531		if self.is_payable {532			write!(writer, " payable")?;533		}534		if !self.result.is_empty() {535			write!(writer, " returns (")?;536			self.result.solidity_name(writer, tc)?;537			write!(writer, ")")?;538		}539		if is_impl {540			writeln!(writer, " {{")?;541			writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?;542			self.args.solidity_get(hide_comment, writer)?;543			match &self.mutability {544				SolidityMutability::Pure => {}545				SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?,546				SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?,547			}548			if !self.result.is_empty() {549				write!(writer, "\t{hide_comment}\treturn ")?;550				self.result.solidity_default(writer, tc)?;551				writeln!(writer, ";")?;552			}553			writeln!(writer, "\t{hide_comment}}}")?;554		} else {555			writeln!(writer, ";")?;556		}557		if self.hide {558			writeln!(writer, "// FORMATTING: FORCE NEWLINE")?;559		}560		Ok(())561	}562}563564#[impl_for_tuples(0, 48)]565impl SolidityFunctions for Tuple {566	for_tuples!( where #( Tuple: SolidityFunctions ),* );567568	fn solidity_name(569		&self,570		is_impl: bool,571		writer: &mut impl fmt::Write,572		tc: &TypeCollector,573	) -> fmt::Result {574		let mut first = false;575		for_tuples!( #(576            Tuple.solidity_name(is_impl, writer, tc)?;577        )* );578		Ok(())579	}580}581582pub struct SolidityInterface<F: SolidityFunctions> {583	pub docs: &'static [&'static str],584	pub selector: bytes4,585	pub name: &'static str,586	pub is: &'static [&'static str],587	pub functions: F,588}589590impl<F: SolidityFunctions> SolidityInterface<F> {591	pub fn format(592		&self,593		is_impl: bool,594		out: &mut impl fmt::Write,595		tc: &TypeCollector,596	) -> fmt::Result {597		const ZERO_BYTES: [u8; 4] = [0; 4];598		for doc in self.docs {599			writeln!(out, "///{}", doc)?;600		}601		if self.selector != ZERO_BYTES {602			writeln!(603				out,604				"/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}",605				u32::from_be_bytes(self.selector)606			)?;607		}608		if is_impl {609			write!(out, "contract ")?;610		} else {611			write!(out, "interface ")?;612		}613		write!(out, "{}", self.name)?;614		if !self.is.is_empty() {615			write!(out, " is")?;616			for (i, n) in self.is.iter().enumerate() {617				if i != 0 {618					write!(out, ",")?;619				}620				write!(out, " {}", n)?;621			}622		}623		writeln!(out, " {{")?;624		self.functions.solidity_name(is_impl, out, tc)?;625		writeln!(out, "}}")?;626		Ok(())627	}628}629630pub struct SolidityEvent<A> {631	pub name: &'static str,632	pub args: A,633}634635impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {636	fn solidity_name(637		&self,638		_is_impl: bool,639		writer: &mut impl fmt::Write,640		tc: &TypeCollector,641	) -> fmt::Result {642		write!(writer, "\tevent {}(", self.name)?;643		self.args.solidity_name(writer, tc)?;644		writeln!(writer, ");")645	}646}