git.delta.rocks / unique-network / refs/commits / 36b556a0c992

difftreelog

feat(evm-coder) implementation generator

Yaroslav Bolyukin2021-08-11parent: #aa106c3.patch.diff
in: master

8 files changed

modifiedcrates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ b/crates/evm-coder-macros/src/solidity_interface.rs
@@ -73,6 +73,20 @@
 			}
 		}
 	}
+
+	fn expand_generator(&self) -> proc_macro2::TokenStream {
+		let pascal_call_name = &self.pascal_call_name;
+		quote! {
+			#pascal_call_name::generate_solidity_interface(out_set, is_impl);
+		}
+	}
+
+	fn expand_event_generator(&self) -> proc_macro2::TokenStream {
+		let name = &self.name;
+		quote! {
+			#name::generate_solidity_interface(out_set, is_impl);
+		}
+	}
 }
 
 #[derive(Default)]
@@ -109,12 +123,15 @@
 
 struct MethodArg {
 	name: Ident,
+	camel_name: String,
 	ty: Ident,
 }
 impl MethodArg {
 	fn try_from(value: &PatType) -> syn::Result<Self> {
+		let name = parse_ident_from_pat(&value.pat)?.clone();
 		Ok(Self {
-			name: parse_ident_from_pat(&value.pat)?.clone(),
+			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
+			name,
 			ty: parse_ident_from_type(&value.ty, false)?.clone(),
 		})
 	}
@@ -168,10 +185,10 @@
 	}
 
 	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
-		let name = &self.name.to_string();
+		let camel_name = &self.camel_name.to_string();
 		let ty = &self.ty;
 		quote! {
-			<NamedArgument<#ty>>::new(#name)
+			<NamedArgument<#ty>>::new(#camel_name)
 		}
 	}
 }
@@ -397,7 +414,11 @@
 		};
 		let result = &self.result;
 
-		let args = self.args.iter().map(MethodArg::expand_solidity_argument);
+		let args = self
+			.args
+			.iter()
+			.filter(|a| !a.is_special())
+			.map(MethodArg::expand_solidity_argument);
 
 		quote! {
 			SolidityFunction {
@@ -475,6 +496,24 @@
 		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);
 		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);
 
+		// TODO: Inline inline_is
+		let solidity_is = self
+			.info
+			.is
+			.0
+			.iter()
+			.chain(self.info.inline_is.0.iter())
+			.map(|is| is.name.to_string());
+		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());
+		let solidity_generators = self
+			.info
+			.is
+			.0
+			.iter()
+			.chain(self.info.inline_is.0.iter())
+			.map(Is::expand_generator);
+		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);
+
 		// let methods = self.methods.iter().map(Method::solidity_def);
 
 		quote! {
@@ -505,18 +544,40 @@
 						)*
 					)
 				}
-				pub fn generate_solidity_interface() -> string {
+				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
 					use evm_coder::solidity::*;
 					use core::fmt::Write;
 					let interface = SolidityInterface {
 						name: #solidity_name,
+						is: &["Dummy", #(
+							#solidity_is,
+						)* #(
+							#solidity_events_is,
+						)* ],
 						functions: (#(
 							#solidity_functions,
 						)*),
 					};
+					if is_impl {
+						out_set.insert("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
+					} else {
+						out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());
+					}
+					#(
+						#solidity_generators
+					)*
+					#(
+						#solidity_event_generators
+					)*
+
 					let mut out = string::new();
-					let _ = interface.format(&mut out);
-					out
+					// In solidity interface usage (is) should be preceeded by interface definition
+					// This comment helps to sort it in a set
+					if #solidity_name.starts_with("Inline") {
+						out.push_str("// Inline\n");
+					}
+					let _ = interface.format(is_impl, &mut out);
+					out_set.insert(out);
 				}
 			}
 			impl ::evm_coder::Call for #call_name {
modifiedcrates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/to_log.rs
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -1,3 +1,4 @@
+use inflector::cases;
 use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};
 use std::fmt::Write;
 use quote::quote;
@@ -6,6 +7,7 @@
 
 struct EventField {
 	name: Ident,
+	camel_name: String,
 	ty: Ident,
 	indexed: bool,
 }
@@ -24,10 +26,18 @@
 		}
 		Ok(Self {
 			name: name.to_owned(),
+			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
 			ty: ty.to_owned(),
 			indexed,
 		})
 	}
+	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
+		let camel_name = &self.camel_name;
+		let ty = &self.ty;
+		quote! {
+			<NamedArgument<#ty>>::new(#camel_name)
+		}
+	}
 }
 
 struct Event {
@@ -116,6 +126,21 @@
 			)*];
 		}
 	}
+
+	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
+		let name = self.name.to_string();
+		let args = self.fields.iter().map(EventField::expand_solidity_argument);
+		quote! {
+			SolidityEvent {
+				name: #name,
+				args: (
+					#(
+						#args,
+					)*
+				),
+			}
+		}
+	}
 }
 
 pub struct Events {
@@ -144,12 +169,30 @@
 
 		let consts = self.events.iter().map(Event::expand_consts);
 		let serializers = self.events.iter().map(Event::expand_serializers);
+		let solidity_name = self.name.to_string();
+		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);
 
 		quote! {
 			impl #name {
 				#(
 					#consts
 				)*
