difftreelog
refactor(nonfungible) merge TokenData & Owner
in: master
3 files changed
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -190,13 +190,19 @@
}
fn token_owner(&self, token: TokenId) -> T::CrossAccountId {
- <Owner<T>>::get((self.id, token))
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.owner)
+ .unwrap_or_default()
}
fn const_metadata(&self, token: TokenId) -> Vec<u8> {
- <TokenData<T>>::get((self.id, token, DataKind::Constant))
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.const_data.clone())
+ .unwrap_or_default()
}
fn variable_metadata(&self, token: TokenId) -> Vec<u8> {
- <TokenData<T>>::get((self.id, token, DataKind::Variable))
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.variable_data.clone())
+ .unwrap_or_default()
}
fn collection_tokens(&self) -> u32 {
@@ -208,7 +214,10 @@
}
fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {
- if <Owner<T>>::get((self.id, token)) == account {
+ if <TokenData<T>>::get((self.id, token))
+ .map(|a| a.owner == account)
+ .unwrap_or(false)
+ {
1
} else {
0
@@ -221,7 +230,10 @@
spender: T::CrossAccountId,
token: TokenId,
) -> u128 {
- if <Owner<T>>::get((self.id, token)) != sender {
+ if <TokenData<T>>::get((self.id, token))
+ .map(|a| a.owner == sender)
+ .unwrap_or(false)
+ {
0
} else if <Allowance<T>>::get((self.id, token)) == Some(spender) {
1
pallets/nonfungible/src/erc.rsdiffbeforeafterboth1use 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, DataKind, NonfungibleHandle, Owner, Pallet, TokenData,17 TokensMinted,18};1920#[derive(ToLog)]21pub enum ERC721Events {22 Transfer {23 #[indexed]24 from: address,25 #[indexed]26 to: address,27 #[indexed]28 token_id: uint256,29 },30 Approval {31 #[indexed]32 owner: address,33 #[indexed]34 approved: address,35 #[indexed]36 token_id: uint256,37 },38 #[allow(dead_code)]39 ApprovalForAll {40 #[indexed]41 owner: address,42 #[indexed]43 operator: address,44 approved: bool,45 },46}4748#[derive(ToLog)]49pub enum ERC721MintableEvents {50 #[allow(dead_code)]51 MintingFinished {},52}5354#[solidity_interface(name = "ERC721Metadata")]55impl<T: Config> NonfungibleHandle<T> {56 fn name(&self) -> Result<string> {57 Ok(decode_utf16(self.name.iter().copied())58 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))59 .collect::<string>())60 }61 fn symbol(&self) -> Result<string> {62 Ok(string::from_utf8_lossy(&self.token_prefix).into())63 }6465 #[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(&<TokenData<T>>::get((69 self.id,70 token_id,71 DataKind::Constant,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 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {84 // TODO: Not implemetable85 Err("not implemented".into())86 }8788 fn total_supply(&self) -> Result<uint256> {89 Ok(<Pallet<T>>::total_supply(self).into())90 }91}9293#[solidity_interface(name = "ERC721", events(ERC721Events))]94impl<T: Config> NonfungibleHandle<T> {95 fn balance_of(&self, owner: address) -> Result<uint256> {96 let owner = T::CrossAccountId::from_eth(owner);97 let balance = <AccountBalance<T>>::get((self.id, owner.as_sub()));98 Ok(balance.into())99 }100 fn owner_of(&self, token_id: uint256) -> Result<address> {101 let token: TokenId = token_id.try_into()?;102 Ok(*<Owner<T>>::get((self.id, token)).as_eth())103 }104 fn safe_transfer_from_with_data(105 &mut self,106 _from: address,107 _to: address,108 _token_id: uint256,109 _data: bytes,110 _value: value,111 ) -> Result<void> {112 // TODO: Not implemetable113 Err("not implemented".into())114 }115 fn safe_transfer_from(116 &mut self,117 _from: address,118 _to: address,119 _token_id: uint256,120 _value: value,121 ) -> Result<void> {122 // TODO: Not implemetable123 Err("not implemented".into())124 }125126 fn transfer_from(127 &mut self,128 caller: caller,129 from: address,130 to: address,131 token_id: uint256,132 _value: value,133 ) -> Result<void> {134 let caller = T::CrossAccountId::from_eth(caller);135 let from = T::CrossAccountId::from_eth(from);136 let to = T::CrossAccountId::from_eth(to);137 let token = token_id.try_into()?;138139 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token)140 .map_err(dispatch_to_evm::<T>)?;141 Ok(())142 }143144 fn approve(145 &mut self,146 caller: caller,147 approved: address,148 token_id: uint256,149 _value: value,150 ) -> Result<void> {151 let caller = T::CrossAccountId::from_eth(caller);152 let approved = T::CrossAccountId::from_eth(approved);153 let token = token_id.try_into()?;154155 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))156 .map_err(dispatch_to_evm::<T>)?;157 Ok(())158 }159160 fn set_approval_for_all(161 &mut self,162 _caller: caller,163 _operator: address,164 _approved: bool,165 ) -> Result<void> {166 // TODO: Not implemetable167 Err("not implemented".into())168 }169170 fn get_approved(&self, _token_id: uint256) -> Result<address> {171 // TODO: Not implemetable172 Err("not implemented".into())173 }174175 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {176 // TODO: Not implemetable177 Err("not implemented".into())178 }179}180181#[solidity_interface(name = "ERC721Burnable")]182impl<T: Config> NonfungibleHandle<T> {183 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {184 let caller = T::CrossAccountId::from_eth(caller);185 let token = token_id.try_into()?;186187 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;188 Ok(())189 }190}191192#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]193impl<T: Config> NonfungibleHandle<T> {194 fn minting_finished(&self) -> Result<bool> {195 Ok(false)196 }197198 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {199 let caller = T::CrossAccountId::from_eth(caller);200 let to = T::CrossAccountId::from_eth(to);201 let token_id: u32 = token_id.try_into()?;202 if <TokensMinted<T>>::get(self.id)203 .checked_add(1)204 .ok_or("item id overflow")?205 != token_id206 {207 return Err("item id should be next".into());208 }209210 <Pallet<T>>::create_item(211 self,212 &caller,213 CreateItemData {214 const_data: BoundedVec::default(),215 variable_data: BoundedVec::default(),216 owner: to,217 },218 )219 .map_err(dispatch_to_evm::<T>)?;220221 Ok(true)222 }223224 #[solidity(rename_selector = "mintWithTokenURI")]225 fn mint_with_token_uri(226 &mut self,227 caller: caller,228 to: address,229 token_id: uint256,230 token_uri: string,231 ) -> Result<bool> {232 let caller = T::CrossAccountId::from_eth(caller);233 let to = T::CrossAccountId::from_eth(to);234 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;235 if <TokensMinted<T>>::get(self.id)236 .checked_add(1)237 .ok_or("item id overflow")?238 != token_id239 {240 return Err("item id should be next".into());241 }242243 <Pallet<T>>::create_item(244 self,245 &caller,246 CreateItemData {247 const_data: Vec::<u8>::from(token_uri)248 .try_into()249 .map_err(|_| "token uri is too long")?,250 variable_data: BoundedVec::default(),251 owner: to,252 },253 )254 .map_err(dispatch_to_evm::<T>)?;255 Ok(true)256 }257258 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {259 Err("not implementable".into())260 }261}262263#[solidity_interface(name = "ERC721UniqueExtensions")]264impl<T: Config> NonfungibleHandle<T> {265 #[solidity(rename_selector = "transfer")]266 fn transfer_nft(267 &mut self,268 caller: caller,269 to: address,270 token_id: uint256,271 _value: value,272 ) -> Result<void> {273 let caller = T::CrossAccountId::from_eth(caller);274 let to = T::CrossAccountId::from_eth(to);275 let token = token_id.try_into()?;276277 <Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;278 Ok(())279 }280281 fn next_token_id(&self) -> Result<uint256> {282 Ok(<TokensMinted<T>>::get(self.id)283 .checked_add(1)284 .ok_or("item id overflow")?285 .into())286 }287288 fn set_variable_metadata(289 &mut self,290 caller: caller,291 token_id: uint256,292 data: bytes,293 ) -> Result<void> {294 let caller = T::CrossAccountId::from_eth(caller);295 let token = token_id.try_into()?;296297 <Pallet<T>>::set_variable_metadata(self, &caller, token, data)298 .map_err(dispatch_to_evm::<T>)?;299 Ok(())300 }301302 fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {303 let token: TokenId = token_id.try_into()?;304305 Ok(<TokenData<T>>::get((self.id, token, DataKind::Variable)))306 }307308 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {309 let caller = T::CrossAccountId::from_eth(caller);310 let to = T::CrossAccountId::from_eth(to);311 let mut expected_index = <TokensMinted<T>>::get(self.id)312 .checked_add(1)313 .ok_or("item id overflow")?;314315 let total_tokens = token_ids.len();316 for id in token_ids.into_iter() {317 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;318 if id != expected_index {319 return Err("item id should be next".into());320 }321 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;322 }323 let data = (0..total_tokens)324 .map(|_| CreateItemData {325 const_data: BoundedVec::default(),326 variable_data: BoundedVec::default(),327 owner: to.clone(),328 })329 .collect();330331 <Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;332 Ok(true)333 }334335 #[solidity(rename_selector = "mintBulkWithTokenURI")]336 fn mint_bulk_with_token_uri(337 &mut self,338 caller: caller,339 to: address,340 tokens: Vec<(uint256, string)>,341 ) -> Result<bool> {342 let caller = T::CrossAccountId::from_eth(caller);343 let to = T::CrossAccountId::from_eth(to);344 let mut expected_index = <TokensMinted<T>>::get(self.id)345 .checked_add(1)346 .ok_or("item id overflow")?;347348 let mut data = Vec::with_capacity(tokens.len());349 for (id, token_uri) in tokens {350 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;351 if id != expected_index {352 panic!("item id should be next ({}) but got {}", expected_index, id);353 }354 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;355356 data.push(CreateItemData {357 const_data: Vec::<u8>::from(token_uri)358 .try_into()359 .map_err(|_| "token uri is too long")?,360 variable_data: vec![].try_into().unwrap(),361 owner: to.clone(),362 });363 }364365 <Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;366 Ok(true)367 }368}369370#[solidity_interface(371 name = "UniqueNFT",372 is(373 ERC721,374 ERC721Metadata,375 ERC721Enumerable,376 ERC721UniqueExtensions,377 ERC721Mintable,378 ERC721Burnable,379 )380)]381impl<T: Config> NonfungibleHandle<T> {}382383// Not a tests, but code generators384generate_stubgen!(gen_impl, UniqueNFTCall, true);385generate_stubgen!(gen_iface, UniqueNFTCall, false);386387pub const CODE: &[u8] = include_bytes!("./stubs/UniqueNFT.raw");388389impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {390 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");391392 fn call(mut self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileOutput> {393 let result = call_internal::<UniqueNFTCall, _>(*source, &mut self, value, input);394 self.0.recorder.evm_to_precompile_output(result)395 }396}pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -13,6 +13,7 @@
use sp_std::{vec::Vec, vec};
use core::ops::Deref;
use sp_std::collections::btree_map::BTreeMap;
+use codec::{Encode, Decode};
pub use pallet::*;
pub mod benchmarking;
@@ -27,10 +28,17 @@
}
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
+#[derive(Encode, Decode)]
+pub struct ItemData<T: Config> {
+ pub const_data: Vec<u8>,
+ pub variable_data: Vec<u8>,
+ pub owner: T::CrossAccountId,
+}
+
#[frame_support::pallet]
pub mod pallet {
+ use super::*;
use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};
- use sp_std::vec::Vec;
use nft_data_structs::{CollectionId, TokenId};
use super::weights::WeightInfo;
@@ -58,29 +66,13 @@
pub(super) type TokensBurnt<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
- #[derive(Encode, Decode)]
- pub enum DataKind {
- Constant,
- Variable,
- }
-
#[pallet::storage]
pub(super) type TokenData<T: Config> = StorageNMap<
- Key = (
- Key<Twox64Concat, CollectionId>,
- Key<Twox64Concat, TokenId>,
- Key<Identity, DataKind>,
- ),
- Value = Vec<u8>,
- QueryKind = ValueQuery,
+ Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
+ Value = ItemData<T>,
+ QueryKind = OptionQuery,
>;
- #[pallet::storage]
- pub(super) type Owner<T: Config> = StorageNMap<
- Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
- Value = T::CrossAccountId,
- QueryKind = ValueQuery,
- >;
/// Used to enumerate tokens owned by account
#[pallet::storage]
pub(super) type Owned<T: Config> = StorageNMap<
@@ -133,29 +125,7 @@
<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)
}
pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {
- <Owner<T>>::contains_key((collection.id, token))
- }
- pub fn ensure_owner(
- collection: &NonfungibleHandle<T>,
- token: TokenId,
- sender: &T::CrossAccountId,
- ) -> DispatchResult {
- ensure!(
- &<Owner<T>>::get((collection.id, token)) == sender,
- <CommonError<T>>::NoPermission
- );
- Ok(())
- }
- pub fn item_owner(
- collection: &NonfungibleHandle<T>,
- token: TokenId,
- ) -> Result<T::CrossAccountId, DispatchError> {
- let owner = <Owner<T>>::get((collection.id, token));
- ensure!(
- owner != T::CrossAccountId::default(),
- <CommonError<T>>::TokenNotFound
- );
- Ok(owner)
+ <TokenData<T>>::contains_key((collection.id, token))
}
}
@@ -174,11 +144,10 @@
PalletCommon::destroy_collection(collection.0, sender)?;
- <Owner<T>>::remove_prefix((id,), None);
+ <TokenData<T>>::remove_prefix((id,), None);
<Owned<T>>::remove_prefix((id,), None);
<TokensMinted<T>>::remove(id);
<TokensBurnt<T>>::remove(id);
- <TokenData<T>>::remove_prefix((id,), None);
<Allowance<T>>::remove_prefix((id,), None);
<AccountBalance<T>>::remove_prefix((id,), None);
Ok(())
@@ -189,9 +158,10 @@
sender: &T::CrossAccountId,
token: TokenId,
) -> DispatchResult {
- let token_owner = <Pallet<T>>::item_owner(collection, token)?;
+ let token_data = <TokenData<T>>::get((collection.id, token))
+ .ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
ensure!(
- &token_owner == sender
+ &token_data.owner == sender
|| (collection.limits.owner_can_transfer
&& collection.is_owner_or_admin(sender)?),
<CommonError<T>>::NoPermission
@@ -207,21 +177,30 @@
// =========
- <Owner<T>>::remove((collection.id, token));
- <Owned<T>>::remove((collection.id, token_owner.as_sub(), token));
+ <Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));
<TokensBurnt<T>>::insert(collection.id, burnt);
- <TokenData<T>>::remove_prefix((collection.id, token), None);
- <Allowance<T>>::remove((collection.id, token));
+ <TokenData<T>>::remove((collection.id, token));
+ let old_spender = <Allowance<T>>::take((collection.id, token));
+ if let Some(old_spender) = old_spender {
+ <PalletCommon<T>>::deposit_event(CommonEvent::Approved(
+ collection.id,
+ token,
+ sender.clone(),
+ old_spender.clone(),
+ 0,
+ ));
+ }
+
collection.log_infallible(ERC721Events::Transfer {
- from: *token_owner.as_eth(),
+ from: *token_data.owner.as_eth(),
to: H160::default(),
token_id: token.into(),
});
<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
collection.id,
token,
- token_owner,
+ token_data.owner,
1,
));
return Ok(());
@@ -238,9 +217,10 @@
<CommonError<T>>::TransferNotAllowed
);
- let token_owner = <Pallet<T>>::item_owner(collection, token)?;
+ let token_data = <TokenData<T>>::get((collection.id, token))
+ .ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
ensure!(
- &token_owner == from
+ &token_data.owner == from
|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),
<CommonError<T>>::NoPermission
);
@@ -274,6 +254,14 @@
// =========
+ <TokenData<T>>::insert(
+ (collection.id, token),
+ ItemData {
+ owner: to.clone(),
+ ..token_data
+ },
+ );
+
if let Some(balance_to) = balance_to {
// from != to
if balance_from == 0 {
@@ -286,7 +274,6 @@
<Owned<T>>::insert((collection.id, to.as_sub(), token), true);
}
Self::set_allowance_unchecked(collection, from, token, None);
- <Owner<T>>::insert((collection.id, token), &to);
collection.log_infallible(ERC721Events::Transfer {
from: *from.as_eth(),
@@ -364,18 +351,16 @@
<AccountBalance<T>>::insert((collection.id, account), balance);
}
for (i, data) in data.into_iter().enumerate() {
- let token = first_token + i as u32;
+ let token = first_token + i as u32 + 1;
- if !data.const_data.is_empty() {
- <TokenData<T>>::insert((collection.id, token, DataKind::Constant), data.const_data);
- }
- if !data.variable_data.is_empty() {
- <TokenData<T>>::insert(
- (collection.id, token, DataKind::Variable),
- data.variable_data,
- );
- }
- <Owner<T>>::insert((collection.id, token), &data.owner);
+ <TokenData<T>>::insert(
+ (collection.id, token),
+ ItemData {
+ const_data: data.const_data.into(),
+ variable_data: data.variable_data.into(),
+ owner: data.owner.clone(),
+ },
+ );
<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);
collection.log_infallible(ERC721Events::Transfer {
@@ -383,6 +368,12 @@
to: *data.owner.as_eth(),
token_id: token.into(),
});
+ <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
+ collection.id,
+ TokenId(token),
+ data.owner.clone(),
+ 1,
+ ));
}
Ok(())
}
@@ -462,8 +453,9 @@
if let Some(spender) = spender {
<PalletCommon<T>>::ensure_correct_receiver(spender)?;
}
- let token_owner = Self::item_owner(collection, token)?;
- if &token_owner != sender {
+ let token_data =
+ <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
+ if &token_data.owner != sender {
ensure!(
collection.ignores_owned_amount(sender)?,
<CommonError<T>>::CantApproveMoreThanOwned
@@ -515,14 +507,21 @@
data.len() as u32 <= CUSTOM_DATA_LIMIT,
<CommonError<T>>::TokenVariableDataLimitExceeded
);
- let item_owner = Self::item_owner(collection, token)?;
- collection.check_can_update_meta(sender, &item_owner)?;
+ let token_data =
+ <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
+ collection.check_can_update_meta(sender, &token_data.owner)?;
collection.consume_sstore()?;
// =========
- <TokenData<T>>::insert((collection.id, token, DataKind::Variable), data);
+ <TokenData<T>>::insert(
+ (collection.id, token),
+ ItemData {
+ variable_data: data,
+ ..token_data
+ },
+ );
Ok(())
}