1234567891011121314151617extern crate alloc;18use core::{19 char::{REPLACEMENT_CHARACTER, decode_utf16},20 convert::TryInto,21};22use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};23use frame_support::BoundedVec;24use up_data_structs::{25 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,26 CollectionPropertiesVec,27};28use pallet_evm_coder_substrate::dispatch_to_evm;29use sp_std::vec::Vec;30use pallet_common::{31 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, token_uri_key},32 CollectionHandle, CollectionPropertyPermissions,33};34use pallet_evm::{account::CrossAccountId, PrecompileHandle};35use pallet_evm_coder_substrate::call;36use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};3738use crate::{39 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,40 SelfWeightOf, weights::WeightInfo, TokenProperties, property_guard::PropertyGuard,41};4243#[solidity_interface(name = "TokenProperties")]44impl<T: Config> NonfungibleHandle<T> {45 fn set_token_property_permission(46 &mut self,47 caller: caller,48 key: string,49 is_mutable: bool,50 collection_admin: bool,51 token_owner: bool,52 ) -> Result<()> {53 let caller = T::CrossAccountId::from_eth(caller);54 <Pallet<T>>::set_property_permission(55 self,56 &caller,57 PropertyKeyPermission {58 key: <Vec<u8>>::from(key)59 .try_into()60 .map_err(|_| "too long key")?,61 permission: PropertyPermission {62 mutable: is_mutable,63 collection_admin,64 token_owner,65 },66 },67 )68 .map_err(dispatch_to_evm::<T>)69 }7071 fn set_property(72 &mut self,73 caller: caller,74 token_id: uint256,75 key: string,76 value: bytes,77 ) -> Result<()> {78 let caller = T::CrossAccountId::from_eth(caller);79 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;80 let key = <Vec<u8>>::from(key)81 .try_into()82 .map_err(|_| "key too long")?;83 let value = value.try_into().map_err(|_| "value too long")?;8485 let is_token_create = false;86 let budget = self87 .recorder88 .weight_calls_budget(<StructureWeight<T>>::find_parent());8990 let mut guard =91 PropertyGuard::new(&caller, self, TokenId(token_id), is_token_create, &budget);9293 <Pallet<T>>::set_token_property(Property { key, value }, &mut guard)94 .map_err(dispatch_to_evm::<T>)95 }9697 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {98 let caller = T::CrossAccountId::from_eth(caller);99 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;100 let key = <Vec<u8>>::from(key)101 .try_into()102 .map_err(|_| "key too long")?;103104 let is_token_create = false;105 let budget = self106 .recorder107 .weight_calls_budget(<StructureWeight<T>>::find_parent());108109 let mut guard =110 PropertyGuard::new(&caller, self, TokenId(token_id), is_token_create, &budget);111112 <Pallet<T>>::delete_token_property(key, &mut guard).map_err(dispatch_to_evm::<T>)113 }114115 116 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {117 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;118 let key = <Vec<u8>>::from(key)119 .try_into()120 .map_err(|_| "key too long")?;121122 let props = <TokenProperties<T>>::get((self.id, token_id));123 let prop = props.get(&key).ok_or("key not found")?;124125 Ok(prop.to_vec())126 }127}128129#[derive(ToLog)]130pub enum ERC721Events {131 Transfer {132 #[indexed]133 from: address,134 #[indexed]135 to: address,136 #[indexed]137 token_id: uint256,138 },139 Approval {140 #[indexed]141 owner: address,142 #[indexed]143 approved: address,144 #[indexed]145 token_id: uint256,146 },147 #[allow(dead_code)]148 ApprovalForAll {149 #[indexed]150 owner: address,151 #[indexed]152 operator: address,153 approved: bool,154 },155}156157#[derive(ToLog)]158pub enum ERC721MintableEvents {159 #[allow(dead_code)]160 MintingFinished {},161}162163#[solidity_interface(name = "ERC721Metadata")]164impl<T: Config> NonfungibleHandle<T> {165 fn name(&self) -> Result<string> {166 Ok(decode_utf16(self.name.iter().copied())167 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))168 .collect::<string>())169 }170171 fn symbol(&self) -> Result<string> {172 Ok(string::from_utf8_lossy(&self.token_prefix).into())173 }174175 176 #[solidity(rename_selector = "tokenURI")]177 fn token_uri(&self, token_id: uint256) -> Result<string> {178 let key = token_uri_key();179 if !has_token_permission::<T>(self.id, &key) {180 return Err("No tokenURI permission".into());181 }182183 self.consume_store_reads(1)?;184 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;185186 let properties = <TokenProperties<T>>::try_get((self.id, token_id))187 .map_err(|_| Error::Revert("Token properties not found".into()))?;188 if let Some(property) = properties.get(&key) {189 return Ok(string::from_utf8_lossy(property).into());190 }191192 Err("Property tokenURI not found".into())193 }194}195196#[solidity_interface(name = "ERC721Enumerable")]197impl<T: Config> NonfungibleHandle<T> {198 fn token_by_index(&self, index: uint256) -> Result<uint256> {199 Ok(index)200 }201202 203 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {204 205 Err("not implemented".into())206 }207208 fn total_supply(&self) -> Result<uint256> {209 self.consume_store_reads(1)?;210 Ok(<Pallet<T>>::total_supply(self).into())211 }212}213214#[solidity_interface(name = "ERC721", events(ERC721Events))]215impl<T: Config> NonfungibleHandle<T> {216 fn balance_of(&self, owner: address) -> Result<uint256> {217 self.consume_store_reads(1)?;218 let owner = T::CrossAccountId::from_eth(owner);219 let balance = <AccountBalance<T>>::get((self.id, owner));220 Ok(balance.into())221 }222 fn owner_of(&self, token_id: uint256) -> Result<address> {223 self.consume_store_reads(1)?;224 let token: TokenId = token_id.try_into()?;225 Ok(*<TokenData<T>>::get((self.id, token))226 .ok_or("token not found")?227 .owner228 .as_eth())229 }230 231 fn safe_transfer_from_with_data(232 &mut self,233 _from: address,234 _to: address,235 _token_id: uint256,236 _data: bytes,237 _value: value,238 ) -> Result<void> {239 240 Err("not implemented".into())241 }242 243 fn safe_transfer_from(244 &mut self,245 _from: address,246 _to: address,247 _token_id: uint256,248 _value: value,249 ) -> Result<void> {250 251 Err("not implemented".into())252 }253254 #[weight(<SelfWeightOf<T>>::transfer_from())]255 fn transfer_from(256 &mut self,257 caller: caller,258 from: address,259 to: address,260 token_id: uint256,261 _value: value,262 ) -> Result<void> {263 let caller = T::CrossAccountId::from_eth(caller);264 let from = T::CrossAccountId::from_eth(from);265 let to = T::CrossAccountId::from_eth(to);266 let token = token_id.try_into()?;267 let budget = self268 .recorder269 .weight_calls_budget(<StructureWeight<T>>::find_parent());270271 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)272 .map_err(dispatch_to_evm::<T>)?;273 Ok(())274 }275276 #[weight(<SelfWeightOf<T>>::approve())]277 fn approve(278 &mut self,279 caller: caller,280 approved: address,281 token_id: uint256,282 _value: value,283 ) -> Result<void> {284 let caller = T::CrossAccountId::from_eth(caller);285 let approved = T::CrossAccountId::from_eth(approved);286 let token = token_id.try_into()?;287288 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))289 .map_err(dispatch_to_evm::<T>)?;290 Ok(())291 }292293 294 fn set_approval_for_all(295 &mut self,296 _caller: caller,297 _operator: address,298 _approved: bool,299 ) -> Result<void> {300 301 Err("not implemented".into())302 }303304 305 fn get_approved(&self, _token_id: uint256) -> Result<address> {306 307 Err("not implemented".into())308 }309310 311 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {312 313 Err("not implemented".into())314 }315}316317#[solidity_interface(name = "ERC721Burnable")]318impl<T: Config> NonfungibleHandle<T> {319 #[weight(<SelfWeightOf<T>>::burn_item())]320 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {321 let caller = T::CrossAccountId::from_eth(caller);322 let token = token_id.try_into()?;323324 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;325 Ok(())326 }327}328329#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]330impl<T: Config> NonfungibleHandle<T> {331 fn minting_finished(&self) -> Result<bool> {332 Ok(false)333 }334335 336 337 #[weight(<SelfWeightOf<T>>::create_item())]338 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {339 let caller = T::CrossAccountId::from_eth(caller);340 let to = T::CrossAccountId::from_eth(to);341 let token_id: u32 = token_id.try_into()?;342 let budget = self343 .recorder344 .weight_calls_budget(<StructureWeight<T>>::find_parent());345346 if <TokensMinted<T>>::get(self.id)347 .checked_add(1)348 .ok_or("item id overflow")?349 != token_id350 {351 return Err("item id should be next".into());352 }353354 <Pallet<T>>::create_item(355 self,356 &caller,357 CreateItemData::<T> {358 properties: BoundedVec::default(),359 owner: to,360 },361 &budget,362 )363 .map_err(dispatch_to_evm::<T>)?;364365 Ok(true)366 }367368 369 370 #[solidity(rename_selector = "mintWithTokenURI")]371 #[weight(<SelfWeightOf<T>>::create_item())]372 fn mint_with_token_uri(373 &mut self,374 caller: caller,375 to: address,376 token_id: uint256,377 token_uri: string,378 ) -> Result<bool> {379 let key = token_uri_key();380 let permission = get_token_permission::<T>(self.id, &key)?;381 if !permission.collection_admin {382 return Err("Operation is not allowed".into());383 }384385 let caller = T::CrossAccountId::from_eth(caller);386 let to = T::CrossAccountId::from_eth(to);387 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;388 let budget = self389 .recorder390 .weight_calls_budget(<StructureWeight<T>>::find_parent());391392 if <TokensMinted<T>>::get(self.id)393 .checked_add(1)394 .ok_or("item id overflow")?395 != token_id396 {397 return Err("item id should be next".into());398 }399400 let mut properties = CollectionPropertiesVec::default();401 properties402 .try_push(Property {403 key,404 value: token_uri405 .into_bytes()406 .try_into()407 .map_err(|_| "token uri is too long")?,408 })409 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;410411 <Pallet<T>>::create_item(412 self,413 &caller,414 CreateItemData::<T> {415 properties,416 owner: to,417 },418 &budget,419 )420 .map_err(dispatch_to_evm::<T>)?;421 Ok(true)422 }423424 425 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {426 Err("not implementable".into())427 }428}429430fn get_token_permission<T: Config>(431 collection_id: CollectionId,432 key: &PropertyKey,433) -> Result<PropertyPermission> {434 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)435 .map_err(|_| Error::Revert("No permissions for collection".into()))?;436 let a = token_property_permissions437 .get(key)438 .map(|p| p.clone())439 .ok_or_else(|| Error::Revert("No permission".into()))?;440 Ok(a)441}442443fn has_token_permission<T: Config>(collection_id: CollectionId, key: &PropertyKey) -> bool {444 if let Ok(token_property_permissions) =445 CollectionPropertyPermissions::<T>::try_get(collection_id)446 {447 return token_property_permissions.contains_key(key);448 }449450 false451}452453#[solidity_interface(name = "ERC721UniqueExtensions")]454impl<T: Config> NonfungibleHandle<T> {455 #[weight(<SelfWeightOf<T>>::transfer())]456 fn transfer(457 &mut self,458 caller: caller,459 to: address,460 token_id: uint256,461 _value: value,462 ) -> Result<void> {463 let caller = T::CrossAccountId::from_eth(caller);464 let to = T::CrossAccountId::from_eth(to);465 let token = token_id.try_into()?;466 let budget = self467 .recorder468 .weight_calls_budget(<StructureWeight<T>>::find_parent());469470 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;471 Ok(())472 }473474 #[weight(<SelfWeightOf<T>>::burn_from())]475 fn burn_from(476 &mut self,477 caller: caller,478 from: address,479 token_id: uint256,480 _value: value,481 ) -> Result<void> {482 let caller = T::CrossAccountId::from_eth(caller);483 let from = T::CrossAccountId::from_eth(from);484 let token = token_id.try_into()?;485 let budget = self486 .recorder487 .weight_calls_budget(<StructureWeight<T>>::find_parent());488489 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)490 .map_err(dispatch_to_evm::<T>)?;491 Ok(())492 }493494 fn next_token_id(&self) -> Result<uint256> {495 self.consume_store_reads(1)?;496 Ok(<TokensMinted<T>>::get(self.id)497 .checked_add(1)498 .ok_or("item id overflow")?499 .into())500 }501502 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]503 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {504 let caller = T::CrossAccountId::from_eth(caller);505 let to = T::CrossAccountId::from_eth(to);506 let mut expected_index = <TokensMinted<T>>::get(self.id)507 .checked_add(1)508 .ok_or("item id overflow")?;509 let budget = self510 .recorder511 .weight_calls_budget(<StructureWeight<T>>::find_parent());512513 let total_tokens = token_ids.len();514 for id in token_ids.into_iter() {515 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;516 if id != expected_index {517 return Err("item id should be next".into());518 }519 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;520 }521 let data = (0..total_tokens)522 .map(|_| CreateItemData::<T> {523 properties: BoundedVec::default(),524 owner: to.clone(),525 })526 .collect();527528 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)529 .map_err(dispatch_to_evm::<T>)?;530 Ok(true)531 }532533 #[solidity(rename_selector = "mintBulkWithTokenURI")]534 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]535 fn mint_bulk_with_token_uri(536 &mut self,537 caller: caller,538 to: address,539 tokens: Vec<(uint256, string)>,540 ) -> Result<bool> {541 let key = token_uri_key();542 let caller = T::CrossAccountId::from_eth(caller);543 let to = T::CrossAccountId::from_eth(to);544 let mut expected_index = <TokensMinted<T>>::get(self.id)545 .checked_add(1)546 .ok_or("item id overflow")?;547 let budget = self548 .recorder549 .weight_calls_budget(<StructureWeight<T>>::find_parent());550551 let mut data = Vec::with_capacity(tokens.len());552 for (id, token_uri) in tokens {553 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;554 if id != expected_index {555 return Err("item id should be next".into());556 }557 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;558559 let mut properties = CollectionPropertiesVec::default();560 properties561 .try_push(Property {562 key: key.clone(),563 value: token_uri564 .into_bytes()565 .try_into()566 .map_err(|_| "token uri is too long")?,567 })568 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;569570 data.push(CreateItemData::<T> {571 properties,572 owner: to.clone(),573 });574 }575576 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)577 .map_err(dispatch_to_evm::<T>)?;578 Ok(true)579 }580}581582#[solidity_interface(583 name = "UniqueNFT",584 is(585 ERC721,586 ERC721Metadata,587 ERC721Enumerable,588 ERC721UniqueExtensions,589 ERC721Mintable,590 ERC721Burnable,591 via("CollectionHandle<T>", common_mut, Collection),592 TokenProperties,593 )594)]595impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> {}596597598generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);599generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);600601impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>602where603 T::AccountId: From<[u8; 32]>,604{605 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");606607 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {608 call::<T, UniqueNFTCall<T>, _, _>(handle, self)609 }610}