git.delta.rocks / unique-network / refs/commits / 3d14ec560294

difftreelog

source

pallets/nonfungible/src/erc.rs10.8 KiBsourcehistory
1use core::{2	char::{REPLACEMENT_CHARACTER, decode_utf16},3	convert::TryInto,4};5use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*};6use frame_support::BoundedVec;7use nft_data_structs::TokenId;8use pallet_evm_coder_substrate::dispatch_to_evm;9use sp_core::{H160, U256};10use sp_std::{vec::Vec, vec};11use pallet_common::{account::CrossAccountId, erc::CommonEvmHandler};12use pallet_evm_coder_substrate::call_internal;13use pallet_common::erc::PrecompileOutput;1415use crate::{16	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,17};1819#[derive(ToLog)]20pub enum ERC721Events {21	Transfer {22		#[indexed]23		from: address,24		#[indexed]25		to: address,26		#[indexed]27		token_id: uint256,28	},29	Approval {30		#[indexed]31		owner: address,32		#[indexed]33		approved: address,34		#[indexed]35		token_id: uint256,36	},37	#[allow(dead_code)]38	ApprovalForAll {39		#[indexed]40		owner: address,41		#[indexed]42		operator: address,43		approved: bool,44	},45}4647#[derive(ToLog)]48pub enum ERC721MintableEvents {49	#[allow(dead_code)]50	MintingFinished {},51}5253#[solidity_interface(name = "ERC721Metadata")]54impl<T: Config> NonfungibleHandle<T> {55	fn name(&self) -> Result<string> {56		Ok(decode_utf16(self.name.iter().copied())57			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))58			.collect::<string>())59	}60	fn symbol(&self) -> Result<string> {61		Ok(string::from_utf8_lossy(&self.token_prefix).into())62	}6364	/// Returns token's const_metadata65	#[solidity(rename_selector = "tokenURI")]66	fn token_uri(&self, token_id: uint256) -> Result<string> {67		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;68		Ok(string::from_utf8_lossy(69			&<TokenData<T>>::get((self.id, token_id))70				.ok_or("token not found")?71				.const_data,72		)73		.into())74	}75}7677#[solidity_interface(name = "ERC721Enumerable")]78impl<T: Config> NonfungibleHandle<T> {79	fn token_by_index(&self, index: uint256) -> Result<uint256> {80		Ok(index)81	}8283	/// Not implemented84	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {85		// TODO: Not implemetable86		Err("not implemented".into())87	}8889	fn total_supply(&self) -> Result<uint256> {90		Ok(<Pallet<T>>::total_supply(self).into())91	}92}9394#[solidity_interface(name = "ERC721", events(ERC721Events))]95impl<T: Config> NonfungibleHandle<T> {96	fn balance_of(&self, owner: address) -> Result<uint256> {97		let owner = T::CrossAccountId::from_eth(owner);98		let balance = <AccountBalance<T>>::get((self.id, owner));99		Ok(balance.into())100	}101	fn owner_of(&self, token_id: uint256) -> Result<address> {102		let token: TokenId = token_id.try_into()?;103		Ok(*<TokenData<T>>::get((self.id, token))104			.ok_or("token not found")?105			.owner106			.as_eth())107	}108	/// Not implemented109	fn safe_transfer_from_with_data(110		&mut self,111		_from: address,112		_to: address,113		_token_id: uint256,114		_data: bytes,115		_value: value,116	) -> Result<void> {117		// TODO: Not implemetable118		Err("not implemented".into())119	}120	/// Not implemented121	fn safe_transfer_from(122		&mut self,123		_from: address,124		_to: address,125		_token_id: uint256,126		_value: value,127	) -> Result<void> {128		// TODO: Not implemetable129		Err("not implemented".into())130	}131132	fn transfer_from(133		&mut self,134		caller: caller,135		from: address,136		to: address,137		token_id: uint256,138		_value: value,139	) -> Result<void> {140		let caller = T::CrossAccountId::from_eth(caller);141		let from = T::CrossAccountId::from_eth(from);142		let to = T::CrossAccountId::from_eth(to);143		let token = token_id.try_into()?;144145		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token)146			.map_err(dispatch_to_evm::<T>)?;147		Ok(())148	}149150	fn approve(151		&mut self,152		caller: caller,153		approved: address,154		token_id: uint256,155		_value: value,156	) -> Result<void> {157		let caller = T::CrossAccountId::from_eth(caller);158		let approved = T::CrossAccountId::from_eth(approved);159		let token = token_id.try_into()?;160161		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))162			.map_err(dispatch_to_evm::<T>)?;163		Ok(())164	}165166	/// Not implemented167	fn set_approval_for_all(168		&mut self,169		_caller: caller,170		_operator: address,171		_approved: bool,172	) -> Result<void> {173		// TODO: Not implemetable174		Err("not implemented".into())175	}176177	/// Not implemented178	fn get_approved(&self, _token_id: uint256) -> Result<address> {179		// TODO: Not implemetable180		Err("not implemented".into())181	}182183	/// Not implemented184	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {185		// TODO: Not implemetable186		Err("not implemented".into())187	}188}189190#[solidity_interface(name = "ERC721Burnable")]191impl<T: Config> NonfungibleHandle<T> {192	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {193		let caller = T::CrossAccountId::from_eth(caller);194		let token = token_id.try_into()?;195196		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;197		Ok(())198	}199}200201#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]202impl<T: Config> NonfungibleHandle<T> {203	fn minting_finished(&self) -> Result<bool> {204		Ok(false)205	}206207	/// `token_id` should be obtained with `next_token_id` method,208	/// unlike standard, you can't specify it manually209	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {210		let caller = T::CrossAccountId::from_eth(caller);211		let to = T::CrossAccountId::from_eth(to);212		let token_id: u32 = token_id.try_into()?;213		if <TokensMinted<T>>::get(self.id)214			.checked_add(1)215			.ok_or("item id overflow")?216			!= token_id217		{218			return Err("item id should be next".into());219		}220221		<Pallet<T>>::create_item(222			self,223			&caller,224			CreateItemData {225				const_data: BoundedVec::default(),226				variable_data: BoundedVec::default(),227				owner: to,228			},229		)230		.map_err(dispatch_to_evm::<T>)?;231232		Ok(true)233	}234235	/// `token_id` should be obtained with `next_token_id` method,236	/// unlike standard, you can't specify it manually237	#[solidity(rename_selector = "mintWithTokenURI")]238	fn mint_with_token_uri(239		&mut self,240		caller: caller,241		to: address,242		token_id: uint256,243		token_uri: string,244	) -> Result<bool> {245		let caller = T::CrossAccountId::from_eth(caller);246		let to = T::CrossAccountId::from_eth(to);247		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;248		if <TokensMinted<T>>::get(self.id)249			.checked_add(1)250			.ok_or("item id overflow")?251			!= token_id252		{253			return Err("item id should be next".into());254		}255256		<Pallet<T>>::create_item(257			self,258			&caller,259			CreateItemData {260				const_data: Vec::<u8>::from(token_uri)261					.try_into()262					.map_err(|_| "token uri is too long")?,263				variable_data: BoundedVec::default(),264				owner: to,265			},266		)267		.map_err(dispatch_to_evm::<T>)?;268		Ok(true)269	}270271	/// Not implemented272	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {273		Err("not implementable".into())274	}275}276277#[solidity_interface(name = "ERC721UniqueExtensions")]278impl<T: Config> NonfungibleHandle<T> {279	fn transfer(280		&mut self,281		caller: caller,282		to: address,283		token_id: uint256,284		_value: value,285	) -> Result<void> {286		let caller = T::CrossAccountId::from_eth(caller);287		let to = T::CrossAccountId::from_eth(to);288		let token = token_id.try_into()?;289290		<Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;291		Ok(())292	}293294	fn burn_from(295		&mut self,296		caller: caller,297		from: address,298		token_id: uint256,299		_value: value,300	) -> Result<void> {301		let caller = T::CrossAccountId::from_eth(caller);302		let from = T::CrossAccountId::from_eth(from);303		let token = token_id.try_into()?;304305		<Pallet<T>>::burn_from(self, &caller, &from, token).map_err(dispatch_to_evm::<T>)?;306		Ok(())307	}308309	fn next_token_id(&self) -> Result<uint256> {310		Ok(<TokensMinted<T>>::get(self.id)311			.checked_add(1)312			.ok_or("item id overflow")?313			.into())314	}315316	fn set_variable_metadata(317		&mut self,318		caller: caller,319		token_id: uint256,320		data: bytes,321	) -> Result<void> {322		let caller = T::CrossAccountId::from_eth(caller);323		let token = token_id.try_into()?;324325		<Pallet<T>>::set_variable_metadata(self, &caller, token, data)326			.map_err(dispatch_to_evm::<T>)?;327		Ok(())328	}329330	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {331		let token: TokenId = token_id.try_into()?;332333		Ok(<TokenData<T>>::get((self.id, token))334			.ok_or("token not found")?335			.variable_data)336	}337338	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {339		let caller = T::CrossAccountId::from_eth(caller);340		let to = T::CrossAccountId::from_eth(to);341		let mut expected_index = <TokensMinted<T>>::get(self.id)342			.checked_add(1)343			.ok_or("item id overflow")?;344345		let total_tokens = token_ids.len();346		for id in token_ids.into_iter() {347			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;348			if id != expected_index {349				return Err("item id should be next".into());350			}351			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;352		}353		let data = (0..total_tokens)354			.map(|_| CreateItemData {355				const_data: BoundedVec::default(),356				variable_data: BoundedVec::default(),357				owner: to.clone(),358			})359			.collect();360361		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;362		Ok(true)363	}364365	#[solidity(rename_selector = "mintBulkWithTokenURI")]366	fn mint_bulk_with_token_uri(367		&mut self,368		caller: caller,369		to: address,370		tokens: Vec<(uint256, string)>,371	) -> Result<bool> {372		let caller = T::CrossAccountId::from_eth(caller);373		let to = T::CrossAccountId::from_eth(to);374		let mut expected_index = <TokensMinted<T>>::get(self.id)375			.checked_add(1)376			.ok_or("item id overflow")?;377378		let mut data = Vec::with_capacity(tokens.len());379		for (id, token_uri) in tokens {380			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;381			if id != expected_index {382				panic!("item id should be next ({}) but got {}", expected_index, id);383			}384			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;385386			data.push(CreateItemData {387				const_data: Vec::<u8>::from(token_uri)388					.try_into()389					.map_err(|_| "token uri is too long")?,390				variable_data: vec![].try_into().unwrap(),391				owner: to.clone(),392			});393		}394395		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;396		Ok(true)397	}398}399400#[solidity_interface(401	name = "UniqueNFT",402	is(403		ERC721,404		ERC721Metadata,405		ERC721Enumerable,406		ERC721UniqueExtensions,407		ERC721Mintable,408		ERC721Burnable,409	)410)]411impl<T: Config> NonfungibleHandle<T> {}412413// Not a tests, but code generators414generate_stubgen!(gen_impl, UniqueNFTCall, true);415generate_stubgen!(gen_iface, UniqueNFTCall, false);416417pub const CODE: &[u8] = include_bytes!("./stubs/UniqueNFT.raw");418419impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {420	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");421422	fn call(mut self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileOutput> {423		let result = call_internal::<UniqueNFTCall, _>(*source, &mut self, value, input);424		self.0.recorder.evm_to_precompile_output(result)425	}426}