+
+				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+					use evm_coder::solidity::*;
+					use core::fmt::Write;
+					let interface = SolidityInterface {
+						name: #solidity_name,
+						is: &[],
+						functions: (#(
+							#solidity_functions,
+						)*),
+					};
+					let mut out = string::new();
+					out.push_str("// Inline\n");
+					let _ = interface.format(is_impl, &mut out);
+					out_set.insert(out);
+				}
 			}
 
 			#[automatically_derived]
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -6,37 +6,44 @@
 
 pub trait SolidityTypeName: 'static {
 	fn solidity_name(writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_default(writer: &mut impl fmt::Write) -> fmt::Result;
 	fn is_void() -> bool {
 		false
 	}
 }
 
 macro_rules! solidity_type_name {
-    ($($ty:ident => $name:expr),* $(,)?) => {
+    ($($ty:ident => $name:literal = $default:literal),* $(,)?) => {
         $(
             impl SolidityTypeName for $ty {
                 fn solidity_name(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
                     write!(writer, $name)
                 }
+				fn solidity_default(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
+					write!(writer, $default)
+				}
             }
         )*
     };
 }
 
 solidity_type_name! {
-	uint8 => "uint8",
-	uint32 => "uint32",
-	uint128 => "uint128",
-	uint256 => "uint256",
-	address => "address",
-	string => "memory string",
-	bytes => "memory bytes",
-	bool => "bool",
+	uint8 => "uint8" = "0",
+	uint32 => "uint32" = "0",
+	uint128 => "uint128" = "0",
+	uint256 => "uint256" = "0",
+	address => "address" = "0x0000000000000000000000000000000000000000",
+	string => "string memory" = "\"\"",
+	bytes => "bytes memory" = "hex\"\"",
+	bool => "bool" = "false",
 }
 impl SolidityTypeName for void {
 	fn solidity_name(_writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
 	}
+	fn solidity_default(_writer: &mut impl fmt::Write) -> fmt::Result {
+		Ok(())
+	}
 	fn is_void() -> bool {
 		true
 	}
@@ -44,6 +51,8 @@
 
 pub trait SolidityArguments {
 	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result;
 	fn is_empty(&self) -> bool {
 		self.len() == 0
 	}
@@ -61,6 +70,12 @@
 			Ok(())
 		}
 	}
+	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+		Ok(())
+	}
+	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		T::solidity_default(writer)
+	}
 	fn len(&self) -> usize {
 		if T::is_void() {
 			0
@@ -87,6 +102,12 @@
 			Ok(())
 		}
 	}
+	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		writeln!(writer, "\t\t{};", self.0)
+	}
+	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		T::solidity_default(writer)
+	}
 	fn len(&self) -> usize {
 		if T::is_void() {
 			0
@@ -100,6 +121,12 @@
 	fn solidity_name(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
 	}
+	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+		Ok(())
+	}
+	fn solidity_default(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+		Ok(())
+	}
 	fn len(&self) -> usize {
 		0
 	}
@@ -122,13 +149,43 @@
         )* );
 		Ok(())
 	}
+	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		for_tuples!( #(
+            Tuple.solidity_get(writer)?;
+        )* );
+		Ok(())
+	}
+	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		if self.is_empty() {
+			Ok(())
+		} else if self.len() == 1 {
+			for_tuples!( #(
+				Tuple.solidity_default(writer)?;
+			)* );
+			Ok(())
+		} else {
+			write!(writer, "(")?;
+			let mut first = true;
+			for_tuples!( #(
+				if !Tuple.is_empty() {
+					if !first {
+						write!(writer, ", ")?;
+					}
+					first = false;
+					Tuple.solidity_name(writer)?;
+				}
+			)* );
+			write!(writer, ")")?;
+			Ok(())
+		}
+	}
 	fn len(&self) -> usize {
 		for_tuples!( #( Tuple.len() )+* )
 	}
 }
 
 pub trait SolidityFunctions {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result;
 }
 
 pub enum SolidityMutability {
@@ -143,10 +200,15 @@
 	pub mutability: SolidityMutability,
 }
 impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		write!(writer, "function {}(", self.name)?;
+	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+		write!(writer, "\tfunction {}(", self.name)?;
 		self.args.solidity_name(writer)?;
-		write!(writer, ") external")?;
+		write!(writer, ")")?;
+		if is_impl {
+			write!(writer, " public")?;
+		} else {
+			write!(writer, " external")?;
+		}
 		match &self.mutability {
 			SolidityMutability::Pure => write!(writer, " pure")?,
 			SolidityMutability::View => write!(writer, " view")?,
@@ -157,7 +219,25 @@
 			self.result.solidity_name(writer)?;
 			write!(writer, ")")?;
 		}
-		writeln!(writer, ";")
+		if is_impl {
+			writeln!(writer, " {{")?;
+			writeln!(writer, "\t\trequire(false, stub_error);")?;
+			self.args.solidity_get(writer)?;
+			match &self.mutability {
+				SolidityMutability::Pure => {}
+				SolidityMutability::View => writeln!(writer, "\t\tdummy;")?,
+				SolidityMutability::Mutable => writeln!(writer, "\t\tdummy = 0;")?,
+			}
+			if !self.result.is_empty() {
+				write!(writer, "\t\treturn ")?;
+				self.result.solidity_default(writer)?;
+				writeln!(writer, ";")?;
+			}
+			writeln!(writer, "\t}}")?;
+		} else {
+			writeln!(writer, ";")?;
+		}
+		Ok(())
 	}
 }
 
@@ -165,10 +245,10 @@
 impl SolidityFunctions for Tuple {
 	for_tuples!( where #( Tuple: SolidityFunctions ),* );
 
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
 		let mut first = false;
 		for_tuples!( #(
-            Tuple.solidity_name(writer)?;
+            Tuple.solidity_name(is_impl, writer)?;
         )* );
 		Ok(())
 	}
