difftreelog
refactor move token address mapping to trait
in: master
6 files changed
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -19,18 +19,12 @@
// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
// TODO: Unhardcode prefix
-const ETH_ACCOUNT_PREFIX: [u8; 16] = [
+const ETH_COLLECTION_PREFIX: [u8; 16] = [
0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,
];
-// 0xf8238ccfff8ed887463fd5e00000000100000002 - collection 1, token 2
-// TODO: Unhardcode prefix
-const ETH_ACCOUNT_TOKEN_PREFIX: [u8; 12] = [
- 0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,
-];
-
pub fn map_eth_to_id(eth: &H160) -> Option<CollectionId> {
- if eth[0..16] != ETH_ACCOUNT_PREFIX {
+ if eth[0..16] != ETH_COLLECTION_PREFIX {
return None;
}
let mut id_bytes = [0; 4];
@@ -39,28 +33,7 @@
}
pub fn collection_id_to_address(id: CollectionId) -> H160 {
let mut out = [0; 20];
- out[0..16].copy_from_slice(Ð_ACCOUNT_PREFIX);
+ out[0..16].copy_from_slice(Ð_COLLECTION_PREFIX);
out[16..20].copy_from_slice(&u32::to_be_bytes(id.0));
- H160(out)
-}
-
-pub fn map_eth_to_token_id(eth: &H160) -> Option<(CollectionId, TokenId)> {
- if eth[0..12] != ETH_ACCOUNT_TOKEN_PREFIX {
- return None;
- }
- let mut id_bytes = [0; 4];
- let mut token_id_bytes = [0; 4];
- id_bytes.copy_from_slice(ð[12..16]);
- token_id_bytes.copy_from_slice(ð[16..20]);
- Some((
- CollectionId(u32::from_be_bytes(id_bytes)),
- TokenId(u32::from_be_bytes(token_id_bytes)),
- ))
-}
-pub fn collection_token_id_to_address(id: CollectionId, token: TokenId) -> H160 {
- let mut out = [0; 20];
- out[0..12].copy_from_slice(Ð_ACCOUNT_TOKEN_PREFIX);
- out[12..16].copy_from_slice(&u32::to_be_bytes(id.0));
- out[16..20].copy_from_slice(&u32::to_be_bytes(token.0));
H160(out)
}
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,33 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId, CollectionStats, MAX_TOKEN_OWNERSHIP,34 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,36 CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41#[cfg(feature = "runtime-benchmarks")]42pub mod benchmarking;43pub mod dispatch;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use pallet_evm::account;167 use dispatch::CollectionDispatch;168 use frame_support::traits::Currency;169 use up_data_structs::TokenId;170 use scale_info::TypeInfo;171 use up_evm_mapping::CrossAccountId;172173 #[pallet::config]174 pub trait Config:175 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config176 {177 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;178179 type Currency: Currency<Self::AccountId>;180181 #[pallet::constant]182 type CollectionCreationPrice: Get<183 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,184 >;185 type CollectionDispatch: CollectionDispatch<Self>;186187 type TreasuryAccountId: Get<Self::AccountId>;188 }189190 #[pallet::pallet]191 #[pallet::generate_store(pub(super) trait Store)]192 pub struct Pallet<T>(_);193194 #[pallet::extra_constants]195 impl<T: Config> Pallet<T> {196 pub fn collection_admins_limit() -> u32 {197 COLLECTION_ADMINS_LIMIT198 }199 }200201 #[pallet::event]202 #[pallet::generate_deposit(pub fn deposit_event)]203 pub enum Event<T: Config> {204 /// New collection was created205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique identifier of newly created collection.209 ///210 /// * mode: [CollectionMode] converted into u8.211 ///212 /// * account_id: Collection owner.213 CollectionCreated(CollectionId, u8, T::AccountId),214215 /// New collection was destroyed216 ///217 /// # Arguments218 ///219 /// * collection_id: Globally unique identifier of collection.220 CollectionDestroyed(CollectionId),221222 /// New item was created.223 ///224 /// # Arguments225 ///226 /// * collection_id: Id of the collection where item was created.227 ///228 /// * item_id: Id of an item. Unique within the collection.229 ///230 /// * recipient: Owner of newly created item231 ///232 /// * amount: Always 1 for NFT233 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),234235 /// Collection item was burned.236 ///237 /// # Arguments238 ///239 /// * collection_id.240 ///241 /// * item_id: Identifier of burned NFT.242 ///243 /// * owner: which user has destroyed its tokens244 ///245 /// * amount: Always 1 for NFT246 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),247248 /// Item was transferred249 ///250 /// * collection_id: Id of collection to which item is belong251 ///252 /// * item_id: Id of an item253 ///254 /// * sender: Original owner of item255 ///256 /// * recipient: New owner of item257 ///258 /// * amount: Always 1 for NFT259 Transfer(260 CollectionId,261 TokenId,262 T::CrossAccountId,263 T::CrossAccountId,264 u128,265 ),266267 /// * collection_id268 ///269 /// * item_id270 ///271 /// * sender272 ///273 /// * spender274 ///275 /// * amount276 Approved(277 CollectionId,278 TokenId,279 T::CrossAccountId,280 T::CrossAccountId,281 u128,282 ),283 }284285 #[pallet::error]286 pub enum Error<T> {287 /// This collection does not exist.288 CollectionNotFound,289 /// Sender parameter and item owner must be equal.290 MustBeTokenOwner,291 /// No permission to perform action292 NoPermission,293 /// Collection is not in mint mode.294 PublicMintingNotAllowed,295 /// Address is not in allow list.296 AddressNotInAllowlist,297298 /// Collection name can not be longer than 63 char.299 CollectionNameLimitExceeded,300 /// Collection description can not be longer than 255 char.301 CollectionDescriptionLimitExceeded,302 /// Token prefix can not be longer than 15 char.303 CollectionTokenPrefixLimitExceeded,304 /// Total collections bound exceeded.305 TotalCollectionsLimitExceeded,306 /// variable_data exceeded data limit.307 TokenVariableDataLimitExceeded,308 /// Exceeded max admin count309 CollectionAdminCountExceeded,310 /// Collection limit bounds per collection exceeded311 CollectionLimitBoundsExceeded,312 /// Tried to enable permissions which are only permitted to be disabled313 OwnerPermissionsCantBeReverted,314315 /// Collection settings not allowing items transferring316 TransferNotAllowed,317 /// Account token limit exceeded per collection318 AccountTokenLimitExceeded,319 /// Collection token limit exceeded320 CollectionTokenLimitExceeded,321 /// Metadata flag frozen322 MetadataFlagFrozen,323324 /// Item not exists.325 TokenNotFound,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Requested value more than approved.329 ApprovedValueTooLow,330 /// Tried to approve more than owned331 CantApproveMoreThanOwned,332333 /// Can't transfer tokens to ethereum zero address334 AddressIsZero,335 /// Target collection doesn't supports this operation336 UnsupportedOperation,337338 /// Not sufficient founds to perform action339 NotSufficientFounds,340 }341342 #[pallet::storage]343 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;344 #[pallet::storage]345 pub type DestroyedCollectionCount<T> =346 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;347348 /// Collection info349 #[pallet::storage]350 pub type CollectionById<T> = StorageMap<351 Hasher = Blake2_128Concat,352 Key = CollectionId,353 Value = Collection<<T as frame_system::Config>::AccountId>,354 QueryKind = OptionQuery,355 >;356357 #[pallet::storage]358 pub type AdminAmount<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = u32,362 QueryKind = ValueQuery,363 >;364365 /// List of collection admins366 #[pallet::storage]367 pub type IsAdmin<T: Config> = StorageNMap<368 Key = (369 Key<Blake2_128Concat, CollectionId>,370 Key<Blake2_128Concat, T::CrossAccountId>,371 ),372 Value = bool,373 QueryKind = ValueQuery,374 >;375376 /// Allowlisted collection users377 #[pallet::storage]378 pub type Allowlist<T: Config> = StorageNMap<379 Key = (380 Key<Blake2_128Concat, CollectionId>,381 Key<Blake2_128Concat, T::CrossAccountId>,382 ),383 Value = bool,384 QueryKind = ValueQuery,385 >;386387 /// Not used by code, exists only to provide some types to metadata388 #[pallet::storage]389 pub type DummyStorageValue<T> =390 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;391}392393impl<T: Config> Pallet<T> {394 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens395 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {396 ensure!(397 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,398 <Error<T>>::AddressIsZero399 );400 Ok(())401 }402 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {403 <IsAdmin<T>>::iter_prefix((collection,))404 .map(|(a, _)| a)405 .collect()406 }407 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <Allowlist<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {413 <Allowlist<T>>::get((collection, user))414 }415 pub fn collection_stats() -> CollectionStats {416 let created = <CreatedCollectionCount<T>>::get();417 let destroyed = <DestroyedCollectionCount<T>>::get();418 CollectionStats {419 created: created.0,420 destroyed: destroyed.0,421 alive: created.0 - destroyed.0,422 }423 }424425 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {426 let collection = <CollectionById<T>>::get(collection);427 if collection.is_none() {428 return None;429 }430431 let collection = collection.unwrap();432 let limits = collection.limits;433 let effective_limits = CollectionLimits {434 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),435 sponsored_data_size: Some(limits.sponsored_data_size()),436 sponsored_data_rate_limit: Some(437 limits438 .sponsored_data_rate_limit439 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),440 ),441 token_limit: Some(limits.token_limit()),442 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(443 match collection.mode {444 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,445 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,446 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,447 },448 )),449 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),450 owner_can_transfer: Some(limits.owner_can_transfer()),451 owner_can_destroy: Some(limits.owner_can_destroy()),452 transfers_enabled: Some(limits.transfers_enabled()),453 };454455 Some(effective_limits)456 }457}458459impl<T: Config> Pallet<T> {460 pub fn init_collection(461 owner: T::AccountId,462 data: CreateCollectionData<T::AccountId>,463 ) -> Result<CollectionId, DispatchError> {464 {465 ensure!(466 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,467 Error::<T>::CollectionTokenPrefixLimitExceeded468 );469 }470471 let created_count = <CreatedCollectionCount<T>>::get()472 .0473 .checked_add(1)474 .ok_or(ArithmeticError::Overflow)?;475 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;476 let id = CollectionId(created_count);477478 // bound Total number of collections479 ensure!(480 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,481 <Error<T>>::TotalCollectionsLimitExceeded482 );483484 // =========485486 let collection = Collection {487 owner: owner.clone(),488 name: data.name,489 mode: data.mode.clone(),490 mint_mode: false,491 access: data.access.unwrap_or_default(),492 description: data.description,493 token_prefix: data.token_prefix,494 offchain_schema: data.offchain_schema,495 schema_version: data.schema_version.unwrap_or_default(),496 sponsorship: data497 .pending_sponsor498 .map(SponsorshipState::Unconfirmed)499 .unwrap_or_default(),500 variable_on_chain_schema: data.variable_on_chain_schema,501 const_on_chain_schema: data.const_on_chain_schema,502 limits: data503 .limits504 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))505 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,506 meta_update_permission: data.meta_update_permission.unwrap_or_default(),507 };508509 // Take a (non-refundable) deposit of collection creation510 {511 let mut imbalance =512 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();513 imbalance.subsume(514 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(515 &T::TreasuryAccountId::get(),516 T::CollectionCreationPrice::get(),517 ),518 );519 <T as Config>::Currency::settle(520 &owner,521 imbalance,522 WithdrawReasons::TRANSFER,523 ExistenceRequirement::KeepAlive,524 )525 .map_err(|_| Error::<T>::NotSufficientFounds)?;526 }527528 <CreatedCollectionCount<T>>::put(created_count);529 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));530 <CollectionById<T>>::insert(id, collection);531 Ok(id)532 }533534 pub fn destroy_collection(535 collection: CollectionHandle<T>,536 sender: &T::CrossAccountId,537 ) -> DispatchResult {538 ensure!(539 collection.limits.owner_can_destroy(),540 <Error<T>>::NoPermission,541 );542 collection.check_is_owner(sender)?;543544 let destroyed_collections = <DestroyedCollectionCount<T>>::get()545 .0546 .checked_add(1)547 .ok_or(ArithmeticError::Overflow)?;548549 // =========550551 <DestroyedCollectionCount<T>>::put(destroyed_collections);552 <CollectionById<T>>::remove(collection.id);553 <AdminAmount<T>>::remove(collection.id);554 <IsAdmin<T>>::remove_prefix((collection.id,), None);555 <Allowlist<T>>::remove_prefix((collection.id,), None);556557 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));558 Ok(())559 }560561 pub fn toggle_allowlist(562 collection: &CollectionHandle<T>,563 sender: &T::CrossAccountId,564 user: &T::CrossAccountId,565 allowed: bool,566 ) -> DispatchResult {567 collection.check_is_owner_or_admin(sender)?;568569 // =========570571 if allowed {572 <Allowlist<T>>::insert((collection.id, user), true);573 } else {574 <Allowlist<T>>::remove((collection.id, user));575 }576577 Ok(())578 }579580 pub fn toggle_admin(581 collection: &CollectionHandle<T>,582 sender: &T::CrossAccountId,583 user: &T::CrossAccountId,584 admin: bool,585 ) -> DispatchResult {586 collection.check_is_owner_or_admin(sender)?;587588 let was_admin = <IsAdmin<T>>::get((collection.id, user));589 if was_admin == admin {590 return Ok(());591 }592 let amount = <AdminAmount<T>>::get(collection.id);593594 if admin {595 let amount = amount596 .checked_add(1)597 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;598 ensure!(599 amount <= Self::collection_admins_limit(),600 <Error<T>>::CollectionAdminCountExceeded,601 );602603 // =========604605 <AdminAmount<T>>::insert(collection.id, amount);606 <IsAdmin<T>>::insert((collection.id, user), true);607 } else {608 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));609 <IsAdmin<T>>::remove((collection.id, user));610 }611612 Ok(())613 }614615 pub fn clamp_limits(616 mode: CollectionMode,617 old_limit: &CollectionLimits,618 mut new_limit: CollectionLimits,619 ) -> Result<CollectionLimits, DispatchError> {620 macro_rules! limit_default {621 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{622 $(623 if let Some($new) = $new.$field {624 let $old = $old.$field($($arg)?);625 let _ = $new;626 let _ = $old;627 $check628 } else {629 $new.$field = $old.$field630 }631 )*632 }};633 }634635 limit_default!(old_limit, new_limit,636 account_token_ownership_limit => ensure!(637 new_limit <= MAX_TOKEN_OWNERSHIP,638 <Error<T>>::CollectionLimitBoundsExceeded,639 ),640 sponsor_transfer_timeout(match mode {641 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,642 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,643 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,644 }) => ensure!(645 new_limit <= MAX_SPONSOR_TIMEOUT,646 <Error<T>>::CollectionLimitBoundsExceeded,647 ),648 sponsored_data_size => ensure!(649 new_limit <= CUSTOM_DATA_LIMIT,650 <Error<T>>::CollectionLimitBoundsExceeded,651 ),652 token_limit => ensure!(653 old_limit >= new_limit && new_limit > 0,654 <Error<T>>::CollectionTokenLimitExceeded655 ),656 owner_can_transfer => ensure!(657 old_limit || !new_limit,658 <Error<T>>::OwnerPermissionsCantBeReverted,659 ),660 owner_can_destroy => ensure!(661 old_limit || !new_limit,662 <Error<T>>::OwnerPermissionsCantBeReverted,663 ),664 sponsored_data_rate_limit => {},665 transfers_enabled => {},666 );667 Ok(new_limit)668 }669}670671#[macro_export]672macro_rules! unsupported {673 () => {674 Err(<Error<T>>::UnsupportedOperation.into())675 };676}677678/// Worst cases679pub trait CommonWeightInfo<CrossAccountId> {680 fn create_item() -> Weight;681 fn create_multiple_items(amount: u32) -> Weight;682 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;683 fn burn_item() -> Weight;684 fn transfer() -> Weight;685 fn approve() -> Weight;686 fn transfer_from() -> Weight;687 fn burn_from() -> Weight;688 fn set_variable_metadata(bytes: u32) -> Weight;689}690691pub trait CommonCollectionOperations<T: Config> {692 fn create_item(693 &self,694 sender: T::CrossAccountId,695 to: T::CrossAccountId,696 data: CreateItemData,697 ) -> DispatchResultWithPostInfo;698 fn create_multiple_items(699 &self,700 sender: T::CrossAccountId,701 to: T::CrossAccountId,702 data: Vec<CreateItemData>,703 ) -> DispatchResultWithPostInfo;704 fn create_multiple_items_ex(705 &self,706 sender: T::CrossAccountId,707 data: CreateItemExData<T::CrossAccountId>,708 ) -> DispatchResultWithPostInfo;709 fn burn_item(710 &self,711 sender: T::CrossAccountId,712 token: TokenId,713 amount: u128,714 ) -> DispatchResultWithPostInfo;715716 fn transfer(717 &self,718 sender: T::CrossAccountId,719 to: T::CrossAccountId,720 token: TokenId,721 amount: u128,722 ) -> DispatchResultWithPostInfo;723 fn approve(724 &self,725 sender: T::CrossAccountId,726 spender: T::CrossAccountId,727 token: TokenId,728 amount: u128,729 ) -> DispatchResultWithPostInfo;730 fn transfer_from(731 &self,732 sender: T::CrossAccountId,733 from: T::CrossAccountId,734 to: T::CrossAccountId,735 token: TokenId,736 amount: u128,737 ) -> DispatchResultWithPostInfo;738 fn burn_from(739 &self,740 sender: T::CrossAccountId,741 from: T::CrossAccountId,742 token: TokenId,743 amount: u128,744 ) -> DispatchResultWithPostInfo;745746 fn set_variable_metadata(747 &self,748 sender: T::CrossAccountId,749 token: TokenId,750 data: BoundedVec<u8, CustomDataLimit>,751 ) -> DispatchResultWithPostInfo;752753 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;754 fn token_exists(&self, token: TokenId) -> bool;755 fn last_token_id(&self) -> TokenId;756757 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;758 fn const_metadata(&self, token: TokenId) -> Vec<u8>;759 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;760761 /// How many tokens collection contains (Applicable to nonfungible/refungible)762 fn collection_tokens(&self) -> u32;763 /// Amount of different tokens account has (Applicable to nonfungible/refungible)764 fn account_balance(&self, account: T::CrossAccountId) -> u32;765 /// Amount of specific token account have (Applicable to fungible/refungible)766 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;767 fn allowance(768 &self,769 sender: T::CrossAccountId,770 spender: T::CrossAccountId,771 token: TokenId,772 ) -> u128;773}774775// Flexible enough for implementing CommonCollectionOperations776pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {777 let post_info = PostDispatchInfo {778 actual_weight: Some(weight),779 pays_fee: Pays::Yes,780 };781 match res {782 Ok(()) => Ok(post_info),783 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),784 }785}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,33 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId, CollectionStats, MAX_TOKEN_OWNERSHIP,34 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,36 CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41#[cfg(feature = "runtime-benchmarks")]42pub mod benchmarking;43pub mod dispatch;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use pallet_evm::account;167 use dispatch::CollectionDispatch;168 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};169 use frame_system::pallet_prelude::*;170 use frame_support::traits::Currency;171 use up_data_structs::{TokenId, mapping::TokenAddressMapping};172 use scale_info::TypeInfo;173 use up_evm_mapping::CrossAccountId;174175 #[pallet::config]176 pub trait Config:177 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config178 {179 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;180181 type Currency: Currency<Self::AccountId>;182183 #[pallet::constant]184 type CollectionCreationPrice: Get<185 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,186 >;187 type CollectionDispatch: CollectionDispatch<Self>;188189 type TreasuryAccountId: Get<Self::AccountId>;190191 type EvmTokenAddressMapping: TokenAddressMapping<H160>;192 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;193 }194195 #[pallet::pallet]196 #[pallet::generate_store(pub(super) trait Store)]197 pub struct Pallet<T>(_);198199 #[pallet::extra_constants]200 impl<T: Config> Pallet<T> {201 pub fn collection_admins_limit() -> u32 {202 COLLECTION_ADMINS_LIMIT203 }204 }205206 #[pallet::event]207 #[pallet::generate_deposit(pub fn deposit_event)]208 pub enum Event<T: Config> {209 /// New collection was created210 ///211 /// # Arguments212 ///213 /// * collection_id: Globally unique identifier of newly created collection.214 ///215 /// * mode: [CollectionMode] converted into u8.216 ///217 /// * account_id: Collection owner.218 CollectionCreated(CollectionId, u8, T::AccountId),219220 /// New collection was destroyed221 ///222 /// # Arguments223 ///224 /// * collection_id: Globally unique identifier of collection.225 CollectionDestroyed(CollectionId),226227 /// New item was created.228 ///229 /// # Arguments230 ///231 /// * collection_id: Id of the collection where item was created.232 ///233 /// * item_id: Id of an item. Unique within the collection.234 ///235 /// * recipient: Owner of newly created item236 ///237 /// * amount: Always 1 for NFT238 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),239240 /// Collection item was burned.241 ///242 /// # Arguments243 ///244 /// * collection_id.245 ///246 /// * item_id: Identifier of burned NFT.247 ///248 /// * owner: which user has destroyed its tokens249 ///250 /// * amount: Always 1 for NFT251 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),252253 /// Item was transferred254 ///255 /// * collection_id: Id of collection to which item is belong256 ///257 /// * item_id: Id of an item258 ///259 /// * sender: Original owner of item260 ///261 /// * recipient: New owner of item262 ///263 /// * amount: Always 1 for NFT264 Transfer(265 CollectionId,266 TokenId,267 T::CrossAccountId,268 T::CrossAccountId,269 u128,270 ),271272 /// * collection_id273 ///274 /// * item_id275 ///276 /// * sender277 ///278 /// * spender279 ///280 /// * amount281 Approved(282 CollectionId,283 TokenId,284 T::CrossAccountId,285 T::CrossAccountId,286 u128,287 ),288 }289290 #[pallet::error]291 pub enum Error<T> {292 /// This collection does not exist.293 CollectionNotFound,294 /// Sender parameter and item owner must be equal.295 MustBeTokenOwner,296 /// No permission to perform action297 NoPermission,298 /// Collection is not in mint mode.299 PublicMintingNotAllowed,300 /// Address is not in allow list.301 AddressNotInAllowlist,302303 /// Collection name can not be longer than 63 char.304 CollectionNameLimitExceeded,305 /// Collection description can not be longer than 255 char.306 CollectionDescriptionLimitExceeded,307 /// Token prefix can not be longer than 15 char.308 CollectionTokenPrefixLimitExceeded,309 /// Total collections bound exceeded.310 TotalCollectionsLimitExceeded,311 /// variable_data exceeded data limit.312 TokenVariableDataLimitExceeded,313 /// Exceeded max admin count314 CollectionAdminCountExceeded,315 /// Collection limit bounds per collection exceeded316 CollectionLimitBoundsExceeded,317 /// Tried to enable permissions which are only permitted to be disabled318 OwnerPermissionsCantBeReverted,319320 /// Collection settings not allowing items transferring321 TransferNotAllowed,322 /// Account token limit exceeded per collection323 AccountTokenLimitExceeded,324 /// Collection token limit exceeded325 CollectionTokenLimitExceeded,326 /// Metadata flag frozen327 MetadataFlagFrozen,328329 /// Item not exists.330 TokenNotFound,331 /// Item balance not enough.332 TokenValueTooLow,333 /// Requested value more than approved.334 ApprovedValueTooLow,335 /// Tried to approve more than owned336 CantApproveMoreThanOwned,337338 /// Can't transfer tokens to ethereum zero address339 AddressIsZero,340 /// Target collection doesn't supports this operation341 UnsupportedOperation,342343 /// Not sufficient founds to perform action344 NotSufficientFounds,345 }346347 #[pallet::storage]348 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;349 #[pallet::storage]350 pub type DestroyedCollectionCount<T> =351 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;352353 /// Collection info354 #[pallet::storage]355 pub type CollectionById<T> = StorageMap<356 Hasher = Blake2_128Concat,357 Key = CollectionId,358 Value = Collection<<T as frame_system::Config>::AccountId>,359 QueryKind = OptionQuery,360 >;361362 #[pallet::storage]363 pub type AdminAmount<T> = StorageMap<364 Hasher = Blake2_128Concat,365 Key = CollectionId,366 Value = u32,367 QueryKind = ValueQuery,368 >;369370 /// List of collection admins371 #[pallet::storage]372 pub type IsAdmin<T: Config> = StorageNMap<373 Key = (374 Key<Blake2_128Concat, CollectionId>,375 Key<Blake2_128Concat, T::CrossAccountId>,376 ),377 Value = bool,378 QueryKind = ValueQuery,379 >;380381 /// Allowlisted collection users382 #[pallet::storage]383 pub type Allowlist<T: Config> = StorageNMap<384 Key = (385 Key<Blake2_128Concat, CollectionId>,386 Key<Blake2_128Concat, T::CrossAccountId>,387 ),388 Value = bool,389 QueryKind = ValueQuery,390 >;391392 /// Not used by code, exists only to provide some types to metadata393 #[pallet::storage]394 pub type DummyStorageValue<T> =395 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;396}397398impl<T: Config> Pallet<T> {399 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens400 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {401 ensure!(402 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,403 <Error<T>>::AddressIsZero404 );405 Ok(())406 }407 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <IsAdmin<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {413 <Allowlist<T>>::iter_prefix((collection,))414 .map(|(a, _)| a)415 .collect()416 }417 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {418 <Allowlist<T>>::get((collection, user))419 }420 pub fn collection_stats() -> CollectionStats {421 let created = <CreatedCollectionCount<T>>::get();422 let destroyed = <DestroyedCollectionCount<T>>::get();423 CollectionStats {424 created: created.0,425 destroyed: destroyed.0,426 alive: created.0 - destroyed.0,427 }428 }429430 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {431 let collection = <CollectionById<T>>::get(collection);432 if collection.is_none() {433 return None;434 }435436 let collection = collection.unwrap();437 let limits = collection.limits;438 let effective_limits = CollectionLimits {439 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),440 sponsored_data_size: Some(limits.sponsored_data_size()),441 sponsored_data_rate_limit: Some(442 limits443 .sponsored_data_rate_limit444 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),445 ),446 token_limit: Some(limits.token_limit()),447 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(448 match collection.mode {449 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,450 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,451 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,452 },453 )),454 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),455 owner_can_transfer: Some(limits.owner_can_transfer()),456 owner_can_destroy: Some(limits.owner_can_destroy()),457 transfers_enabled: Some(limits.transfers_enabled()),458 };459460 Some(effective_limits)461 }462}463464impl<T: Config> Pallet<T> {465 pub fn init_collection(466 owner: T::AccountId,467 data: CreateCollectionData<T::AccountId>,468 ) -> Result<CollectionId, DispatchError> {469 {470 ensure!(471 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,472 Error::<T>::CollectionTokenPrefixLimitExceeded473 );474 }475476 let created_count = <CreatedCollectionCount<T>>::get()477 .0478 .checked_add(1)479 .ok_or(ArithmeticError::Overflow)?;480 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;481 let id = CollectionId(created_count);482483 // bound Total number of collections484 ensure!(485 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,486 <Error<T>>::TotalCollectionsLimitExceeded487 );488489 // =========490491 let collection = Collection {492 owner: owner.clone(),493 name: data.name,494 mode: data.mode.clone(),495 mint_mode: false,496 access: data.access.unwrap_or_default(),497 description: data.description,498 token_prefix: data.token_prefix,499 offchain_schema: data.offchain_schema,500 schema_version: data.schema_version.unwrap_or_default(),501 sponsorship: data502 .pending_sponsor503 .map(SponsorshipState::Unconfirmed)504 .unwrap_or_default(),505 variable_on_chain_schema: data.variable_on_chain_schema,506 const_on_chain_schema: data.const_on_chain_schema,507 limits: data508 .limits509 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))510 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,511 meta_update_permission: data.meta_update_permission.unwrap_or_default(),512 };513514 // Take a (non-refundable) deposit of collection creation515 {516 let mut imbalance =517 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();518 imbalance.subsume(519 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(520 &T::TreasuryAccountId::get(),521 T::CollectionCreationPrice::get(),522 ),523 );524 <T as Config>::Currency::settle(525 &owner,526 imbalance,527 WithdrawReasons::TRANSFER,528 ExistenceRequirement::KeepAlive,529 )530 .map_err(|_| Error::<T>::NotSufficientFounds)?;531 }532533 <CreatedCollectionCount<T>>::put(created_count);534 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));535 <CollectionById<T>>::insert(id, collection);536 Ok(id)537 }538539 pub fn destroy_collection(540 collection: CollectionHandle<T>,541 sender: &T::CrossAccountId,542 ) -> DispatchResult {543 ensure!(544 collection.limits.owner_can_destroy(),545 <Error<T>>::NoPermission,546 );547 collection.check_is_owner(sender)?;548549 let destroyed_collections = <DestroyedCollectionCount<T>>::get()550 .0551 .checked_add(1)552 .ok_or(ArithmeticError::Overflow)?;553554 // =========555556 <DestroyedCollectionCount<T>>::put(destroyed_collections);557 <CollectionById<T>>::remove(collection.id);558 <AdminAmount<T>>::remove(collection.id);559 <IsAdmin<T>>::remove_prefix((collection.id,), None);560 <Allowlist<T>>::remove_prefix((collection.id,), None);561562 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));563 Ok(())564 }565566 pub fn toggle_allowlist(567 collection: &CollectionHandle<T>,568 sender: &T::CrossAccountId,569 user: &T::CrossAccountId,570 allowed: bool,571 ) -> DispatchResult {572 collection.check_is_owner_or_admin(sender)?;573574 // =========575576 if allowed {577 <Allowlist<T>>::insert((collection.id, user), true);578 } else {579 <Allowlist<T>>::remove((collection.id, user));580 }581582 Ok(())583 }584585 pub fn toggle_admin(586 collection: &CollectionHandle<T>,587 sender: &T::CrossAccountId,588 user: &T::CrossAccountId,589 admin: bool,590 ) -> DispatchResult {591 collection.check_is_owner_or_admin(sender)?;592593 let was_admin = <IsAdmin<T>>::get((collection.id, user));594 if was_admin == admin {595 return Ok(());596 }597 let amount = <AdminAmount<T>>::get(collection.id);598599 if admin {600 let amount = amount601 .checked_add(1)602 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;603 ensure!(604 amount <= Self::collection_admins_limit(),605 <Error<T>>::CollectionAdminCountExceeded,606 );607608 // =========609610 <AdminAmount<T>>::insert(collection.id, amount);611 <IsAdmin<T>>::insert((collection.id, user), true);612 } else {613 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));614 <IsAdmin<T>>::remove((collection.id, user));615 }616617 Ok(())618 }619620 pub fn clamp_limits(621 mode: CollectionMode,622 old_limit: &CollectionLimits,623 mut new_limit: CollectionLimits,624 ) -> Result<CollectionLimits, DispatchError> {625 macro_rules! limit_default {626 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{627 $(628 if let Some($new) = $new.$field {629 let $old = $old.$field($($arg)?);630 let _ = $new;631 let _ = $old;632 $check633 } else {634 $new.$field = $old.$field635 }636 )*637 }};638 }639640 limit_default!(old_limit, new_limit,641 account_token_ownership_limit => ensure!(642 new_limit <= MAX_TOKEN_OWNERSHIP,643 <Error<T>>::CollectionLimitBoundsExceeded,644 ),645 sponsor_transfer_timeout(match mode {646 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,647 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,648 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,649 }) => ensure!(650 new_limit <= MAX_SPONSOR_TIMEOUT,651 <Error<T>>::CollectionLimitBoundsExceeded,652 ),653 sponsored_data_size => ensure!(654 new_limit <= CUSTOM_DATA_LIMIT,655 <Error<T>>::CollectionLimitBoundsExceeded,656 ),657 token_limit => ensure!(658 old_limit >= new_limit && new_limit > 0,659 <Error<T>>::CollectionTokenLimitExceeded660 ),661 owner_can_transfer => ensure!(662 old_limit || !new_limit,663 <Error<T>>::OwnerPermissionsCantBeReverted,664 ),665 owner_can_destroy => ensure!(666 old_limit || !new_limit,667 <Error<T>>::OwnerPermissionsCantBeReverted,668 ),669 sponsored_data_rate_limit => {},670 transfers_enabled => {},671 );672 Ok(new_limit)673 }674}675676#[macro_export]677macro_rules! unsupported {678 () => {679 Err(<Error<T>>::UnsupportedOperation.into())680 };681}682683/// Worst cases684pub trait CommonWeightInfo<CrossAccountId> {685 fn create_item() -> Weight;686 fn create_multiple_items(amount: u32) -> Weight;687 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;688 fn burn_item() -> Weight;689 fn transfer() -> Weight;690 fn approve() -> Weight;691 fn transfer_from() -> Weight;692 fn burn_from() -> Weight;693 fn set_variable_metadata(bytes: u32) -> Weight;694}695696pub trait CommonCollectionOperations<T: Config> {697 fn create_item(698 &self,699 sender: T::CrossAccountId,700 to: T::CrossAccountId,701 data: CreateItemData,702 ) -> DispatchResultWithPostInfo;703 fn create_multiple_items(704 &self,705 sender: T::CrossAccountId,706 to: T::CrossAccountId,707 data: Vec<CreateItemData>,708 ) -> DispatchResultWithPostInfo;709 fn create_multiple_items_ex(710 &self,711 sender: T::CrossAccountId,712 data: CreateItemExData<T::CrossAccountId>,713 ) -> DispatchResultWithPostInfo;714 fn burn_item(715 &self,716 sender: T::CrossAccountId,717 token: TokenId,718 amount: u128,719 ) -> DispatchResultWithPostInfo;720721 fn transfer(722 &self,723 sender: T::CrossAccountId,724 to: T::CrossAccountId,725 token: TokenId,726 amount: u128,727 ) -> DispatchResultWithPostInfo;728 fn approve(729 &self,730 sender: T::CrossAccountId,731 spender: T::CrossAccountId,732 token: TokenId,733 amount: u128,734 ) -> DispatchResultWithPostInfo;735 fn transfer_from(736 &self,737 sender: T::CrossAccountId,738 from: T::CrossAccountId,739 to: T::CrossAccountId,740 token: TokenId,741 amount: u128,742 ) -> DispatchResultWithPostInfo;743 fn burn_from(744 &self,745 sender: T::CrossAccountId,746 from: T::CrossAccountId,747 token: TokenId,748 amount: u128,749 ) -> DispatchResultWithPostInfo;750751 fn set_variable_metadata(752 &self,753 sender: T::CrossAccountId,754 token: TokenId,755 data: BoundedVec<u8, CustomDataLimit>,756 ) -> DispatchResultWithPostInfo;757758 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;759 fn token_exists(&self, token: TokenId) -> bool;760 fn last_token_id(&self) -> TokenId;761762 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;763 fn const_metadata(&self, token: TokenId) -> Vec<u8>;764 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;765766 /// How many tokens collection contains (Applicable to nonfungible/refungible)767 fn collection_tokens(&self) -> u32;768 /// Amount of different tokens account has (Applicable to nonfungible/refungible)769 fn account_balance(&self, account: T::CrossAccountId) -> u32;770 /// Amount of specific token account have (Applicable to fungible/refungible)771 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;772 fn allowance(773 &self,774 sender: T::CrossAccountId,775 spender: T::CrossAccountId,776 token: TokenId,777 ) -> u128;778}779780// Flexible enough for implementing CommonCollectionOperations781pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {782 let post_info = PostDispatchInfo {783 actual_weight: Some(weight),784 pays_fee: Pays::Yes,785 };786 match res {787 Ok(()) => Ok(post_info),788 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),789 }790}primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -20,34 +20,23 @@
convert::{TryFrom, TryInto},
fmt,
};
-use frame_support::storage::bounded_btree_map::BoundedBTreeMap;
-use sp_std::collections::btree_map::BTreeMap;
+use frame_support::{
+ storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},
+ traits::ConstU16,
+};
+use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
#[cfg(feature = "serde")]
-pub use serde::{Serialize, Deserialize};
+use serde::{Serialize, Deserialize};
use sp_core::U256;
use sp_runtime::{ArithmeticError, sp_std::prelude::Vec};
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
-pub use frame_support::{
- BoundedVec, construct_runtime, decl_event, decl_module, decl_storage, decl_error,
- dispatch::DispatchResult,
- ensure, fail, parameter_types,
- traits::{
- Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,
- Randomness, IsSubType, WithdrawReasons,
- },
- weights::{
- constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
- DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,
- WeightToFeePolynomial, DispatchClass,
- },
- StorageValue, transactional,
- pallet_prelude::ConstU32,
-};
+use frame_support::{BoundedVec, traits::ConstU32};
use derivative::Derivative;
use scale_info::TypeInfo;
+pub mod mapping;
mod migration;
pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;
primitives/data-structs/src/mapping.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/data-structs/src/mapping.rs
@@ -0,0 +1,63 @@
+use core::marker::PhantomData;
+
+use sp_core::H160;
+
+use crate::{CollectionId, TokenId};
+use up_evm_mapping::CrossAccountId;
+
+pub trait TokenAddressMapping<Address> {
+ fn token_to_address(collection: CollectionId, token: TokenId) -> Address;
+ fn address_to_token(address: &Address) -> Option<(CollectionId, TokenId)>;
+ fn is_token_address(address: &Address) -> bool;
+}
+
+pub struct EvmTokenAddressMapping;
+
+/// 0xf8238ccfff8ed887463fd5e00000000100000002 - collection 1, token 2
+const ETH_COLLECTION_TOKEN_PREFIX: [u8; 12] = [
+ 0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,
+];
+
+impl TokenAddressMapping<H160> for EvmTokenAddressMapping {
+ fn token_to_address(collection: CollectionId, token: TokenId) -> H160 {
+ let mut out = [0; 20];
+ out[0..12].copy_from_slice(Ð_COLLECTION_TOKEN_PREFIX);
+ out[12..16].copy_from_slice(&u32::to_be_bytes(collection.0));
+ out[16..20].copy_from_slice(&u32::to_be_bytes(token.0));
+ H160(out)
+ }
+
+ fn address_to_token(eth: &H160) -> Option<(CollectionId, TokenId)> {
+ if eth[0..12] != ETH_COLLECTION_TOKEN_PREFIX {
+ return None;
+ }
+ let mut id_bytes = [0; 4];
+ let mut token_id_bytes = [0; 4];
+ id_bytes.copy_from_slice(ð[12..16]);
+ token_id_bytes.copy_from_slice(ð[16..20]);
+ Some((
+ CollectionId(u32::from_be_bytes(id_bytes)),
+ TokenId(u32::from_be_bytes(token_id_bytes)),
+ ))
+ }
+
+ fn is_token_address(address: &H160) -> bool {
+ address[0..12] == ETH_COLLECTION_TOKEN_PREFIX
+ }
+}
+
+pub struct CrossTokenAddressMapping<A>(PhantomData<A>);
+
+impl<A, C: CrossAccountId<A>> TokenAddressMapping<C> for CrossTokenAddressMapping<A> {
+ fn token_to_address(collection: CollectionId, token: TokenId) -> C {
+ C::from_eth(EvmTokenAddressMapping::token_to_address(collection, token))
+ }
+
+ fn address_to_token(address: &C) -> Option<(CollectionId, TokenId)> {
+ EvmTokenAddressMapping::address_to_token(address.as_eth())
+ }
+
+ fn is_token_address(address: &C) -> bool {
+ EvmTokenAddressMapping::is_token_address(address.as_eth())
+ }
+}
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -66,6 +66,7 @@
WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier,
},
};
+use up_data_structs::mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping};
use up_data_structs::{CollectionId, TokenId, CollectionStats, Collection};
// use pallet_contracts::weights::WeightInfo;
// #[cfg(any(feature = "std", test))]
tests/src/eth/util/helpers.tsdiffbeforeafterboth--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -56,13 +56,27 @@
}
}
-export function collectionIdToAddress(address: number): string {
- if (address >= 0xffffffff || address < 0) throw new Error('id overflow');
+function encodeIntBE(v: number): number[] {
+ if (v >= 0xffffffff || v < 0) throw new Error('id overflow');
+ return [
+ v >> 24,
+ (v >> 16) & 0xff,
+ (v >> 8) & 0xff,
+ v & 0xff,
+ ];
+}
+
+export function collectionIdToAddress(collection: number): string {
const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,
- address >> 24,
- (address >> 16) & 0xff,
- (address >> 8) & 0xff,
- address & 0xff,
+ ...encodeIntBE(collection),
+ ]);
+ return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));
+}
+
+export function tokenIdToAddress(collection: number, token: number): string {
+ const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,
+ ...encodeIntBE(collection),
+ ...encodeIntBE(token),
]);
return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));
}