difftreelog
feat bulk mint/get/set metadata
in: master
5 files changed
pallets/nft/src/eth/erc.rsdiffbeforeafterboth1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, generate_stubgen, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use crate::{6 Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,7 ItemListIndex,8};9use frame_support::storage::{StorageMap, StorageDoubleMap};10use pallet_evm::AddressMapping;11use super::account::CrossAccountId;12use sp_std::{vec, vec::Vec};1314#[solidity_interface(name = "ERC165")]15impl<T: Config> CollectionHandle<T> {16 fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {17 Ok(match self.mode {18 CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),19 CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),20 _ => false,21 })22 }23}2425#[solidity_interface(name = "InlineNameSymbol")]26impl<T: Config> CollectionHandle<T> {27 fn name(&self) -> Result<string> {28 Ok(decode_utf16(self.name.iter().copied())29 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))30 .collect::<string>())31 }3233 fn symbol(&self) -> Result<string> {34 Ok(string::from_utf8_lossy(&self.token_prefix).into())35 }36}3738#[solidity_interface(name = "InlineTotalSupply")]39impl<T: Config> CollectionHandle<T> {40 fn total_supply(&self) -> Result<uint256> {41 // TODO: we do not track total amount of all tokens42 Ok(0.into())43 }44}4546#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]47impl<T: Config> CollectionHandle<T> {48 #[solidity(rename_selector = "tokenURI")]49 fn token_uri(&self, token_id: uint256) -> Result<string> {50 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;51 Ok(string::from_utf8_lossy(52 &<NftItemList<T>>::get(self.id, token_id)53 .ok_or("token not found")?54 .const_data,55 )56 .into())57 }58}5960#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]61impl<T: Config> CollectionHandle<T> {62 fn token_by_index(&self, index: uint256) -> Result<uint256> {63 Ok(index)64 }6566 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {67 // TODO: Not implemetable68 Err("not implemented".into())69 }70}7172#[derive(ToLog)]73pub enum ERC721Events {74 Transfer {75 #[indexed]76 from: address,77 #[indexed]78 to: address,79 #[indexed]80 token_id: uint256,81 },82 Approval {83 #[indexed]84 owner: address,85 #[indexed]86 approved: address,87 #[indexed]88 token_id: uint256,89 },90 #[allow(dead_code)]91 ApprovalForAll {92 #[indexed]93 owner: address,94 #[indexed]95 operator: address,96 approved: bool,97 },98}99100#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]101impl<T: Config> CollectionHandle<T> {102 #[solidity(rename_selector = "balanceOf")]103 fn balance_of_nft(&self, owner: address) -> Result<uint256> {104 let owner = T::EvmAddressMapping::into_account_id(owner);105 let balance = <Balance<T>>::get(self.id, owner);106 Ok(balance.into())107 }108 fn owner_of(&self, token_id: uint256) -> Result<address> {109 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;110 let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;111 Ok(*token.owner.as_eth())112 }113 fn safe_transfer_from_with_data(114 &mut self,115 _from: address,116 _to: address,117 _token_id: uint256,118 _data: bytes,119 _value: value,120 ) -> Result<void> {121 // TODO: Not implemetable122 Err("not implemented".into())123 }124 fn safe_transfer_from(125 &mut self,126 _from: address,127 _to: address,128 _token_id: uint256,129 _value: value,130 ) -> Result<void> {131 // TODO: Not implemetable132 Err("not implemented".into())133 }134135 fn transfer_from(136 &mut self,137 caller: caller,138 from: address,139 to: address,140 token_id: uint256,141 _value: value,142 ) -> Result<void> {143 let caller = T::CrossAccountId::from_eth(caller);144 let from = T::CrossAccountId::from_eth(from);145 let to = T::CrossAccountId::from_eth(to);146 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;147148 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)149 .map_err(|_| "transferFrom error")?;150 Ok(())151 }152153 fn approve(154 &mut self,155 caller: caller,156 approved: address,157 token_id: uint256,158 _value: value,159 ) -> Result<void> {160 let caller = T::CrossAccountId::from_eth(caller);161 let approved = T::CrossAccountId::from_eth(approved);162 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;163164 <Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)165 .map_err(|_| "approve internal")?;166 Ok(())167 }168169 fn set_approval_for_all(170 &mut self,171 _caller: caller,172 _operator: address,173 _approved: bool,174 ) -> Result<void> {175 // TODO: Not implemetable176 Err("not implemented".into())177 }178179 fn get_approved(&self, _token_id: uint256) -> Result<address> {180 // TODO: Not implemetable181 Err("not implemented".into())182 }183184 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> CollectionHandle<T> {192 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {193 let caller = T::CrossAccountId::from_eth(caller);194 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;195196 <Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;197 Ok(())198 }199}200201#[derive(ToLog)]202pub enum ERC721MintableEvents {203 #[allow(dead_code)]204 MintingFinished {},205}206207#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]208impl<T: Config> CollectionHandle<T> {209 fn minting_finished(&self) -> Result<bool> {210 Ok(false)211 }212213 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {214 let caller = T::CrossAccountId::from_eth(caller);215 let to = T::CrossAccountId::from_eth(to);216 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;217 if <ItemListIndex>::get(self.id)218 .checked_add(1)219 .ok_or("item id overflow")?220 != token_id221 {222 return Err("item id should be next".into());223 }224225 <Module<T>>::create_item_internal(226 &caller,227 &self,228 &to,229 CreateItemData::NFT(CreateNftData {230 const_data: vec![].try_into().unwrap(),231 variable_data: vec![].try_into().unwrap(),232 }),233 )234 .map_err(|_| "mint error")?;235 Ok(true)236 }237238 #[solidity(rename_selector = "mintWithTokenURI")]239 fn mint_with_token_uri(240 &mut self,241 caller: caller,242 to: address,243 token_id: uint256,244 token_uri: string,245 ) -> Result<bool> {246 let caller = T::CrossAccountId::from_eth(caller);247 let to = T::CrossAccountId::from_eth(to);248 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;249 if <ItemListIndex>::get(self.id)250 .checked_add(1)251 .ok_or("item id overflow")?252 != token_id253 {254 return Err("item id should be next".into());255 }256257 <Module<T>>::create_item_internal(258 &caller,259 &self,260 &to,261 CreateItemData::NFT(CreateNftData {262 const_data: Vec::<u8>::from(token_uri)263 .try_into()264 .map_err(|_| "token uri is too long")?,265 variable_data: vec![].try_into().unwrap(),266 }),267 )268 .map_err(|_| "mint error")?;269 Ok(true)270 }271272 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {273 Err("not implementable".into())274 }275}276277#[solidity_interface(name = "ERC721UniqueExtensions")]278impl<T: Config> CollectionHandle<T> {279 #[solidity(rename_selector = "transfer")]280 fn transfer_nft(281 &mut self,282 caller: caller,283 to: address,284 token_id: uint256,285 _value: value,286 ) -> Result<void> {287 let caller = T::CrossAccountId::from_eth(caller);288 let to = T::CrossAccountId::from_eth(to);289 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;290291 <Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)292 .map_err(|_| "transfer error")?;293 Ok(())294 }295296 fn next_token_id(&self) -> Result<uint256> {297 Ok(ItemListIndex::get(self.id)298 .checked_add(1)299 .ok_or("item id overflow")?300 .into())301 }302}303304#[solidity_interface(305 name = "UniqueNFT",306 is(307 ERC165,308 ERC721,309 ERC721Metadata,310 ERC721Enumerable,311 ERC721UniqueExtensions,312 ERC721Mintable,313 ERC721Burnable,314 )315)]316impl<T: Config> CollectionHandle<T> {}317318#[derive(ToLog)]319pub enum ERC20Events {320 Transfer {321 #[indexed]322 from: address,323 #[indexed]324 to: address,325 value: uint256,326 },327 Approval {328 #[indexed]329 owner: address,330 #[indexed]331 spender: address,332 value: uint256,333 },334}335336#[solidity_interface(337 name = "ERC20",338 inline_is(InlineNameSymbol, InlineTotalSupply),339 events(ERC20Events)340)]341impl<T: Config> CollectionHandle<T> {342 fn decimals(&self) -> Result<uint8> {343 Ok(if let CollectionMode::Fungible(decimals) = &self.mode {344 *decimals345 } else {346 unreachable!()347 })348 }349 fn balance_of(&self, owner: address) -> Result<uint256> {350 let owner = T::EvmAddressMapping::into_account_id(owner);351 let balance = <Balance<T>>::get(self.id, owner);352 Ok(balance.into())353 }354 fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {355 let caller = T::CrossAccountId::from_eth(caller);356 let to = T::CrossAccountId::from_eth(to);357 let amount = amount.try_into().map_err(|_| "amount overflow")?;358359 <Module<T>>::transfer_internal(&caller, &to, self, 1, amount)360 .map_err(|_| "transfer error")?;361 Ok(true)362 }363 #[solidity(rename_selector = "transferFrom")]364 fn transfer_from_fungible(365 &mut self,366 caller: caller,367 from: address,368 to: address,369 amount: uint256,370 ) -> Result<bool> {371 let caller = T::CrossAccountId::from_eth(caller);372 let from = T::CrossAccountId::from_eth(from);373 let to = T::CrossAccountId::from_eth(to);374 let amount = amount.try_into().map_err(|_| "amount overflow")?;375376 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)377 .map_err(|_| "transferFrom error")?;378 Ok(true)379 }380 #[solidity(rename_selector = "approve")]381 fn approve_fungible(382 &mut self,383 caller: caller,384 spender: address,385 amount: uint256,386 ) -> Result<bool> {387 let caller = T::CrossAccountId::from_eth(caller);388 let spender = T::CrossAccountId::from_eth(spender);389 let amount = amount.try_into().map_err(|_| "amount overflow")?;390391 <Module<T>>::approve_internal(&caller, &spender, self, 1, amount)392 .map_err(|_| "approve internal")?;393 Ok(true)394 }395 fn allowance(&self, owner: address, spender: address) -> Result<uint256> {396 let owner = T::CrossAccountId::from_eth(owner);397 let spender = T::CrossAccountId::from_eth(spender);398399 Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())400 }401}402403#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]404impl<T: Config> CollectionHandle<T> {}405406// Not a tests, but code generators407generate_stubgen!(nft_impl, UniqueNFTCall, true);408generate_stubgen!(nft_iface, UniqueNFTCall, false);409410generate_stubgen!(fungible_impl, UniqueFungibleCall, true);411generate_stubgen!(fungible_iface, UniqueFungibleCall, false);1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, generate_stubgen, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use crate::{6 Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,7 ItemListIndex,8};9use frame_support::storage::{StorageMap, StorageDoubleMap};10use pallet_evm::AddressMapping;11use pallet_evm_coder_substrate::dispatch_to_evm;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 #[solidity(rename_selector = "tokenURI")]50 fn token_uri(&self, token_id: uint256) -> Result<string> {51 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;52 Ok(string::from_utf8_lossy(53 &<NftItemList<T>>::get(self.id, token_id)54 .ok_or("token not found")?55 .const_data,56 )57 .into())58 }59}6061#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]62impl<T: Config> CollectionHandle<T> {63 fn token_by_index(&self, index: uint256) -> Result<uint256> {64 Ok(index)65 }6667 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {68 // TODO: Not implemetable69 Err("not implemented".into())70 }71}7273#[derive(ToLog)]74pub enum ERC721Events {75 Transfer {76 #[indexed]77 from: address,78 #[indexed]79 to: address,80 #[indexed]81 token_id: uint256,82 },83 Approval {84 #[indexed]85 owner: address,86 #[indexed]87 approved: address,88 #[indexed]89 token_id: uint256,90 },91 #[allow(dead_code)]92 ApprovalForAll {93 #[indexed]94 owner: address,95 #[indexed]96 operator: address,97 approved: bool,98 },99}100101#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]102impl<T: Config> CollectionHandle<T> {103 #[solidity(rename_selector = "balanceOf")]104 fn balance_of_nft(&self, owner: address) -> Result<uint256> {105 let owner = T::EvmAddressMapping::into_account_id(owner);106 let balance = <Balance<T>>::get(self.id, owner);107 Ok(balance.into())108 }109 fn owner_of(&self, token_id: uint256) -> Result<address> {110 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;111 let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;112 Ok(*token.owner.as_eth())113 }114 fn safe_transfer_from_with_data(115 &mut self,116 _from: address,117 _to: address,118 _token_id: uint256,119 _data: bytes,120 _value: value,121 ) -> Result<void> {122 // TODO: Not implemetable123 Err("not implemented".into())124 }125 fn safe_transfer_from(126 &mut self,127 _from: address,128 _to: address,129 _token_id: uint256,130 _value: value,131 ) -> Result<void> {132 // TODO: Not implemetable133 Err("not implemented".into())134 }135136 fn transfer_from(137 &mut self,138 caller: caller,139 from: address,140 to: address,141 token_id: uint256,142 _value: value,143 ) -> Result<void> {144 let caller = T::CrossAccountId::from_eth(caller);145 let from = T::CrossAccountId::from_eth(from);146 let to = T::CrossAccountId::from_eth(to);147 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;148149 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)150 .map_err(|_| "transferFrom error")?;151 Ok(())152 }153154 fn approve(155 &mut self,156 caller: caller,157 approved: address,158 token_id: uint256,159 _value: value,160 ) -> Result<void> {161 let caller = T::CrossAccountId::from_eth(caller);162 let approved = T::CrossAccountId::from_eth(approved);163 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;164165 <Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)166 .map_err(|_| "approve internal")?;167 Ok(())168 }169170 fn set_approval_for_all(171 &mut self,172 _caller: caller,173 _operator: address,174 _approved: bool,175 ) -> Result<void> {176 // TODO: Not implemetable177 Err("not implemented".into())178 }179180 fn get_approved(&self, _token_id: uint256) -> Result<address> {181 // TODO: Not implemetable182 Err("not implemented".into())183 }184185 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {186 // TODO: Not implemetable187 Err("not implemented".into())188 }189}190191#[solidity_interface(name = "ERC721Burnable")]192impl<T: Config> CollectionHandle<T> {193 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {194 let caller = T::CrossAccountId::from_eth(caller);195 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;196197 <Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;198 Ok(())199 }200}201202#[derive(ToLog)]203pub enum ERC721MintableEvents {204 #[allow(dead_code)]205 MintingFinished {},206}207208#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]209impl<T: Config> CollectionHandle<T> {210 fn minting_finished(&self) -> Result<bool> {211 Ok(false)212 }213214 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {215 let caller = T::CrossAccountId::from_eth(caller);216 let to = T::CrossAccountId::from_eth(to);217 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;218 if <ItemListIndex>::get(self.id)219 .checked_add(1)220 .ok_or("item id overflow")?221 != token_id222 {223 return Err("item id should be next".into());224 }225226 <Module<T>>::create_item_internal(227 &caller,228 &self,229 &to,230 CreateItemData::NFT(CreateNftData {231 const_data: vec![].try_into().unwrap(),232 variable_data: vec![].try_into().unwrap(),233 }),234 )235 .map_err(|_| "mint error")?;236 Ok(true)237 }238239 #[solidity(rename_selector = "mintWithTokenURI")]240 fn mint_with_token_uri(241 &mut self,242 caller: caller,243 to: address,244 token_id: uint256,245 token_uri: string,246 ) -> Result<bool> {247 let caller = T::CrossAccountId::from_eth(caller);248 let to = T::CrossAccountId::from_eth(to);249 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;250 if <ItemListIndex>::get(self.id)251 .checked_add(1)252 .ok_or("item id overflow")?253 != token_id254 {255 return Err("item id should be next".into());256 }257258 <Module<T>>::create_item_internal(259 &caller,260 &self,261 &to,262 CreateItemData::NFT(CreateNftData {263 const_data: Vec::<u8>::from(token_uri)264 .try_into()265 .map_err(|_| "token uri is too long")?,266 variable_data: vec![].try_into().unwrap(),267 }),268 )269 .map_err(|_| "mint error")?;270 Ok(true)271 }272273 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {274 Err("not implementable".into())275 }276}277278#[solidity_interface(name = "ERC721UniqueExtensions")]279impl<T: Config> CollectionHandle<T> {280 #[solidity(rename_selector = "transfer")]281 fn transfer_nft(282 &mut self,283 caller: caller,284 to: address,285 token_id: uint256,286 _value: value,287 ) -> Result<void> {288 let caller = T::CrossAccountId::from_eth(caller);289 let to = T::CrossAccountId::from_eth(to);290 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;291292 <Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)293 .map_err(|_| "transfer error")?;294 Ok(())295 }296297 fn next_token_id(&self) -> Result<uint256> {298 Ok(ItemListIndex::get(self.id)299 .checked_add(1)300 .ok_or("item id overflow")?301 .into())302 }303304 fn set_variable_metadata(305 &mut self,306 caller: caller,307 token_id: uint256,308 data: bytes,309 ) -> Result<void> {310 let caller = T::CrossAccountId::from_eth(caller);311 let token_id = token_id.try_into().map_err(|_| "token id overflow")?;312313 <Module<T>>::set_variable_meta_data_internal(&caller, self, token_id, data)314 .map_err(dispatch_to_evm::<T>)?;315 Ok(())316 }317318 fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {319 let token_id = token_id.try_into().map_err(|_| "token id overflow")?;320321 <Module<T>>::get_variable_metadata(self, token_id).map_err(dispatch_to_evm::<T>)322 }323324 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {325 let caller = T::CrossAccountId::from_eth(caller);326 let to = T::CrossAccountId::from_eth(to);327 let mut expected_index = <ItemListIndex>::get(self.id)328 .checked_add(1)329 .ok_or("item id overflow")?;330331 let total_tokens = token_ids.len();332 for id in token_ids.into_iter() {333 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;334 if id != expected_index {335 return Err("item id should be next".into());336 }337 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;338 }339340 let data = (0..total_tokens)341 .map(|_| {342 CreateItemData::NFT(CreateNftData {343 const_data: vec![].try_into().unwrap(),344 variable_data: vec![].try_into().unwrap(),345 })346 })347 .collect();348349 <Module<T>>::create_multiple_items_internal(&caller, self, &to, data)350 .map_err(dispatch_to_evm::<T>)?;351 Ok(true)352 }353354 #[solidity(rename_selector = "mintBulkWithTokenURI")]355 fn mint_bulk_with_token_uri(356 &mut self,357 caller: caller,358 to: address,359 tokens: Vec<(uint256, string)>,360 ) -> Result<bool> {361 let caller = T::CrossAccountId::from_eth(caller);362 let to = T::CrossAccountId::from_eth(to);363 let mut expected_index = <ItemListIndex>::get(self.id)364 .checked_add(1)365 .ok_or("item id overflow")?;366367 let mut data = Vec::with_capacity(tokens.len());368 for (id, token_uri) in tokens {369 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;370 if id != expected_index {371 panic!("item id should be next ({}) but got {}", expected_index, id);372 }373 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;374375 data.push(CreateItemData::NFT(CreateNftData {376 const_data: Vec::<u8>::from(token_uri)377 .try_into()378 .map_err(|_| "token uri is too long")?,379 variable_data: vec![].try_into().unwrap(),380 }));381 }382383 <Module<T>>::create_multiple_items_internal(&caller, self, &to, data)384 .map_err(dispatch_to_evm::<T>)?;385 Ok(true)386 }387}388389#[solidity_interface(390 name = "UniqueNFT",391 is(392 ERC165,393 ERC721,394 ERC721Metadata,395 ERC721Enumerable,396 ERC721UniqueExtensions,397 ERC721Mintable,398 ERC721Burnable,399 )400)]401impl<T: Config> CollectionHandle<T> {}402403#[derive(ToLog)]404pub enum ERC20Events {405 Transfer {406 #[indexed]407 from: address,408 #[indexed]409 to: address,410 value: uint256,411 },412 Approval {413 #[indexed]414 owner: address,415 #[indexed]416 spender: address,417 value: uint256,418 },419}420421#[solidity_interface(422 name = "ERC20",423 inline_is(InlineNameSymbol, InlineTotalSupply),424 events(ERC20Events)425)]426impl<T: Config> CollectionHandle<T> {427 fn decimals(&self) -> Result<uint8> {428 Ok(if let CollectionMode::Fungible(decimals) = &self.mode {429 *decimals430 } else {431 unreachable!()432 })433 }434 fn balance_of(&self, owner: address) -> Result<uint256> {435 let owner = T::EvmAddressMapping::into_account_id(owner);436 let balance = <Balance<T>>::get(self.id, owner);437 Ok(balance.into())438 }439 fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {440 let caller = T::CrossAccountId::from_eth(caller);441 let to = T::CrossAccountId::from_eth(to);442 let amount = amount.try_into().map_err(|_| "amount overflow")?;443444 <Module<T>>::transfer_internal(&caller, &to, self, 1, amount)445 .map_err(|_| "transfer error")?;446 Ok(true)447 }448 #[solidity(rename_selector = "transferFrom")]449 fn transfer_from_fungible(450 &mut self,451 caller: caller,452 from: address,453 to: address,454 amount: uint256,455 ) -> Result<bool> {456 let caller = T::CrossAccountId::from_eth(caller);457 let from = T::CrossAccountId::from_eth(from);458 let to = T::CrossAccountId::from_eth(to);459 let amount = amount.try_into().map_err(|_| "amount overflow")?;460461 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)462 .map_err(|_| "transferFrom error")?;463 Ok(true)464 }465 #[solidity(rename_selector = "approve")]466 fn approve_fungible(467 &mut self,468 caller: caller,469 spender: address,470 amount: uint256,471 ) -> Result<bool> {472 let caller = T::CrossAccountId::from_eth(caller);473 let spender = T::CrossAccountId::from_eth(spender);474 let amount = amount.try_into().map_err(|_| "amount overflow")?;475476 <Module<T>>::approve_internal(&caller, &spender, self, 1, amount)477 .map_err(|_| "approve internal")?;478 Ok(true)479 }480 fn allowance(&self, owner: address, spender: address) -> Result<uint256> {481 let owner = T::CrossAccountId::from_eth(owner);482 let spender = T::CrossAccountId::from_eth(spender);483484 Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())485 }486}487488#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]489impl<T: Config> CollectionHandle<T> {}490491// Not a tests, but code generators492generate_stubgen!(nft_impl, UniqueNFTCall, true);493generate_stubgen!(nft_iface, UniqueNFTCall, false);494495generate_stubgen!(fungible_impl, UniqueFungibleCall, true);496generate_stubgen!(fungible_iface, UniqueFungibleCall, false);pallets/nft/src/eth/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nft/src/eth/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nft/src/eth/stubs/UniqueNFT.sol
+++ b/pallets/nft/src/eth/stubs/UniqueNFT.sol
@@ -3,6 +3,12 @@
pragma solidity >=0.8.0 <0.9.0;
+// Anonymous struct
+struct Tuple0 {
+ uint256 field_0;
+ string field_1;
+}
+
// Common stubs holder
contract Dummy {
uint8 dummy;
@@ -239,6 +245,50 @@
dummy;
return 0;
}
+
+ // Selector: setVariableMetadata(uint256,bytes) d4eac26d
+ function setVariableMetadata(uint256 tokenId, bytes memory data) public {
+ require(false, stub_error);
+ tokenId;
+ data;
+ dummy = 0;
+ }
+
+ // Selector: getVariableMetadata(uint256) e6c5ce6f
+ function getVariableMetadata(uint256 tokenId)
+ public
+ view
+ returns (bytes memory)
+ {
+ require(false, stub_error);
+ tokenId;
+ dummy;
+ return hex"";
+ }
+
+ // Selector: mintBulk(address,uint256[]) 44a9945e
+ function mintBulk(address to, uint256[] memory tokenIds)
+ public
+ returns (bool)
+ {
+ require(false, stub_error);
+ to;
+ tokenIds;
+ dummy = 0;
+ return false;
+ }
+
+ // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
+ function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
+ public
+ returns (bool)
+ {
+ require(false, stub_error);
+ to;
+ tokens;
+ dummy = 0;
+ return false;
+ }
}
contract UniqueNFT is
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -1501,6 +1501,14 @@
Ok(())
}
+ pub fn get_variable_metadata(collection: &CollectionHandle<T>, item_id: TokenId) -> Result<Vec<u8>, DispatchError> {
+ Ok(match collection.mode {
+ CollectionMode::NFT => <NftItemList<T>>::get(collection.id, item_id).ok_or(Error::<T>::TokenNotFound)?.variable_data,
+ CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection.id, item_id).ok_or(Error::<T>::TokenNotFound)?.variable_data,
+ _ => fail!(Error::<T>::UnexpectedCollectionType),
+ })
+ }
+
pub fn create_multiple_items_internal(
sender: &T::CrossAccountId,
collection: &CollectionHandle<T>,
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -3,6 +3,12 @@
pragma solidity >=0.8.0 <0.9.0;
+// Anonymous struct
+struct Tuple0 {
+ uint256 field_0;
+ string field_1;
+}
+
// Common stubs holder
interface Dummy {
@@ -119,6 +125,25 @@
function transfer(address to, uint256 tokenId) external;
function nextTokenId() external view returns (uint256);
+
+ // Selector: setVariableMetadata(uint256,bytes) d4eac26d
+ function setVariableMetadata(uint256 tokenId, bytes memory data) external;
+
+ // Selector: getVariableMetadata(uint256) e6c5ce6f
+ function getVariableMetadata(uint256 tokenId)
+ external
+ view
+ returns (bytes memory);
+
+ // Selector: mintBulk(address,uint256[]) 44a9945e
+ function mintBulk(address to, uint256[] memory tokenIds)
+ external
+ returns (bool);
+
+ // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
+ function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
+ external
+ returns (bool);
}
interface UniqueNFT is