@@ -176,14 +256,43 @@
 
 pub struct SolidityInterface<F: SolidityFunctions> {
 	pub name: &'static str,
+	pub is: &'static [&'static str],
 	pub functions: F,
 }
 
 impl<F: SolidityFunctions> SolidityInterface<F> {
-	pub fn format(&self, out: &mut impl fmt::Write) -> fmt::Result {
-		writeln!(out, "interface {} {{", self.name)?;
-		self.functions.solidity_name(out)?;
+	pub fn format(&self, is_impl: bool, out: &mut impl fmt::Write) -> fmt::Result {
+		if is_impl {
+			write!(out, "contract ")?;
+		} else {
+			write!(out, "interface ")?;
+		}
+		write!(out, "{}", self.name)?;
+		if !self.is.is_empty() {
+			write!(out, " is")?;
+			for (i, n) in self.is.iter().enumerate() {
+				if i != 0 {
+					write!(out, ",")?;
+				}
+				write!(out, " {}", n)?;
+			}
+		}
+		writeln!(out, " {{")?;
+		self.functions.solidity_name(is_impl, out)?;
 		writeln!(out, "}}")?;
 		Ok(())
 	}
 }
+
+pub struct SolidityEvent<A> {
+	pub name: &'static str,
+	pub args: A,
+}
+
+impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
+	fn solidity_name(&self, _is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+		write!(writer, "\tevent {}(", self.name)?;
+		self.args.solidity_name(writer)?;
+		writeln!(writer, ");")
+	}
+}
modifiedpallets/nft/src/eth/erc.rsdiffbeforeafterboth
before · pallets/nft/src/eth/erc.rs
1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use alloc::format;6use crate::{7	Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,8	ItemListIndex,9};10use frame_support::storage::{StorageMap, StorageDoubleMap};11use pallet_evm::AddressMapping;12use super::account::CrossAccountId;13use sp_std::{vec, vec::Vec};1415#[solidity_interface(name = "ERC165")]16impl<T: Config> CollectionHandle<T> {17	fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {18		Ok(match self.mode {19			CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),20			CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),21			_ => false,22		})23	}24}2526#[solidity_interface(name = "InlineNameSymbol")]27impl<T: Config> CollectionHandle<T> {28	fn name(&self) -> Result<string> {29		Ok(decode_utf16(self.name.iter().copied())30			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))31			.collect::<string>())32	}3334	fn symbol(&self) -> Result<string> {35		Ok(string::from_utf8_lossy(&self.token_prefix).into())36	}37}3839#[solidity_interface(name = "InlineTotalSupply")]40impl<T: Config> CollectionHandle<T> {41	fn total_supply(&self) -> Result<uint256> {42		// TODO: we do not track total amount of all tokens43		Ok(0.into())44	}45}4647#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]48impl<T: Config> CollectionHandle<T> {49	fn token_uri(&self, token_id: uint256) -> Result<string> {50		// TODO: We should standartize url prefix, maybe via offchain schema?51		Ok(format!("unique.network/{}/{}", self.id, token_id))52	}53}5455#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]56impl<T: Config> CollectionHandle<T> {57	fn token_by_index(&self, index: uint256) -> Result<uint256> {58		Ok(index)59	}6061	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {62		// TODO: Not implemetable63		Err("not implemented".into())64	}65}6667#[derive(ToLog)]68pub enum ERC721Events {69	Transfer {70		#[indexed]71		from: address,72		#[indexed]73		to: address,74		#[indexed]75		token_id: uint256,76	},77	Approval {78		#[indexed]79		owner: address,80		#[indexed]81		approved: address,82		#[indexed]83		token_id: uint256,84	},85	#[allow(dead_code)]86	ApprovalForAll {87		#[indexed]88		owner: address,89		#[indexed]90		operator: address,91		approved: bool,92	},93}9495#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]96impl<T: Config> CollectionHandle<T> {97	#[solidity(rename_selector = "balanceOf")]98	fn balance_of_nft(&self, owner: address) -> Result<uint256> {99		let owner = T::EvmAddressMapping::into_account_id(owner);100		let balance = <Balance<T>>::get(self.id, owner);101		Ok(balance.into())102	}103	fn owner_of(&self, token_id: uint256) -> Result<address> {104		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;105		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;106		Ok(*token.owner.as_eth())107	}108	fn safe_transfer_from_with_data(109		&mut self,110		_from: address,111		_to: address,112		_token_id: uint256,113		_data: bytes,114		_value: value,115	) -> Result<void> {116		// TODO: Not implemetable117		Err("not implemented".into())118	}119	fn safe_transfer_from(120		&mut self,121		_from: address,122		_to: address,123		_token_id: uint256,124		_value: value,125	) -> Result<void> {126		// TODO: Not implemetable127		Err("not implemented".into())128	}129130	fn transfer_from(131		&mut self,132		caller: caller,133		from: address,134		to: address,135		token_id: uint256,136		_value: value,137	) -> Result<void> {138		let caller = T::CrossAccountId::from_eth(caller);139		let from = T::CrossAccountId::from_eth(from);140		let to = T::CrossAccountId::from_eth(to);141		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;142143		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)144			.map_err(|_| "transferFrom error")?;145		Ok(())146	}147148	fn approve(149		&mut self,150		caller: caller,151		approved: address,152		token_id: uint256,153		_value: value,154	) -> Result<void> {155		let caller = T::CrossAccountId::from_eth(caller);156		let approved = T::CrossAccountId::from_eth(approved);157		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;158159		<Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)160			.map_err(|_| "approve internal")?;161		Ok(())162	}163164	fn set_approval_for_all(165		&mut self,166		_caller: caller,167		_operator: address,168		_approved: bool,169	) -> Result<void> {170		// TODO: Not implemetable171		Err("not implemented".into())172	}173174	fn get_approved(&self, _token_id: uint256) -> Result<address> {175		// TODO: Not implemetable176		Err("not implemented".into())177	}178179	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {180		// TODO: Not implemetable181		Err("not implemented".into())182	}183}184185#[solidity_interface(name = "ERC721Burnable")]186impl<T: Config> CollectionHandle<T> {187	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {188		let caller = T::CrossAccountId::from_eth(caller);189		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;190191		<Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;192		Ok(())193	}194}195196#[derive(ToLog)]197pub enum ERC721MintableEvents {198	#[allow(dead_code)]199	MintingFinished {},200}201202#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]203impl<T: Config> CollectionHandle<T> {204	fn minting_finished(&self) -> Result<bool> {205		Ok(false)206	}207208	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {209		let caller = T::CrossAccountId::from_eth(caller);210		let to = T::CrossAccountId::from_eth(to);211		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;212		if <ItemListIndex>::get(self.id)213			.checked_add(1)214			.ok_or("item id overflow")?215			!= token_id216		{217			return Err("item id should be next".into());218		}219220		<Module<T>>::create_item_internal(221			&caller,222			&self,223			&to,224			CreateItemData::NFT(CreateNftData {225				const_data: vec![],226				variable_data: vec![],227			}),228		)229		.map_err(|_| "mint error")?;230		Ok(true)231	}232233	#[solidity(rename_selector = "mintWithTokenURI")]234	fn mint_with_token_uri(235		&mut self,236		caller: caller,237		to: address,238		token_id: uint256,239		token_uri: string,240	) -> Result<bool> {241		let caller = T::CrossAccountId::from_eth(caller);242		let to = T::CrossAccountId::from_eth(to);243		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;244		if <ItemListIndex>::get(self.id)245			.checked_add(1)246			.ok_or("item id overflow")?247			!= token_id248		{249			return Err("item id should be next".into());250		}251252		<Module<T>>::create_item_internal(253			&caller,254			&self,255			&to,256			CreateItemData::NFT(CreateNftData {257				const_data: token_uri.into(),258				variable_data: vec![],259			}),260		)261		.map_err(|_| "mint error")?;262		Ok(true)263	}264265	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {266		Err("not implementable".into())267	}268}269270#[solidity_interface(name = "ERC721UniqueExtensions")]271impl<T: Config> CollectionHandle<T> {272	#[solidity(rename_selector = "transfer")]273	fn transfer_nft(274		&mut self,275		caller: caller,276		to: address,277		token_id: uint256,278		_value: value,279	) -> Result<void> {280		let caller = T::CrossAccountId::from_eth(caller);281		let to = T::CrossAccountId::from_eth(to);282		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;283284		<Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)285			.map_err(|_| "transfer error")?;286		Ok(())287	}288289	fn next_token_id(&self) -> Result<uint256> {290		Ok(ItemListIndex::get(self.id)291			.checked_add(1)292			.ok_or("item id overflow")?293			.into())294	}295}296297#[solidity_interface(298	name = "UniqueNFT",299	is(300		ERC165,301		ERC721,302		ERC721Metadata,303		ERC721Enumerable,304		ERC721UniqueExtensions,305		ERC721Mintable,306		ERC721Burnable,307	)308)]309impl<T: Config> CollectionHandle<T> {}310311#[derive(ToLog)]312pub enum ERC20Events {313	Transfer {314		#[indexed]315		from: address,316		#[indexed]317		to: address,318		value: uint256,319	},320	Approval {321		#[indexed]322		owner: address,323		#[indexed]324		spender: address,325		value: uint256,326	},327}328329#[solidity_interface(330	name = "ERC20",331	inline_is(InlineNameSymbol, InlineTotalSupply),332	events(ERC20Events)333)]334impl<T: Config> CollectionHandle<T> {335	fn decimals(&self) -> Result<uint8> {336		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {337			*decimals338		} else {339			unreachable!()340		})341	}342	fn balance_of(&self, owner: address) -> Result<uint256> {343		let owner = T::EvmAddressMapping::into_account_id(owner);344		let balance = <Balance<T>>::get(self.id, owner);345		Ok(balance.into())346	}347	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {348		let caller = T::CrossAccountId::from_eth(caller);349		let to = T::CrossAccountId::from_eth(to);350		let amount = amount.try_into().map_err(|_| "amount overflow")?;351352		<Module<T>>::transfer_internal(&caller, &to, self, 1, amount)353			.map_err(|_| "transfer error")?;354		Ok(true)355	}356	#[solidity(rename_selector = "transferFrom")]357	fn transfer_from_fungible(358		&mut self,359		caller: caller,360		from: address,361		to: address,362		amount: uint256,363	) -> Result<bool> {364		let caller = T::CrossAccountId::from_eth(caller);365		let from = T::CrossAccountId::from_eth(from);366		let to = T::CrossAccountId::from_eth(to);367		let amount = amount.try_into().map_err(|_| "amount overflow")?;368369		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)370			.map_err(|_| "transferFrom error")?;371		Ok(true)372	}373	#[solidity(rename_selector = "approve")]374	fn approve_fungible(375		&mut self,376		caller: caller,377		spender: address,378		amount: uint256,379	) -> Result<bool> {380		let caller = T::CrossAccountId::from_eth(caller);381		let spender = T::CrossAccountId::from_eth(spender);382		let amount = amount.try_into().map_err(|_| "amount overflow")?;383384		<Module<T>>::approve_internal(&caller, &spender, self, 1, amount)385			.map_err(|_| "approve internal")?;386		Ok(true)387	}388	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {389		let owner = T::CrossAccountId::from_eth(owner);390		let spender = T::CrossAccountId::from_eth(spender);391392		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())393	}394}395396#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]397impl<T: Config> CollectionHandle<T> {}
after · pallets/nft/src/eth/erc.rs
1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use alloc::format;6use crate::{7	Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,8	ItemListIndex,9};10use frame_support::storage::{StorageMap, StorageDoubleMap};11use pallet_evm::AddressMapping;12use super::account::CrossAccountId;13use sp_std::{vec, vec::Vec};1415#[solidity_interface(name = "ERC165")]16impl<T: Config> CollectionHandle<T> {17	fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {18		Ok(match self.mode {19			CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),20			CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),21			_ => false,22		})23	}24}2526#[solidity_interface(name = "InlineNameSymbol")]27impl<T: Config> CollectionHandle<T> {28	fn name(&self) -> Result<string> {29		Ok(decode_utf16(self.name.iter().copied())30			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))31			.collect::<string>())32	}3334	fn symbol(&self) -> Result<string> {35		Ok(string::from_utf8_lossy(&self.token_prefix).into())36	}37}3839#[solidity_interface(name = "InlineTotalSupply")]40impl<T: Config> CollectionHandle<T> {41	fn total_supply(&self) -> Result<uint256> {42		// TODO: we do not track total amount of all tokens43		Ok(0.into())44	}45}4647#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]48impl<T: Config> CollectionHandle<T> {49	fn token_uri(&self, token_id: uint256) -> Result<string> {50		// TODO: We should standartize url prefix, maybe via offchain schema?51		Ok(format!("unique.network/{}/{}", self.id, token_id))52	}53}5455#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]56impl<T: Config> CollectionHandle<T> {57	fn token_by_index(&self, index: uint256) -> Result<uint256> {58		Ok(index)59	}6061	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {62		// TODO: Not implemetable63		Err("not implemented".into())64	}65}6667#[derive(ToLog)]68pub enum ERC721Events {69	Transfer {70		#[indexed]71		from: address,72		#[indexed]73		to: address,74		#[indexed]75		token_id: uint256,76	},77	Approval {78		#[indexed]79		owner: address,80		#[indexed]81		approved: address,82		#[indexed]83		token_id: uint256,84	},85	#[allow(dead_code)]86	ApprovalForAll {87		#[indexed]88		owner: address,89		#[indexed]90		operator: address,91		approved: bool,92	},93}9495#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]96impl<T: Config> CollectionHandle<T> {97	#[solidity(rename_selector = "balanceOf")]98	fn balance_of_nft(&self, owner: address) -> Result<uint256> {99		let owner = T::EvmAddressMapping::into_account_id(owner);100		let balance = <Balance<T>>::get(self.id, owner);101		Ok(balance.into())102	}103	fn owner_of(&self, token_id: uint256) -> Result<address> {104		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;105		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;106		Ok(*token.owner.as_eth())107	}108	fn safe_transfer_from_with_data(109		&mut self,110		_from: address,111		_to: address,112		_token_id: uint256,113		_data: bytes,114		_value: value,115	) -> Result<void> {116		// TODO: Not implemetable117		Err("not implemented".into())118	}119	fn safe_transfer_from(120		&mut self,121		_from: address,122		_to: address,123		_token_id: uint256,124		_value: value,125	) -> Result<void> {126		// TODO: Not implemetable127		Err("not implemented".into())128	}129130	fn transfer_from(131		&mut self,132		caller: caller,133		from: address,134		to: address,135		token_id: uint256,136		_value: value,137	) -> Result<void> {138		let caller = T::CrossAccountId::from_eth(caller);139		let from = T::CrossAccountId::from_eth(from);140		let to = T::CrossAccountId::from_eth(to);141		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;142143		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)144			.map_err(|_| "transferFrom error")?;145		Ok(())146	}147148	fn approve(149		&mut self,150		caller: caller,151		approved: address,152		token_id: uint256,153		_value: value,154	) -> Result<void> {155		let caller = T::CrossAccountId::from_eth(caller);156		let approved = T::CrossAccountId::from_eth(approved);157		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;158159		<Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)160			.map_err(|_| "approve internal")?;161		Ok(())162	}163164	fn set_approval_for_all(165		&mut self,166		_caller: caller,167		_operator: address,168		_approved: bool,169	) -> Result<void> {170		// TODO: Not implemetable171		Err("not implemented".into())172	}173174	fn get_approved(&self, _token_id: uint256) -> Result<address> {175		// TODO: Not implemetable176		Err("not implemented".into())177	}178179	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {180		// TODO: Not implemetable181		Err("not implemented".into())182	}183}184185#[solidity_interface(name = "ERC721Burnable")]186impl<T: Config> CollectionHandle<T> {187	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {188		let caller = T::CrossAccountId::from_eth(caller);189		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;190191		<Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;192		Ok(())193	}194}195196#[derive(ToLog)]197pub enum ERC721MintableEvents {198	#[allow(dead_code)]199	MintingFinished {},200}201202#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]203impl<T: Config> CollectionHandle<T> {204	fn minting_finished(&self) -> Result<bool> {205		Ok(false)206	}207208	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {209		let caller = T::CrossAccountId::from_eth(caller);210		let to = T::CrossAccountId::from_eth(to);211		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;212		if <ItemListIndex>::get(self.id)213			.checked_add(1)214			.ok_or("item id overflow")?215			!= token_id216		{217			return Err("item id should be next".into());218		}219220		<Module<T>>::create_item_internal(221			&caller,222			&self,223			&to,224			CreateItemData::NFT(CreateNftData {225				const_data: vec![],226				variable_data: vec![],227			}),228		)229		.map_err(|_| "mint error")?;230		Ok(true)231	}232233	#[solidity(rename_selector = "mintWithTokenURI")]234	fn mint_with_token_uri(235		&mut self,236		caller: caller,237		to: address,238		token_id: uint256,239		token_uri: string,240	) -> Result<bool> {241		let caller = T::CrossAccountId::from_eth(caller);242		let to = T::CrossAccountId::from_eth(to);243		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;244		if <ItemListIndex>::get(self.id)245			.checked_add(1)246			.ok_or("item id overflow")?247			!= token_id248		{249			return Err("item id should be next".into());250		}251252		<Module<T>>::create_item_internal(253			&caller,254			&self,255			&to,256			CreateItemData::NFT(CreateNftData {257				const_data: token_uri.into(),258				variable_data: vec![],259			}),260		)261		.map_err(|_| "mint error")?;262		Ok(true)263	}264265	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {266		Err("not implementable".into())267	}268}269270#[solidity_interface(name = "ERC721UniqueExtensions")]271impl<T: Config> CollectionHandle<T> {272	#[solidity(rename_selector = "transfer")]273	fn transfer_nft(274		&mut self,275		caller: caller,276		to: address,277		token_id: uint256,278		_value: value,279	) -> Result<void> {280		let caller = T::CrossAccountId::from_eth(caller);281		let to = T::CrossAccountId::from_eth(to);282		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;283284		<Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)285			.map_err(|_| "transfer error")?;286		Ok(())287	}288289	fn next_token_id(&self) -> Result<uint256> {290		Ok(ItemListIndex::get(self.id)291			.checked_add(1)292			.ok_or("item id overflow")?293			.into())294	}295}296297#[solidity_interface(298	name = "UniqueNFT",299	is(300		ERC165,301		ERC721,302		ERC721Metadata,303		ERC721Enumerable,304		ERC721UniqueExtensions,305		ERC721Mintable,306		ERC721Burnable,307	)308)]309impl<T: Config> CollectionHandle<T> {}310311#[derive(ToLog)]312pub enum ERC20Events {313	Transfer {314		#[indexed]315		from: address,316		#[indexed]317		to: address,318		value: uint256,319	},320	Approval {321		#[indexed]322		owner: address,323		#[indexed]324		spender: address,325		value: uint256,326	},327}328329#[solidity_interface(330	name = "ERC20",331	inline_is(InlineNameSymbol, InlineTotalSupply),332	events(ERC20Events)333)]334impl<T: Config> CollectionHandle<T> {335	fn decimals(&self) -> Result<uint8> {336		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {337			*decimals338		} else {339			unreachable!()340		})341	}342	fn balance_of(&self, owner: address) -> Result<uint256> {343		let owner = T::EvmAddressMapping::into_account_id(owner);344		let balance = <Balance<T>>::get(self.id, owner);345		Ok(balance.into())346	}347	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {348		let caller = T::CrossAccountId::from_eth(caller);349		let to = T::CrossAccountId::from_eth(to);350		let amount = amount.try_into().map_err(|_| "amount overflow")?;351352		<Module<T>>::transfer_internal(&caller, &to, self, 1, amount)353			.map_err(|_| "transfer error")?;354		Ok(true)355	}356	#[solidity(rename_selector = "transferFrom")]357	fn transfer_from_fungible(358		&mut self,359		caller: caller,360		from: address,361		to: address,362		amount: uint256,363	) -> Result<bool> {364		let caller = T::CrossAccountId::from_eth(caller);365		let from = T::CrossAccountId::from_eth(from);366		let to = T::CrossAccountId::from_eth(to);367		let amount = amount.try_into().map_err(|_| "amount overflow")?;368369		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)370			.map_err(|_| "transferFrom error")?;371		Ok(true)372	}373	#[solidity(rename_selector = "approve")]374	fn approve_fungible(375		&mut self,376		caller: caller,377		spender: address,378		amount: uint256,379	) -> Result<bool> {380		let caller = T::CrossAccountId::from_eth(caller);381		let spender = T::CrossAccountId::from_eth(spender);382		let amount = amount.try_into().map_err(|_| "amount overflow")?;383384		<Module<T>>::approve_internal(&caller, &spender, self, 1, amount)385			.map_err(|_| "approve internal")?;386		Ok(true)387	}388	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {389		let owner = T::CrossAccountId::from_eth(owner);390		let spender = T::CrossAccountId::from_eth(spender);391392		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())393	}394}395396#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]397impl<T: Config> CollectionHandle<T> {}398399macro_rules! generate_code {400	($name:ident, $decl:ident, $is_impl:literal) => {401		#[test]402		#[ignore]403		fn $name() {404			use sp_std::collections::btree_set::BTreeSet;405			let mut out = BTreeSet::new();406			$decl::generate_solidity_interface(&mut out, $is_impl);407			println!("=== SNIP START ===");408			println!("// SPDX-License-Identifier: OTHER");409			println!("// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::{} --exact --nocapture --ignored`", stringify!(name));410			println!();411			println!("pragma solidity >=0.8.0 <0.9.0;");412			println!();413			for b in out {414				println!("{}", b);415			}416			println!("=== SNIP END ===");417		}418	};419}420421// Not a tests, but code generators422generate_code!(nft_impl, UniqueNFTCall, true);423generate_code!(nft_iface, UniqueNFTCall, false);424425generate_code!(fungible_impl, UniqueFungibleCall, true);426generate_code!(fungible_iface, UniqueFungibleCall, false);
modifiedpallets/nft/src/eth/stubs/ERC20.bindiffbeforeafterboth

