--- /dev/null +++ b/pallets/common/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pallet-common" +version = "0.1.0" +edition = "2018" + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '2.0.0' + +[dependencies] +frame-support = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' } +frame-system = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' } +sp-runtime = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' } +sp-std = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' } +sp-core = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' } +nft-data-structs = { default-features = false, path = '../../primitives/nft' } +pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } +evm-coder = { default-features = false, path = '../../crates/evm-coder' } + +pallet-evm = { default-features = false, version = "6.0.0-dev", git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.10" } +serde = { version = "1.0.130", default-features = false } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "nft-data-structs/std", + "pallet-evm/std", +] +runtime-benchmarks = [] --- /dev/null +++ b/pallets/common/src/account.rs @@ -0,0 +1,142 @@ +use crate::Config; +use codec::{Encode, EncodeLike, Decode}; +use sp_core::H160; +use sp_core::crypto::AccountId32; +use core::cmp::Ordering; +use serde::{Serialize, Deserialize}; +use pallet_evm::AddressMapping; +use sp_std::vec::Vec; +use sp_std::clone::Clone; + +pub trait CrossAccountId: + Encode + EncodeLike + Decode + Clone + PartialEq + Ord + core::fmt::Debug + Default +// + +// Serialize + Deserialize<'static> +{ + fn as_sub(&self) -> &AccountId; + fn as_eth(&self) -> &H160; + + fn from_sub(account: AccountId) -> Self; + fn from_eth(account: H160) -> Self; +} + +#[derive(Eq, Serialize, Deserialize)] +pub struct BasicCrossAccountId { + /// If true - then ethereum is canonical encoding + from_ethereum: bool, + substrate: T::AccountId, + ethereum: H160, +} + +impl Default for BasicCrossAccountId { + fn default() -> Self { + Self::from_sub(T::AccountId::default()) + } +} + +impl core::fmt::Debug for BasicCrossAccountId { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + if self.from_ethereum { + fmt.debug_tuple("CrossAccountId::Ethereum") + .field(&self.ethereum) + .finish() + } else { + fmt.debug_tuple("CrossAccountId::Substrate") + .field(&self.substrate) + .finish() + } + } +} + +impl PartialOrd for BasicCrossAccountId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.substrate.cmp(&other.substrate)) + } +} + +impl Ord for BasicCrossAccountId { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other) + .expect("substrate account is total ordered") + } +} + +impl PartialEq for BasicCrossAccountId { + fn eq(&self, other: &Self) -> bool { + if self.from_ethereum == other.from_ethereum { + self.substrate == other.substrate && self.ethereum == other.ethereum + } else if self.from_ethereum { + // ethereum is canonical encoding, but we need to compare derived address + self.substrate == other.substrate + } else { + self.ethereum == other.ethereum + } + } +} +impl Clone for BasicCrossAccountId { + fn clone(&self) -> Self { + Self { + from_ethereum: self.from_ethereum, + substrate: self.substrate.clone(), + ethereum: self.ethereum, + } + } +} +impl Encode for BasicCrossAccountId { + fn encode(&self) -> Vec { + let as_result = if !self.from_ethereum { + Ok(self.substrate.clone()) + } else { + Err(self.ethereum) + }; + as_result.encode() + } +} +impl EncodeLike for BasicCrossAccountId {} +impl Decode for BasicCrossAccountId { + fn decode(input: &mut I) -> Result + where + I: codec::Input, + { + Ok(match >::decode(input)? { + Ok(s) => Self::from_sub(s), + Err(e) => Self::from_eth(e), + }) + } +} +impl CrossAccountId for BasicCrossAccountId { + fn as_sub(&self) -> &T::AccountId { + &self.substrate + } + fn as_eth(&self) -> &H160 { + &self.ethereum + } + fn from_sub(substrate: T::AccountId) -> Self { + Self { + ethereum: T::EvmBackwardsAddressMapping::from_account_id(substrate.clone()), + substrate, + from_ethereum: false, + } + } + fn from_eth(ethereum: H160) -> Self { + Self { + ethereum, + substrate: T::EvmAddressMapping::into_account_id(ethereum), + from_ethereum: true, + } + } +} + +pub trait EvmBackwardsAddressMapping { + fn from_account_id(account_id: AccountId) -> H160; +} + +/// Should have same mapping as EnsureAddressTruncated +pub struct MapBackwardsAddressTruncated; +impl EvmBackwardsAddressMapping for MapBackwardsAddressTruncated { + fn from_account_id(account_id: AccountId32) -> H160 { + let mut out = [0; 20]; + out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]); + H160(out) + } +} --- /dev/null +++ b/pallets/common/src/benchmarking.rs @@ -0,0 +1 @@ +#![cfg(feature = "runtime-benchmarking")] --- /dev/null +++ b/pallets/common/src/erc.rs @@ -0,0 +1,10 @@ +pub use pallet_evm::PrecompileOutput; +use sp_core::{H160, U256}; + +/// Does not always represent a full collection, for RFT it is either +/// collection (Implementing ERC721), or specific collection token (Implementing ERC20) +pub trait CommonEvmHandler { + const CODE: &'static [u8]; + + fn call(self, source: &H160, input: &[u8], value: U256) -> Option; +} --- /dev/null +++ b/pallets/common/src/eth.rs @@ -0,0 +1,50 @@ +use nft_data_structs::{CollectionId, TokenId}; +use sp_core::H160; + +// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1 +// TODO: Unhardcode prefix +const ETH_ACCOUNT_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 { + if eth[0..16] != ETH_ACCOUNT_PREFIX { + return None; + } + let mut id_bytes = [0; 4]; + id_bytes.copy_from_slice(ð[16..20]); + Some(CollectionId(u32::from_be_bytes(id_bytes))) +} +pub fn collection_id_to_address(id: CollectionId) -> H160 { + let mut out = [0; 20]; + out[0..16].copy_from_slice(Ð_ACCOUNT_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) +} --- /dev/null +++ b/pallets/common/src/lib.rs @@ -0,0 +1,543 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use core::ops::{Deref, DerefMut}; +use sp_std::vec::Vec; +use account::CrossAccountId; +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo}, + ensure, fail, + traits::{Imbalance, Get, Currency}, +}; +use nft_data_structs::{ + COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement, + MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH, + MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight, WithdrawReasons, +}; +pub use pallet::*; +use sp_core::H160; +use sp_runtime::{ArithmeticError, DispatchError, DispatchResult}; +pub mod account; +pub mod benchmarking; +pub mod erc; +pub mod eth; + +#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"] +pub struct CollectionHandle { + pub id: CollectionId, + collection: Collection, + pub recorder: pallet_evm_coder_substrate::SubstrateRecorder, +} +impl CollectionHandle { + pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option { + >::get(id).map(|collection| Self { + id, + collection, + recorder: pallet_evm_coder_substrate::SubstrateRecorder::new( + eth::collection_id_to_address(id), + gas_limit, + ), + }) + } + pub fn new(id: CollectionId) -> Option { + Self::new_with_gas_limit(id, u64::MAX) + } + pub fn try_get(id: CollectionId) -> Result { + Ok(Self::new(id).ok_or_else(|| >::CollectionNotFound)?) + } + pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult { + self.recorder.log_sub(log) + } + pub fn log_infallible(&self, log: impl evm_coder::ToLog) { + self.recorder.log_infallible(log) + } + #[allow(dead_code)] + fn consume_gas(&self, gas: u64) -> DispatchResult { + self.recorder.consume_gas_sub(gas) + } + pub fn consume_sload(&self) -> DispatchResult { + self.recorder.consume_sload_sub() + } + pub fn consume_sstores(&self, amount: usize) -> DispatchResult { + self.recorder.consume_sstores_sub(amount) + } + pub fn consume_sstore(&self) -> DispatchResult { + self.recorder.consume_sstore_sub() + } + pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult { + self.recorder.consume_log_sub(topics, data) + } + pub fn submit_logs(self) -> DispatchResult { + self.recorder.submit_logs() + } + pub fn save(self) -> DispatchResult { + self.recorder.submit_logs()?; + >::insert(self.id, self.collection); + Ok(()) + } +} +impl Deref for CollectionHandle { + type Target = Collection; + + fn deref(&self) -> &Self::Target { + &self.collection + } +} + +impl DerefMut for CollectionHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.collection + } +} + +impl CollectionHandle { + pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult { + ensure!(*subject.as_sub() == self.owner, >::NoPermission); + Ok(()) + } + pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result { + self.consume_sload()?; + + Ok(*subject.as_sub() == self.owner || >::get((self.id, subject.as_sub()))) + } + pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult { + ensure!(self.is_owner_or_admin(subject)?, >::NoPermission); + Ok(()) + } + pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result { + Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?) + } + pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result { + Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?) + } + pub fn check_whitelist(&self, user: &T::CrossAccountId) -> DispatchResult { + self.consume_sload()?; + + ensure!( + >::get((self.id, user.as_sub())), + >::AddressNotInWhiteList + ); + Ok(()) + } + + pub fn check_can_update_meta( + &self, + subject: &T::CrossAccountId, + item_owner: &T::CrossAccountId, + ) -> DispatchResult { + match self.meta_update_permission { + MetaUpdatePermission::ItemOwner => { + ensure!(subject == item_owner, >::NoPermission); + Ok(()) + } + MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject), + MetaUpdatePermission::None => fail!(>::NoPermission), + } + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*}; + use frame_support::{Blake2_128Concat, storage::Key}; + use account::{EvmBackwardsAddressMapping, CrossAccountId}; + use frame_support::traits::Currency; + use nft_data_structs::TokenId; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config { + type Event: IsType<::Event> + From>; + + type CrossAccountId: CrossAccountId; + + type EvmAddressMapping: pallet_evm::AddressMapping; + type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping; + + type Currency: Currency; + type CollectionCreationPrice: Get< + <::Currency as Currency>::Balance, + >; + type TreasuryAccountId: Get; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + /// New collection was created + /// + /// # Arguments + /// + /// * collection_id: Globally unique identifier of newly created collection. + /// + /// * mode: [CollectionMode] converted into u8. + /// + /// * account_id: Collection owner. + CollectionCreated(CollectionId, u8, T::AccountId), + + /// New item was created. + /// + /// # Arguments + /// + /// * collection_id: Id of the collection where item was created. + /// + /// * item_id: Id of an item. Unique within the collection. + /// + /// * recipient: Owner of newly created item + /// + /// * amount: Always 1 for NFT + ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128), + + /// Collection item was burned. + /// + /// # Arguments + /// + /// * collection_id. + /// + /// * item_id: Identifier of burned NFT. + /// + /// * owner: which user has destroyed its tokens + /// + /// * amount: Always 1 for NFT + ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128), + + /// Item was transferred + /// + /// * collection_id: Id of collection to which item is belong + /// + /// * item_id: Id of an item + /// + /// * sender: Original owner of item + /// + /// * recipient: New owner of item + /// + /// * amount: Always 1 for NFT + Transfer( + CollectionId, + TokenId, + T::CrossAccountId, + T::CrossAccountId, + u128, + ), + + /// * collection_id + /// + /// * item_id + /// + /// * sender + /// + /// * spender + /// + /// * amount + Approved( + CollectionId, + TokenId, + T::CrossAccountId, + T::CrossAccountId, + u128, + ), + } + + #[pallet::error] + pub enum Error { + /// This collection does not exist. + CollectionNotFound, + /// Sender parameter and item owner must be equal. + MustBeTokenOwner, + /// No permission to perform action + NoPermission, + /// Collection is not in mint mode. + PublicMintingNotAllowed, + /// Address is not in white list. + AddressNotInWhiteList, + + /// Collection name can not be longer than 63 char. + CollectionNameLimitExceeded, + /// Collection description can not be longer than 255 char. + CollectionDescriptionLimitExceeded, + /// Token prefix can not be longer than 15 char. + CollectionTokenPrefixLimitExceeded, + /// Total collections bound exceeded. + TotalCollectionsLimitExceeded, + /// variable_data exceeded data limit. + TokenVariableDataLimitExceeded, + + /// Collection settings not allowing items transferring + TransferNotAllowed, + /// Account token limit exceeded per collection + AccountTokenLimitExceeded, + /// Collection token limit exceeded + CollectionTokenLimitExceeded, + /// Metadata flag frozen + MetadataFlagFrozen, + + /// Item not exists. + TokenNotFound, + /// Item balance not enough. + TokenValueTooLow, + /// Requested value more than approved. + TokenValueNotEnough, + /// Tried to approve more than owned + CantApproveMoreThanOwned, + + /// Can't transfer tokens to ethereum zero address + AddressIsZero, + /// Target collection doesn't supports this operation + UnsupportedOperation, + } + + #[pallet::storage] + pub type CreatedCollectionCount = StorageValue; + #[pallet::storage] + pub type DestroyedCollectionCount = + StorageValue; + + /// Collection info + #[pallet::storage] + pub type CollectionById = StorageMap< + Hasher = Blake2_128Concat, + Key = CollectionId, + Value = Collection, + QueryKind = OptionQuery, + >; + + /// List of collection admins + #[pallet::storage] + pub type IsAdmin = StorageNMap< + Key = ( + Key, + Key, + ), + Value = bool, + QueryKind = ValueQuery, + >; + + /// Whitelisted collection users + #[pallet::storage] + pub type WhiteList = StorageNMap< + Key = ( + Key, + Key, + ), + Value = bool, + QueryKind = ValueQuery, + >; +} + +impl Pallet { + /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens + pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult { + ensure!( + &T::CrossAccountId::from_eth(H160([0; 20])) != receiver, + >::AddressIsZero + ); + Ok(()) + } +} + +impl Pallet { + pub fn init_collection(data: Collection) -> Result { + { + ensure!( + data.name.len() <= MAX_COLLECTION_NAME_LENGTH, + Error::::CollectionNameLimitExceeded + ); + ensure!( + data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH, + Error::::CollectionDescriptionLimitExceeded + ); + ensure!( + data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH, + Error::::CollectionTokenPrefixLimitExceeded + ); + } + + let created_count = >::get() + .0 + .checked_add(1) + .ok_or(ArithmeticError::Overflow)?; + let destroyed_count = >::get().0; + let id = CollectionId(created_count); + + // bound Total number of collections + ensure!( + created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, + >::TotalCollectionsLimitExceeded + ); + + // ========= + + // Take a (non-refundable) deposit of collection creation + { + let mut imbalance = + <<::Currency as Currency>::PositiveImbalance>::zero(); + imbalance.subsume( + <::Currency as Currency>::deposit_creating( + &T::TreasuryAccountId::get(), + T::CollectionCreationPrice::get(), + ), + ); + ::Currency::settle( + &data.owner, + imbalance, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| Error::::NoPermission)?; + } + + >::put(created_count); + >::deposit_event(Event::CollectionCreated( + id, + data.mode.id(), + data.owner.clone(), + )); + >::insert(id, data); + Ok(id) + } + pub fn destroy_collection( + collection: CollectionHandle, + sender: &T::CrossAccountId, + ) -> DispatchResult { + if !collection.limits.owner_can_destroy { + fail!(Error::::NoPermission); + } + collection.check_is_owner(&sender)?; + + let destroyed_collections = >::get() + .0 + .checked_add(1) + .ok_or(ArithmeticError::Overflow)?; + + // ========= + + >::put(destroyed_collections); + >::remove(collection.id); + >::remove_prefix((collection.id,), None); + >::remove_prefix((collection.id,), None); + Ok(()) + } + + pub fn toggle_whitelist( + collection: &CollectionHandle, + sender: &T::CrossAccountId, + user: &T::CrossAccountId, + allowed: bool, + ) -> DispatchResult { + collection.check_is_owner_or_admin(&sender)?; + + // ========= + + if allowed { + >::insert((collection.id, user.as_sub()), true); + } else { + >::remove((collection.id, user.as_sub())); + } + + Ok(()) + } +} + +#[macro_export] +macro_rules! unsupported { + () => { + Err(>::UnsupportedOperation.into()) + }; +} + +/// Worst cases +pub trait CommonWeightInfo { + fn create_item() -> Weight; + fn create_multiple_items(amount: u32) -> Weight; + fn burn_item() -> Weight; + fn transfer() -> Weight; + fn approve() -> Weight; + fn transfer_from() -> Weight; + fn set_variable_metadata(bytes: u32) -> Weight; +} + +pub trait CommonCollectionOperations { + fn create_item( + &self, + sender: T::CrossAccountId, + to: T::CrossAccountId, + data: CreateItemData, + ) -> DispatchResultWithPostInfo; + fn create_multiple_items( + &self, + sender: T::CrossAccountId, + to: T::CrossAccountId, + data: Vec, + ) -> DispatchResultWithPostInfo; + fn burn_item( + &self, + sender: T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> DispatchResultWithPostInfo; + + fn transfer( + &self, + sender: T::CrossAccountId, + to: T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> DispatchResultWithPostInfo; + fn approve( + &self, + sender: T::CrossAccountId, + spender: T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> DispatchResultWithPostInfo; + fn transfer_from( + &self, + sender: T::CrossAccountId, + from: T::CrossAccountId, + to: T::CrossAccountId, + token: TokenId, + amount: u128, + ) -> DispatchResultWithPostInfo; + + fn set_variable_metadata( + &self, + sender: T::CrossAccountId, + token: TokenId, + data: Vec, + ) -> DispatchResultWithPostInfo; + + fn account_tokens(&self, account: T::CrossAccountId) -> Vec; + fn token_exists(&self, token: TokenId) -> bool; + + fn token_owner(&self, token: TokenId) -> T::CrossAccountId; + fn const_metadata(&self, token: TokenId) -> Vec; + fn variable_metadata(&self, token: TokenId) -> Vec; + + /// How many tokens collection contains (Applicable to nonfungible/refungible) + fn collection_tokens(&self) -> u32; + /// Amount of different tokens account has (Applicable to nonfungible/refungible) + fn account_balance(&self, account: T::CrossAccountId) -> u32; + /// Amount of specific token account have (Applicable to fungible/refungible) + fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128; + fn allowance( + &self, + sender: T::CrossAccountId, + spender: T::CrossAccountId, + token: TokenId, + ) -> u128; +} + +// Flexible enough for implementing CommonCollectionOperations +pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo { + let post_info = PostDispatchInfo { + actual_weight: Some(weight), + pays_fee: Pays::Yes, + }; + match res { + Ok(()) => Ok(post_info), + Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }), + } +} --- a/pallets/nft/src/eth/account.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::Config; -use codec::{Encode, EncodeLike, Decode}; -use sp_core::crypto::AccountId32; -use primitive_types::H160; -use core::cmp::Ordering; -use serde::{Serialize, Deserialize}; -use pallet_evm::AddressMapping; -use sp_std::vec::Vec; -use sp_std::clone::Clone; - -pub trait CrossAccountId: - Encode + EncodeLike + Decode + Clone + PartialEq + Ord + core::fmt::Debug -// + -// Serialize + Deserialize<'static> -{ - fn as_sub(&self) -> &AccountId; - fn as_eth(&self) -> &H160; - - fn from_sub(account: AccountId) -> Self; - fn from_eth(account: H160) -> Self; -} - -#[derive(Eq, Serialize, Deserialize)] -pub struct BasicCrossAccountId { - /// If true - then ethereum is canonical encoding - from_ethereum: bool, - substrate: T::AccountId, - ethereum: H160, -} - -impl core::fmt::Debug for BasicCrossAccountId { - fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { - if self.from_ethereum { - fmt.debug_tuple("CrossAccountId::Ethereum") - .field(&self.ethereum) - .finish() - } else { - fmt.debug_tuple("CrossAccountId::Substrate") - .field(&self.substrate) - .finish() - } - } -} - -impl PartialOrd for BasicCrossAccountId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.substrate.cmp(&other.substrate)) - } -} - -impl Ord for BasicCrossAccountId { - fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other) - .expect("substrate account is total ordered") - } -} - -impl PartialEq for BasicCrossAccountId { - fn eq(&self, other: &Self) -> bool { - if self.from_ethereum == other.from_ethereum { - self.substrate == other.substrate && self.ethereum == other.ethereum - } else if self.from_ethereum { - // ethereum is canonical encoding, but we need to compare derived address - self.substrate == other.substrate - } else { - self.ethereum == other.ethereum - } - } -} -impl Clone for BasicCrossAccountId { - fn clone(&self) -> Self { - Self { - from_ethereum: self.from_ethereum, - substrate: self.substrate.clone(), - ethereum: self.ethereum, - } - } -} -impl Encode for BasicCrossAccountId { - fn encode(&self) -> Vec { - let as_result = if !self.from_ethereum { - Ok(self.substrate.clone()) - } else { - Err(self.ethereum) - }; - as_result.encode() - } -} -impl EncodeLike for BasicCrossAccountId {} -impl Decode for BasicCrossAccountId { - fn decode(input: &mut I) -> Result - where - I: codec::Input, - { - Ok(match >::decode(input)? { - Ok(s) => Self::from_sub(s), - Err(e) => Self::from_eth(e), - }) - } -} -impl CrossAccountId for BasicCrossAccountId { - fn as_sub(&self) -> &T::AccountId { - &self.substrate - } - fn as_eth(&self) -> &H160 { - &self.ethereum - } - fn from_sub(substrate: T::AccountId) -> Self { - Self { - ethereum: T::EvmBackwardsAddressMapping::from_account_id(substrate.clone()), - substrate, - from_ethereum: false, - } - } - fn from_eth(ethereum: H160) -> Self { - Self { - ethereum, - substrate: T::EvmAddressMapping::into_account_id(ethereum), - from_ethereum: true, - } - } -} - -pub trait EvmBackwardsAddressMapping { - fn from_account_id(account_id: AccountId) -> H160; -} - -/// Should have same mapping as EnsureAddressTruncated -pub struct MapBackwardsAddressTruncated; -impl EvmBackwardsAddressMapping for MapBackwardsAddressTruncated { - fn from_account_id(account_id: AccountId32) -> H160 { - let mut out = [0; 20]; - out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]); - H160(out) - } -} --- a/pallets/nft/src/eth/erc.rs +++ /dev/null @@ -1,496 +0,0 @@ -use core::char::{decode_utf16, REPLACEMENT_CHARACTER}; -use evm_coder::{ToLog, execution::Result, generate_stubgen, solidity, solidity_interface, types::*}; -use nft_data_structs::{CreateItemData, CreateNftData}; -use core::convert::TryInto; -use crate::{ - Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList, - ItemListIndex, -}; -use frame_support::storage::{StorageMap, StorageDoubleMap}; -use pallet_evm::AddressMapping; -use pallet_evm_coder_substrate::dispatch_to_evm; -use super::account::CrossAccountId; -use sp_std::{vec, vec::Vec}; - -#[solidity_interface(name = "ERC165")] -impl CollectionHandle { - fn supports_interface(&self, interface_id: bytes4) -> Result { - Ok(match self.mode { - CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id), - CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id), - _ => false, - }) - } -} - -#[solidity_interface(name = "InlineNameSymbol")] -impl CollectionHandle { - fn name(&self) -> Result { - Ok(decode_utf16(self.name.iter().copied()) - .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) - .collect::()) - } - - fn symbol(&self) -> Result { - Ok(string::from_utf8_lossy(&self.token_prefix).into()) - } -} - -#[solidity_interface(name = "InlineTotalSupply")] -impl CollectionHandle { - fn total_supply(&self) -> Result { - // TODO: we do not track total amount of all tokens - Ok(0.into()) - } -} - -#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))] -impl CollectionHandle { - #[solidity(rename_selector = "tokenURI")] - fn token_uri(&self, token_id: uint256) -> Result { - let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?; - Ok(string::from_utf8_lossy( - &>::get(self.id, token_id) - .ok_or("token not found")? - .const_data, - ) - .into()) - } -} - -#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))] -impl CollectionHandle { - fn token_by_index(&self, index: uint256) -> Result { - Ok(index) - } - - fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result { - // TODO: Not implemetable - Err("not implemented".into()) - } -} - -#[derive(ToLog)] -pub enum ERC721Events { - Transfer { - #[indexed] - from: address, - #[indexed] - to: address, - #[indexed] - token_id: uint256, - }, - Approval { - #[indexed] - owner: address, - #[indexed] - approved: address, - #[indexed] - token_id: uint256, - }, - #[allow(dead_code)] - ApprovalForAll { - #[indexed] - owner: address, - #[indexed] - operator: address, - approved: bool, - }, -} - -#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))] -impl CollectionHandle { - #[solidity(rename_selector = "balanceOf")] - fn balance_of_nft(&self, owner: address) -> Result { - let owner = T::EvmAddressMapping::into_account_id(owner); - let balance = >::get(self.id, owner); - Ok(balance.into()) - } - fn owner_of(&self, token_id: uint256) -> Result
{ - let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?; - let token = >::get(self.id, token_id).ok_or("unknown token")?; - Ok(*token.owner.as_eth()) - } - fn safe_transfer_from_with_data( - &mut self, - _from: address, - _to: address, - _token_id: uint256, - _data: bytes, - _value: value, - ) -> Result { - // TODO: Not implemetable - Err("not implemented".into()) - } - fn safe_transfer_from( - &mut self, - _from: address, - _to: address, - _token_id: uint256, - _value: value, - ) -> Result { - // TODO: Not implemetable - Err("not implemented".into()) - } - - fn transfer_from( - &mut self, - caller: caller, - from: address, - to: address, - token_id: uint256, - _value: value, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let from = T::CrossAccountId::from_eth(from); - let to = T::CrossAccountId::from_eth(to); - let token_id = token_id.try_into().map_err(|_| "token_id overflow")?; - - >::transfer_from_internal(&caller, &from, &to, self, token_id, 1) - .map_err(|_| "transferFrom error")?; - Ok(()) - } - - fn approve( - &mut self, - caller: caller, - approved: address, - token_id: uint256, - _value: value, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let approved = T::CrossAccountId::from_eth(approved); - let token_id = token_id.try_into().map_err(|_| "token_id overflow")?; - - >::approve_internal(&caller, &approved, self, token_id, 1) - .map_err(|_| "approve internal")?; - Ok(()) - } - - fn set_approval_for_all( - &mut self, - _caller: caller, - _operator: address, - _approved: bool, - ) -> Result { - // TODO: Not implemetable - Err("not implemented".into()) - } - - fn get_approved(&self, _token_id: uint256) -> Result
{ - // TODO: Not implemetable - Err("not implemented".into()) - } - - fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result
{ - // TODO: Not implemetable - Err("not implemented".into()) - } -} - -#[solidity_interface(name = "ERC721Burnable")] -impl CollectionHandle { - fn burn(&mut self, caller: caller, token_id: uint256) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let token_id = token_id.try_into().map_err(|_| "amount overflow")?; - - >::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?; - Ok(()) - } -} - -#[derive(ToLog)] -pub enum ERC721MintableEvents { - #[allow(dead_code)] - MintingFinished {}, -} - -#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))] -impl CollectionHandle { - fn minting_finished(&self) -> Result { - Ok(false) - } - - fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let to = T::CrossAccountId::from_eth(to); - let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?; - if ::get(self.id) - .checked_add(1) - .ok_or("item id overflow")? - != token_id - { - return Err("item id should be next".into()); - } - - >::create_item_internal( - &caller, - &self, - &to, - CreateItemData::NFT(CreateNftData { - const_data: vec![].try_into().unwrap(), - variable_data: vec![].try_into().unwrap(), - }), - ) - .map_err(|_| "mint error")?; - Ok(true) - } - - #[solidity(rename_selector = "mintWithTokenURI")] - fn mint_with_token_uri( - &mut self, - caller: caller, - to: address, - token_id: uint256, - token_uri: string, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let to = T::CrossAccountId::from_eth(to); - let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?; - if ::get(self.id) - .checked_add(1) - .ok_or("item id overflow")? - != token_id - { - return Err("item id should be next".into()); - } - - >::create_item_internal( - &caller, - &self, - &to, - CreateItemData::NFT(CreateNftData { - const_data: Vec::::from(token_uri) - .try_into() - .map_err(|_| "token uri is too long")?, - variable_data: vec![].try_into().unwrap(), - }), - ) - .map_err(|_| "mint error")?; - Ok(true) - } - - fn finish_minting(&mut self, _caller: caller) -> Result { - Err("not implementable".into()) - } -} - -#[solidity_interface(name = "ERC721UniqueExtensions")] -impl CollectionHandle { - #[solidity(rename_selector = "transfer")] - fn transfer_nft( - &mut self, - caller: caller, - to: address, - token_id: uint256, - _value: value, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let to = T::CrossAccountId::from_eth(to); - let token_id = token_id.try_into().map_err(|_| "amount overflow")?; - - >::transfer_internal(&caller, &to, self, token_id, 1) - .map_err(|_| "transfer error")?; - Ok(()) - } - - fn next_token_id(&self) -> Result { - Ok(ItemListIndex::get(self.id) - .checked_add(1) - .ok_or("item id overflow")? - .into()) - } - - fn set_variable_metadata( - &mut self, - caller: caller, - token_id: uint256, - data: bytes, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let token_id = token_id.try_into().map_err(|_| "token id overflow")?; - - >::set_variable_meta_data_internal(&caller, self, token_id, data) - .map_err(dispatch_to_evm::)?; - Ok(()) - } - - fn get_variable_metadata(&self, token_id: uint256) -> Result { - let token_id = token_id.try_into().map_err(|_| "token id overflow")?; - - >::get_variable_metadata(self, token_id).map_err(dispatch_to_evm::) - } - - fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let to = T::CrossAccountId::from_eth(to); - let mut expected_index = ::get(self.id) - .checked_add(1) - .ok_or("item id overflow")?; - - let total_tokens = token_ids.len(); - for id in token_ids.into_iter() { - let id: u32 = id.try_into().map_err(|_| "token id overflow")?; - if id != expected_index { - return Err("item id should be next".into()); - } - expected_index = expected_index.checked_add(1).ok_or("item id overflow")?; - } - - let data = (0..total_tokens) - .map(|_| { - CreateItemData::NFT(CreateNftData { - const_data: vec![].try_into().unwrap(), - variable_data: vec![].try_into().unwrap(), - }) - }) - .collect(); - - >::create_multiple_items_internal(&caller, self, &to, data) - .map_err(dispatch_to_evm::)?; - Ok(true) - } - - #[solidity(rename_selector = "mintBulkWithTokenURI")] - fn mint_bulk_with_token_uri( - &mut self, - caller: caller, - to: address, - tokens: Vec<(uint256, string)>, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let to = T::CrossAccountId::from_eth(to); - let mut expected_index = ::get(self.id) - .checked_add(1) - .ok_or("item id overflow")?; - - let mut data = Vec::with_capacity(tokens.len()); - for (id, token_uri) in tokens { - let id: u32 = id.try_into().map_err(|_| "token id overflow")?; - if id != expected_index { - panic!("item id should be next ({}) but got {}", expected_index, id); - } - expected_index = expected_index.checked_add(1).ok_or("item id overflow")?; - - data.push(CreateItemData::NFT(CreateNftData { - const_data: Vec::::from(token_uri) - .try_into() - .map_err(|_| "token uri is too long")?, - variable_data: vec![].try_into().unwrap(), - })); - } - - >::create_multiple_items_internal(&caller, self, &to, data) - .map_err(dispatch_to_evm::)?; - Ok(true) - } -} - -#[solidity_interface( - name = "UniqueNFT", - is( - ERC165, - ERC721, - ERC721Metadata, - ERC721Enumerable, - ERC721UniqueExtensions, - ERC721Mintable, - ERC721Burnable, - ) -)] -impl CollectionHandle {} - -#[derive(ToLog)] -pub enum ERC20Events { - Transfer { - #[indexed] - from: address, - #[indexed] - to: address, - value: uint256, - }, - Approval { - #[indexed] - owner: address, - #[indexed] - spender: address, - value: uint256, - }, -} - -#[solidity_interface( - name = "ERC20", - inline_is(InlineNameSymbol, InlineTotalSupply), - events(ERC20Events) -)] -impl CollectionHandle { - fn decimals(&self) -> Result { - Ok(if let CollectionMode::Fungible(decimals) = &self.mode { - *decimals - } else { - unreachable!() - }) - } - fn balance_of(&self, owner: address) -> Result { - let owner = T::EvmAddressMapping::into_account_id(owner); - let balance = >::get(self.id, owner); - Ok(balance.into()) - } - fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let to = T::CrossAccountId::from_eth(to); - let amount = amount.try_into().map_err(|_| "amount overflow")?; - - >::transfer_internal(&caller, &to, self, 1, amount) - .map_err(|_| "transfer error")?; - Ok(true) - } - #[solidity(rename_selector = "transferFrom")] - fn transfer_from_fungible( - &mut self, - caller: caller, - from: address, - to: address, - amount: uint256, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let from = T::CrossAccountId::from_eth(from); - let to = T::CrossAccountId::from_eth(to); - let amount = amount.try_into().map_err(|_| "amount overflow")?; - - >::transfer_from_internal(&caller, &from, &to, self, 1, amount) - .map_err(|_| "transferFrom error")?; - Ok(true) - } - #[solidity(rename_selector = "approve")] - fn approve_fungible( - &mut self, - caller: caller, - spender: address, - amount: uint256, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let spender = T::CrossAccountId::from_eth(spender); - let amount = amount.try_into().map_err(|_| "amount overflow")?; - - >::approve_internal(&caller, &spender, self, 1, amount) - .map_err(|_| "approve internal")?; - Ok(true) - } - fn allowance(&self, owner: address, spender: address) -> Result { - let owner = T::CrossAccountId::from_eth(owner); - let spender = T::CrossAccountId::from_eth(spender); - - Ok(>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into()) - } -} - -#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))] -impl CollectionHandle {} - -// Not a tests, but code generators -generate_stubgen!(nft_impl, UniqueNFTCall, true); -generate_stubgen!(nft_iface, UniqueNFTCall, false); - -generate_stubgen!(fungible_impl, UniqueFungibleCall, true); -generate_stubgen!(fungible_iface, UniqueFungibleCall, false); --- a/pallets/nft/src/eth/mod.rs +++ b/pallets/nft/src/eth/mod.rs @@ -1,86 +1,21 @@ -pub mod account; -pub mod erc; pub mod sponsoring; -use pallet_evm_coder_substrate::call_internal; +use pallet_common::{ + CollectionById, + erc::CommonEvmHandler, + eth::{map_eth_to_id, map_eth_to_token_id}, +}; +use pallet_fungible::FungibleHandle; +use pallet_nonfungible::NonfungibleHandle; +use pallet_refungible::{RefungibleHandle, erc::RefungibleTokenHandle}; use sp_std::borrow::ToOwned; use sp_std::vec::Vec; use pallet_evm::{PrecompileOutput}; use sp_core::{H160, U256}; -use frame_support::storage::StorageMap; -use crate::{Config, CollectionById, CollectionHandle, CollectionId, CollectionMode}; -use erc::{UniqueFungibleCall, UniqueNFTCall}; +use crate::{CollectionMode, Config, dispatch::Dispatched}; +use pallet_common::CollectionHandle; pub struct NftErcSupport(core::marker::PhantomData); - -// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection -// TODO: Unhardcode prefix -const ETH_ACCOUNT_PREFIX: [u8; 16] = [ - 0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e, -]; - -fn map_eth_to_id(eth: &H160) -> Option { - if eth[0..16] != ETH_ACCOUNT_PREFIX { - return None; - } - let mut id_bytes = [0; 4]; - id_bytes.copy_from_slice(ð[16..20]); - Some(u32::from_be_bytes(id_bytes)) -} -pub fn collection_id_to_address(id: u32) -> H160 { - let mut out = [0; 20]; - out[0..16].copy_from_slice(Ð_ACCOUNT_PREFIX); - out[16..20].copy_from_slice(&u32::to_be_bytes(id)); - H160(out) -} - -/* -fn call_internal( - collection: &mut CollectionHandle, - caller: caller, - method_id: u32, - mut input: AbiReader, - value: U256, -) -> Result> { - match collection.mode.clone() { - CollectionMode::Fungible(_) => { - call_internal(); - let call = match UniqueFungibleCall::parse(method_id, &mut input)? { - Some(v) => v, - None => { - #[cfg(feature = "std")] - { - println!("Method not found"); - } - return Ok(None); - } - }; - Ok(Some( as UniqueFungible>::call( - collection, - Msg { - call, - caller, - value, - }, - )?)) - } - CollectionMode::NFT => { - let call = match UniqueNFTCall::parse(method_id, &mut input)? { - Some(v) => v, - None => return Ok(None), - }; - Ok(Some( as UniqueNFT>::call( - collection, - Msg { - call, - caller, - value, - }, - )?)) - } - _ => Err("erc calls only supported to fungible and nft collections for now".into()), - } -}*/ impl pallet_evm::OnMethodCall for NftErcSupport { fn is_reserved(target: &H160) -> bool { @@ -92,20 +27,26 @@ .unwrap_or(false) } fn get_code(target: &H160) -> Option> { - map_eth_to_id(target) - .and_then(>::get) - .map(|collection| { + if let Some(collection_id) = map_eth_to_id(target) { + let collection = >::get(collection_id)?; + Some( match collection.mode { - CollectionMode::NFT => include_bytes!("stubs/UniqueNFT.raw") as &[u8], - CollectionMode::Fungible(_) => { - include_bytes!("stubs/UniqueFungible.raw") as &[u8] - } - CollectionMode::ReFungible => { - include_bytes!("stubs/UniqueRefungible.raw") as &[u8] - } + CollectionMode::NFT => >::CODE, + CollectionMode::Fungible(_) => >::CODE, + CollectionMode::ReFungible => >::CODE, } - .to_owned() - }) + .to_owned(), + ) + } else if let Some((collection_id, _token_id)) = map_eth_to_token_id(target) { + let collection = >::get(collection_id)?; + if collection.mode != CollectionMode::ReFungible { + return None; + } + // TODO: check token existence + Some(>::CODE.to_owned()) + } else { + None + } } fn call( source: &H160, @@ -114,17 +55,26 @@ input: &[u8], value: U256, ) -> Option { - let mut collection = map_eth_to_id(target) - .and_then(|id| >::get_with_gas_limit(id, gas_limit))?; - let result = match collection.mode { - CollectionMode::NFT => { - call_internal::(*source, &mut collection, value, input) + if let Some(collection_id) = map_eth_to_id(target) { + let collection = >::new_with_gas_limit(collection_id, gas_limit)?; + let dispatched = Dispatched::dispatch(collection); + + match dispatched { + Dispatched::Fungible(h) => h.call(source, input, value), + Dispatched::Nonfungible(h) => h.call(source, input, value), + Dispatched::Refungible(h) => h.call(source, input, value), } - CollectionMode::Fungible(_) => { - call_internal::(*source, &mut collection, value, input) + } else if let Some((collection_id, token_id)) = map_eth_to_token_id(target) { + let collection = >::new_with_gas_limit(collection_id, gas_limit)?; + if collection.mode != CollectionMode::ReFungible { + return None; } - _ => return None, - }; - collection.recorder.evm_to_precompile_output(result) + + let handle = RefungibleHandle::cast(collection); + // TODO: check token existence + RefungibleTokenHandle(handle, token_id).call(source, input, value) + } else { + None + } } } --- a/pallets/nft/src/eth/sponsoring.rs +++ b/pallets/nft/src/eth/sponsoring.rs @@ -1,29 +1,30 @@ //! Implements EVM sponsoring logic via OnChargeEVMTransaction -use crate::{ - Collection, CollectionById, Config, FungibleTransferBasket, NftTransferBasket, - eth::{account::EvmBackwardsAddressMapping, map_eth_to_id}, -}; +use crate::{Collection, Config, FungibleTransferBasket, NftTransferBasket}; use evm_coder::{Call, abi::AbiReader}; use frame_support::{ - storage::{StorageMap, StorageDoubleMap}, + storage::{StorageDoubleMap}, }; +use pallet_common::eth::map_eth_to_id; use sp_core::H160; use sp_std::prelude::*; use up_sponsorship::SponsorshipHandler; -use super::{ - account::CrossAccountId, - erc::{UniqueFungibleCall, UniqueNFTCall, ERC721Call, ERC20Call, ERC721UniqueExtensionsCall}, -}; -use core::convert::TryInto; use core::marker::PhantomData; -use nft_data_structs::{NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT}; +use core::convert::TryInto; +use nft_data_structs::{CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT}; +use pallet_common::{ + CollectionById, + account::{CrossAccountId, EvmBackwardsAddressMapping}, +}; + +use pallet_nonfungible::erc::{UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721Call}; +use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call}; struct AnyError; fn try_sponsor( caller: &H160, - collection_id: u32, + collection_id: CollectionId, collection: &Collection, call: &[u8], ) -> Result<(), AnyError> { --- a/pallets/nft/src/eth/stubs/UniqueFungible.sol +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: OTHER -// This code is automatically generated - -pragma solidity >=0.8.0 <0.9.0; - -// Common stubs holder -contract Dummy { - uint8 dummy; - string stub_error = "this contract is implemented in native"; -} - -// Inline -contract ERC20Events { - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); -} - -// Inline -contract InlineNameSymbol is Dummy { - // Selector: name() 06fdde03 - function name() public view returns (string memory) { - require(false, stub_error); - dummy; - return ""; - } - - // Selector: symbol() 95d89b41 - function symbol() public view returns (string memory) { - require(false, stub_error); - dummy; - return ""; - } -} - -// Inline -contract InlineTotalSupply is Dummy { - // Selector: totalSupply() 18160ddd - function totalSupply() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; - } -} - -contract ERC165 is Dummy { - // Selector: supportsInterface(bytes4) 01ffc9a7 - function supportsInterface(uint32 interfaceId) public view returns (bool) { - require(false, stub_error); - interfaceId; - dummy; - return false; - } -} - -contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events { - // Selector: decimals() 313ce567 - function decimals() public view returns (uint8) { - require(false, stub_error); - dummy; - return 0; - } - - // Selector: balanceOf(address) 70a08231 - function balanceOf(address owner) public view returns (uint256) { - require(false, stub_error); - owner; - dummy; - return 0; - } - - // Selector: transfer(address,uint256) a9059cbb - function transfer(address to, uint256 amount) public returns (bool) { - require(false, stub_error); - to; - amount; - dummy = 0; - return false; - } - - // Selector: transferFrom(address,address,uint256) 23b872dd - function transferFrom( - address from, - address to, - uint256 amount - ) public returns (bool) { - require(false, stub_error); - from; - to; - amount; - dummy = 0; - return false; - } - - // Selector: approve(address,uint256) 095ea7b3 - function approve(address spender, uint256 amount) public returns (bool) { - require(false, stub_error); - spender; - amount; - dummy = 0; - return false; - } - - // Selector: allowance(address,address) dd62ed3e - function allowance(address owner, address spender) - public - view - returns (uint256) - { - require(false, stub_error); - owner; - spender; - dummy; - return 0; - } -} - -contract UniqueFungible is Dummy, ERC165, ERC20 {} --- a/pallets/nft/src/eth/stubs/UniqueNFT.sol +++ /dev/null @@ -1,326 +0,0 @@ -// SPDX-License-Identifier: OTHER -// This code is automatically generated - -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; - string stub_error = "this contract is implemented in native"; -} - -// Inline -contract ERC721Events { - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - event ApprovalForAll( - address indexed owner, - address indexed operator, - bool approved - ); -} - -// Inline -contract ERC721MintableEvents { - event MintingFinished(); -} - -// Inline -contract InlineNameSymbol is Dummy { - // Selector: name() 06fdde03 - function name() public view returns (string memory) { - require(false, stub_error); - dummy; - return ""; - } - - // Selector: symbol() 95d89b41 - function symbol() public view returns (string memory) { - require(false, stub_error); - dummy; - return ""; - } -} - -// Inline -contract InlineTotalSupply is Dummy { - // Selector: totalSupply() 18160ddd - function totalSupply() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; - } -} - -contract ERC165 is Dummy { - // Selector: supportsInterface(bytes4) 01ffc9a7 - function supportsInterface(uint32 interfaceId) public view returns (bool) { - require(false, stub_error); - interfaceId; - dummy; - return false; - } -} - -contract ERC721 is Dummy, ERC165, ERC721Events { - // Selector: balanceOf(address) 70a08231 - function balanceOf(address owner) public view returns (uint256) { - require(false, stub_error); - owner; - dummy; - return 0; - } - - // Selector: ownerOf(uint256) 6352211e - function ownerOf(uint256 tokenId) public view returns (address) { - require(false, stub_error); - tokenId; - dummy; - return 0x0000000000000000000000000000000000000000; - } - - // Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672 - function safeTransferFromWithData( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public { - require(false, stub_error); - from; - to; - tokenId; - data; - dummy = 0; - } - - // Selector: safeTransferFrom(address,address,uint256) 42842e0e - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public { - require(false, stub_error); - from; - to; - tokenId; - dummy = 0; - } - - // Selector: transferFrom(address,address,uint256) 23b872dd - function transferFrom( - address from, - address to, - uint256 tokenId - ) public { - require(false, stub_error); - from; - to; - tokenId; - dummy = 0; - } - - // Selector: approve(address,uint256) 095ea7b3 - function approve(address approved, uint256 tokenId) public { - require(false, stub_error); - approved; - tokenId; - dummy = 0; - } - - // Selector: setApprovalForAll(address,bool) a22cb465 - function setApprovalForAll(address operator, bool approved) public { - require(false, stub_error); - operator; - approved; - dummy = 0; - } - - // Selector: getApproved(uint256) 081812fc - function getApproved(uint256 tokenId) public view returns (address) { - require(false, stub_error); - tokenId; - dummy; - return 0x0000000000000000000000000000000000000000; - } - - // Selector: isApprovedForAll(address,address) e985e9c5 - function isApprovedForAll(address owner, address operator) - public - view - returns (address) - { - require(false, stub_error); - owner; - operator; - dummy; - return 0x0000000000000000000000000000000000000000; - } -} - -contract ERC721Burnable is Dummy { - // Selector: burn(uint256) 42966c68 - function burn(uint256 tokenId) public { - require(false, stub_error); - tokenId; - dummy = 0; - } -} - -contract ERC721Enumerable is Dummy, InlineTotalSupply { - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) public view returns (uint256) { - require(false, stub_error); - index; - dummy; - return 0; - } - - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - public - view - returns (uint256) - { - require(false, stub_error); - owner; - index; - dummy; - return 0; - } -} - -contract ERC721Metadata is Dummy, InlineNameSymbol { - // Selector: tokenURI(uint256) c87b56dd - function tokenURI(uint256 tokenId) public view returns (string memory) { - require(false, stub_error); - tokenId; - dummy; - return ""; - } -} - -contract ERC721Mintable is Dummy, ERC721MintableEvents { - // Selector: mintingFinished() 05d2035b - function mintingFinished() public view returns (bool) { - require(false, stub_error); - dummy; - return false; - } - - // Selector: mint(address,uint256) 40c10f19 - function mint(address to, uint256 tokenId) public returns (bool) { - require(false, stub_error); - to; - tokenId; - dummy = 0; - return false; - } - - // Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f - function mintWithTokenURI( - address to, - uint256 tokenId, - string memory tokenUri - ) public returns (bool) { - require(false, stub_error); - to; - tokenId; - tokenUri; - dummy = 0; - return false; - } - - // Selector: finishMinting() 7d64bcb4 - function finishMinting() public returns (bool) { - require(false, stub_error); - dummy = 0; - return false; - } -} - -contract ERC721UniqueExtensions is Dummy { - // Selector: transfer(address,uint256) a9059cbb - function transfer(address to, uint256 tokenId) public { - require(false, stub_error); - to; - tokenId; - dummy = 0; - } - - // Selector: nextTokenId() 75794a3c - function nextTokenId() public view returns (uint256) { - require(false, stub_error); - 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 - Dummy, - ERC165, - ERC721, - ERC721Metadata, - ERC721Enumerable, - ERC721UniqueExtensions, - ERC721Mintable, - ERC721Burnable -{} --- a/pallets/nft/src/eth/stubs/UniqueRefungible.raw +++ /dev/null @@ -1 +0,0 @@ -TODO \ No newline at end of file --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -69,164 +69,29 @@ decl_error! { /// Error for non-fungible-token module. pub enum Error for Module { - /// Total collections bound exceeded. - TotalCollectionsLimitExceeded, /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30. CollectionDecimalPointLimitExceeded, - /// Collection name can not be longer than 63 char. - CollectionNameLimitExceeded, - /// Collection description can not be longer than 255 char. - CollectionDescriptionLimitExceeded, - /// Token prefix can not be longer than 15 char. - CollectionTokenPrefixLimitExceeded, - /// This collection does not exist. - CollectionNotFound, - /// Item not exists. - TokenNotFound, - /// Admin not found - AdminNotFound, - /// Arithmetic calculation overflow. - NumOverflow, - /// Account already has admin role. - AlreadyAdmin, - /// You do not own this collection. - NoPermission, /// This address is not set as sponsor, use setCollectionSponsor first. ConfirmUnsetSponsorFail, - /// Collection is not in mint mode. - PublicMintingNotAllowed, - /// Sender parameter and item owner must be equal. - MustBeTokenOwner, - /// Item balance not enough. - TokenValueTooLow, - /// Size of item is too large. - NftSizeLimitExceeded, - /// No approve found - ApproveNotFound, - /// Requested value more than approved. - TokenValueNotEnough, - /// Only approved addresses can call this method. - ApproveRequired, - /// Address is not in white list. - AddresNotInWhiteList, - /// Number of collection admins bound exceeded. - CollectionAdminsLimitExceeded, - /// Owned tokens by a single address bound exceeded. - AddressOwnershipLimitExceeded, /// Length of items properties must be greater than 0. EmptyArgument, - /// const_data exceeded data limit. - TokenConstDataLimitExceeded, - /// variable_data exceeded data limit. - TokenVariableDataLimitExceeded, - /// Not NFT item data used to mint in NFT collection. - NotNftDataUsedToMintNftCollectionToken, - /// Not Fungible item data used to mint in Fungible collection. - NotFungibleDataUsedToMintFungibleCollectionToken, - /// Not Re Fungible item data used to mint in Re Fungible collection. - NotReFungibleDataUsedToMintReFungibleCollectionToken, - /// Unexpected collection type. - UnexpectedCollectionType, - /// Can't store metadata in fungible tokens. - CantStoreMetadataInFungibleTokens, - /// Collection token limit exceeded - CollectionTokenLimitExceeded, - /// Account token limit exceeded per collection - AccountTokenLimitExceeded, /// Collection limit bounds per collection exceeded CollectionLimitBoundsExceeded, /// Tried to enable permissions which are only permitted to be disabled OwnerPermissionsCantBeReverted, - /// Schema data size limit bound exceeded - SchemaDataLimitExceeded, - /// Maximum refungibility exceeded - WrongRefungiblePieces, - /// createRefungible should be called with one owner - BadCreateRefungibleCall, - /// Gas limit exceeded - OutOfGas, - /// Metadata update denied by collection settings - MetadataUpdateDenied, - /// Metadata update flag become unmutable with None option - MetadataFlagFrozen, - /// Collection settings not allowing items transferring - TransferNotAllowed, - /// Can't transfer tokens to ethereum zero address - AddressIsZero, } -} - -#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"] -pub struct CollectionHandle { - pub id: CollectionId, - collection: Collection, - recorder: pallet_evm_coder_substrate::SubstrateRecorder, } -impl CollectionHandle { - pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option { - >::get(id).map(|collection| Self { - id, - collection, - recorder: pallet_evm_coder_substrate::SubstrateRecorder::new( - eth::collection_id_to_address(id), - gas_limit, - ), - }) - } - pub fn get(id: CollectionId) -> Option { - Self::get_with_gas_limit(id, u64::MAX) - } - pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult { - self.recorder.log_sub(log) - } - #[allow(dead_code)] - fn consume_gas(&self, gas: u64) -> DispatchResult { - self.recorder.consume_gas_sub(gas) - } - fn consume_sload(&self) -> DispatchResult { - self.recorder.consume_sload_sub() - } - fn consume_sstore(&self) -> DispatchResult { - self.recorder.consume_sstore_sub() - } - pub fn submit_logs(self) -> DispatchResult { - self.recorder.submit_logs() - } - pub fn save(self) -> DispatchResult { - self.recorder.submit_logs()?; - >::insert(self.id, self.collection); - Ok(()) - } -} -impl Deref for CollectionHandle { - type Target = Collection; - - fn deref(&self) -> &Self::Target { - &self.collection - } -} - -impl DerefMut for CollectionHandle { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.collection - } -} - -pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized { - type Event: From> + Into<::Event>; - +pub trait Config: + system::Config + + pallet_evm_coder_substrate::Config + + pallet_common::Config + + pallet_nonfungible::Config + + pallet_refungible::Config + + pallet_fungible::Config + + Sized +{ /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; - - type EvmAddressMapping: pallet_evm::AddressMapping; - type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping; - - type CrossAccountId: CrossAccountId; - type Currency: Currency; - type CollectionCreationPrice: Get< - <::Currency as Currency>::Balance, - >; - type TreasuryAccountId: Get; } type SelfWeightOf = ::WeightInfo; @@ -293,56 +158,10 @@ trait Store for Module as Nft { //#region Private members - /// Id of next collection - CreatedCollectionCount: u32; /// Used for migrations ChainVersion: u64; - /// Id of last collection token - /// Collection id (controlled?1) - ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId; - //#endregion - - //#region Bound counters - /// Amount of collections destroyed, used for total amount tracking with - /// CreatedCollectionCount - DestroyedCollectionCount: u32; - //#endregion - - //#region Basic collections - /// Collection info - /// Collection id (controlled?1) - pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option> = None; - /// List of collection admins - /// Collection id (controlled?2) - pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec; - /// Whitelisted collection users - /// Collection id (controlled?2), user id (controlled?3) - pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool; //#endregion - /// How many of collection items user have - /// Collection id (controlled?2), account id (real) - pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128; - - /// Amount of items which spender can transfer out of owners account (via transferFrom) - /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3)) - /// TODO: Off chain worker should remove from this map when token gets removed - pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128; - - //#region Item collections - /// Collection id (controlled?2), token id (controlled?1) - pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option>; - /// Collection id (controlled?2), owner (controlled?2) - pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType; - /// Collection id (controlled?2), token id (controlled?1) - pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option>; - //#endregion - - //#region Index list - /// Collection id (controlled?2), tokens owner (controlled?2) - pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec; - //#endregion - //#region Tokens transfer rate limit baskets /// (Collection id (controlled?2), who created (real)) /// TODO: Off chain worker should remove from this map when collection gets removed @@ -359,97 +178,13 @@ /// Collection id (controlled?2), token id (controlled?2) pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option = None; } - add_extra_genesis { - build(|config: &GenesisConfig| { - // Modification of storage - for (_num, _c) in &config.collection_id { - >::init_collection(_c); - } - - for (_num, _c, _i) in &config.nft_item_id { - >::init_nft_token(*_c, _i); - } - - for (collection_id, account_id, fungible_item) in &config.fungible_item_id { - >::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item); - } - - for (_num, _c, _i) in &config.refungible_item_id { - >::init_refungible_token(*_c, _i); - } - }) - } } - -decl_event!( - pub enum Event - where - AccountId = ::AccountId, - CrossAccountId = ::CrossAccountId, - { - /// New collection was created - /// - /// # Arguments - /// - /// * collection_id: Globally unique identifier of newly created collection. - /// - /// * mode: [CollectionMode] converted into u8. - /// - /// * account_id: Collection owner. - CollectionCreated(CollectionId, u8, AccountId), - - /// New item was created. - /// - /// # Arguments - /// - /// * collection_id: Id of the collection where item was created. - /// - /// * item_id: Id of an item. Unique within the collection. - /// - /// * recipient: Owner of newly created item - ItemCreated(CollectionId, TokenId, CrossAccountId), - - /// Collection item was burned. - /// - /// # Arguments - /// - /// collection_id. - /// - /// item_id: Identifier of burned NFT. - ItemDestroyed(CollectionId, TokenId), - /// Item was transferred - /// - /// * collection_id: Id of collection to which item is belong - /// - /// * item_id: Id of an item - /// - /// * sender: Original owner of item - /// - /// * recipient: New owner of item - /// - /// * amount: Always 1 for NFT - Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128), - - /// * collection_id - /// - /// * item_id - /// - /// * sender - /// - /// * spender - /// - /// * amount - Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128), - } -); - decl_module! { pub struct Module for enum Call where origin: T::Origin { - fn deposit_event() = default; const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT; type Error = Error;