binary blob — no preview

modifiedpallets/nft/src/eth/stubs/ERC20.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/ERC20.sol
+++ b/pallets/nft/src/eth/stubs/ERC20.sol
@@ -1,69 +1,94 @@
 // SPDX-License-Identifier: OTHER
+// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
 
 pragma solidity >=0.8.0 <0.9.0;
 
-contract ERC20 {
-	uint8 _dummy = 0;
-	string stub_error = "this contract does not exists, code for collections is implemented at pallet side";
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
 
-	// 0x18160ddd
-	function totalSupply() external view returns (uint256) {
+// Inline
+contract ERC20Events {
+	event Transfer(address from, address to, uint256 value);
+	event Approval(address owner, address spender, uint256 value);
+}
+
+// Inline
+contract InlineNameSymbol is Dummy {
+	function name() public view returns (string memory) {
 		require(false, stub_error);
-		_dummy;
-		return 0;
+		dummy;
+		return "";
 	}
+	function symbol() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+}
 
-	// 0x70a08231
-	function balanceOf(address account) external view returns (uint256) {
+// Inline
+contract InlineTotalSupply is Dummy {
+	function totalSupply() public view returns (uint256) {
 		require(false, stub_error);
-		account;
-		_dummy;
+		dummy;
 		return 0;
 	}
+}
 
-	// 0xa9059cbb
-	function transfer(address recipient, uint256 amount) external returns (bool) {
+contract ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) public view returns (bool) {
 		require(false, stub_error);
-		recipient;
-		amount;
-		_dummy = 0;
+		interfaceId;
+		dummy;
 		return false;
 	}
+}
 
-	// 0xdd62ed3e
-	function allowance(address owner, address spender) external view returns (uint256) {
+contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
+	function decimals() public view returns (uint8) {
 		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+	function balanceOf(address owner) public view returns (uint256) {
+		require(false, stub_error);
 		owner;
-		spender;
-		return _dummy;
+		dummy;
+		return 0;
+	}
+	function transfer(address to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
 	}
-
-	// 0x095ea7b3
-	function approve(address spender, uint256 amount) external returns (bool) {
+	function transferFrom(address from, address to, uint256 amount) public returns (bool) {
 		require(false, stub_error);
-		spender;
+		from;
+		to;
 		amount;
-		_dummy = 0;
+		dummy = 0;
 		return false;
 	}
-
-	// 0x23b872dd
-	function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
+	function approve(address spender, uint256 amount) public returns (bool) {
 		require(false, stub_error);
-		sender;
-		recipient;
+		spender;
 		amount;
-		_dummy = 0;
+		dummy = 0;
 		return false;
 	}
+	function allowance(address owner, address spender) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		spender;
+		dummy;
+		return 0;
+	}
+}
 
-	// While ERC165 is not required by spec of ERC20, better implement it
-	// 0x01ffc9a7
-	function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
-		return 
-			// ERC20
-			interfaceID == 0x36372b07 || 
-			// ERC165
-			interfaceID == 0x01ffc9a7;
-	}
+contract UniqueFungible is Dummy, ERC165, ERC20 {
 }
\ No newline at end of file
modifiedpallets/nft/src/eth/stubs/ERC721.bindiffbeforeafterboth

binary blob — no preview

modifiedpallets/nft/src/eth/stubs/ERC721.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/ERC721.sol
+++ b/pallets/nft/src/eth/stubs/ERC721.sol
@@ -1,163 +1,194 @@
 // SPDX-License-Identifier: OTHER
+// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
 
 pragma solidity >=0.8.0 <0.9.0;
 
-contract ERC721 {
-    uint8 _dummy = 0;
-    address _dummy_addr = 0x0000000000000000000000000000000000000000;
-    string _dummy_string = "";
-    string stub_error =
-        "this contract does not exists, code for collections is implemented at pallet side";
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
 
-    event Transfer(
-        address indexed from,
-        address indexed to,
-        uint256 indexed tokenId
-    );
+// Inline
+contract ERC721Events {
+	event Transfer(address from, address to, uint256 tokenId);
+	event Approval(address owner, address approved, uint256 tokenId);
+	event ApprovalForAll(address owner, address operator, bool approved);
+}
 
-    event Approval(
-        address indexed owner,
-        address indexed approved,
-        uint256 indexed tokenId
-    );
+// Inline
+contract ERC721MintableEvents {
+	event MintingFinished();
+}
 
-    event ApprovalForAll(
-        address indexed owner,
-        address indexed operator,
-        bool approved
-    );
+// Inline
+contract InlineNameSymbol is Dummy {
+	function name() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+	function symbol() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+}
 
-	// 0x18160ddd
-    function totalSupply() external view returns (uint256) {
-        require(false, stub_error);
-        return 0;
-    }
+// Inline
+contract InlineTotalSupply is Dummy {
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
 
-    function name() external view returns (string memory res_name) {
-        require(false, stub_error);
-        res_name = _dummy_string;
-    }
+contract ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) public view returns (bool) {
+		require(false, stub_error);
+		interfaceId;
+		dummy;
+		return false;
+	}
+}
 
-    function symbol() external view returns (string memory res_symbol) {
-        require(false, stub_error);
-        res_symbol = _dummy_string;
-    }
+contract ERC721 is Dummy, ERC165, ERC721Events {
+	function balanceOf(address owner) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		dummy;
+		return 0;
+	}
+	function ownerOf(uint256 tokenId) public view returns (address) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+	function safeTransferFromWithData(address from, address to, uint256 tokenId, bytes memory data) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		data;
+		dummy = 0;
+	}
+	function safeTransferFrom(address from, address to, uint256 tokenId) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+	function transferFrom(address from, address to, uint256 tokenId) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+	function approve(address approved, uint256 tokenId) public {
+		require(false, stub_error);
+		approved;
+		tokenId;
+		dummy = 0;
+	}
+	function setApprovalForAll(address operator, bool approved) public {
+		require(false, stub_error);
+		operator;
+		approved;
+		dummy = 0;
+	}
+	function getApproved(uint256 tokenId) public view returns (address) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+	function isApprovedForAll(address owner, address operator) public view returns (address) {
+		require(false, stub_error);
+		owner;
+		operator;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+}
 
-    function tokenURI(uint256 tokenId) external view returns (string memory) {
-        require(false, stub_error);
-        tokenId;
-        return _dummy_string;
-    }
+contract ERC721Burnable is Dummy {
+	function burn(uint256 tokenId) public {
+		require(false, stub_error);
+		tokenId;
+		dummy = 0;
+	}
+}
 
-    function tokenByIndex(uint256 index) external view returns (uint256) {
-        require(false, stub_error);
-        index;
+contract ERC721Enumerable is Dummy, InlineTotalSupply {
+	function tokenByIndex(uint256 index) public view returns (uint256) {
+		require(false, stub_error);
+		index;
+		dummy;
 		return 0;
-    }
-
-    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) {
-        require(false, stub_error);
+	}
+	function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
+		require(false, stub_error);
 		owner;
 		index;
+		dummy;
 		return 0;
-    }
+	}
+}
 
-    // 0x70a08231
-    function balanceOf(address owner) external view returns (uint256) {
-        require(false, stub_error);
-        owner;
-        return 0;
-    }
+contract ERC721Metadata is Dummy, InlineNameSymbol {
+	function tokenUri(uint256 tokenId) public view returns (string memory) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return "";
+	}
+}
 
-    // 0x6352211e
-    function ownerOf(uint256 tokenId) external view returns (address) {
-        require(false, stub_error);
-        tokenId;
-        return _dummy_addr;
-    }
+contract ERC721Mintable is Dummy, ERC721MintableEvents {
+	function mintingFinished() public view returns (bool) {
+		require(false, stub_error);
+		dummy;
+		return false;
+	}
+	function mint(address to, uint256 tokenId) public returns (bool) {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+		return false;
+	}
+	function mintWithTokenURI(address to, uint256 tokenId, string memory tokenUri) public returns (bool) {
+		require(false, stub_error);
+		to;
+		tokenId;
+		tokenUri;
+		dummy = 0;
+		return false;
+	}
+	function finishMinting() public returns (bool) {
+		require(false, stub_error);
+		dummy = 0;
+		return false;
+	}
+}
 
-    // 0xb88d4fde
-    function safeTransferFrom(
-        address from,
-        address to,
-        uint256 tokenId,
-        bytes calldata data
-    ) external payable {
-        require(false, stub_error);
-        from;
-        to;
-        tokenId;
-        data;
-    }
-
-    // 0x42842e0e
-    function safeTransferFrom(
-        address from,
-        address to,
-        uint256 tokenId
-    ) external payable {
-        require(false, stub_error);
-        from;
-        to;
-        tokenId;
-    }
-
-    // 0x23b872dd
-    function transferFrom(
-        address from,
-        address to,
-        uint256 tokenId
-    ) external payable {
-        require(false, stub_error);
-        from;
-        to;
-        tokenId;
-    }
+contract ERC721UniqueExtensions is Dummy {
+	function transfer(address to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+	function nextTokenId() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
 
-    // 0x095ea7b3
-    function approve(address approved, uint256 tokenId) external payable {
-        require(false, stub_error);
-        approved;
-        tokenId;
-    }
-
-    // 0xa22cb465
-    function setApprovalForAll(address operator, bool approved) external {
-        require(false, stub_error);
-        operator;
-        approved;
-        _dummy = 0;
-    }
-
-    // 0x081812fc
-    function getApproved(uint256 tokenId) external view returns (address) {
-        require(false, stub_error);
-        tokenId;
-        return _dummy_addr;
-    }
-
-    // 0xe985e9c5
-    function isApprovedForAll(address owner, address operator)
-        external
-        view
-        returns (bool)
-    {
-        require(false, stub_error);
-        owner;
-        operator;
-        return false;
-    }
-
-    // 0x01ffc9a7
-    function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
-        return
-            // ERC721
-            interfaceID == 0x80ac58cd ||
-            // ERC721Metadata
-            interfaceID == 0x5b5e139f ||
-            // ERC721Enumerable
-            interfaceID == 0x780e9d63 ||
-            // ERC165
-            interfaceID == 0x01ffc9a7;
-    }
-}
+contract UniqueNFT is Dummy, ERC165, ERC721, ERC721Metadata, ERC721Enumerable, ERC721UniqueExtensions, ERC721Mintable, ERC721Burnable {
+}
\ No newline at end of file