diff --git a/Cargo.lock b/Cargo.lock index 2d86a55..42b46ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6774,6 +6774,28 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.13.0)", ] +[[package]] +name = "pallet-dmarket" +version = "0.0.1" +dependencies = [ + "account", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-nfts", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.13.0)", +] + [[package]] name = "pallet-election-provider-multi-phase" version = "27.0.0" @@ -7000,6 +7022,28 @@ dependencies = [ "sp-weights", ] +[[package]] +name = "pallet-migration" +version = "0.0.1" +dependencies = [ + "account", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-dmarket", + "pallet-nfts", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.13.0)", +] + [[package]] name = "pallet-mmr" version = "27.0.0" @@ -13607,9 +13651,11 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-collective", + "pallet-dmarket", "pallet-escrow", "pallet-marketplace", "pallet-message-queue", + "pallet-migration", "pallet-multibatching", "pallet-multisig", "pallet-myth-proxy", diff --git a/Cargo.toml b/Cargo.toml index 8017c5f..904f641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,9 @@ enumflags2 = { version = "0.7.7" } # Local dependencies testnet-runtime = { path = "runtime/testnet" } mainnet-runtime = { path = "runtime/mainnet" } +pallet-dmarket = { path = "pallets/dmarket", default-features = false } pallet-marketplace = { path = "pallets/marketplace", default-features = false } +pallet-migration = { path = "pallets/migration", default-features = false } pallet-multibatching = { path = "pallets/multibatching", default-features = false } runtime-common = { path = "runtime/common", default-features = false } pallet-escrow = { path = "pallets/escrow", default-features = false } diff --git a/pallets/dmarket/Cargo.toml b/pallets/dmarket/Cargo.toml new file mode 100644 index 0000000..c34facd --- /dev/null +++ b/pallets/dmarket/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "pallet-dmarket" +version = "0.0.1" +description = "DMarket FRAME pallet to provide Nft trading" +authors = { workspace = true } +homepage = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true, default-features = false, features = [ + "derive", +] } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = [ + "derive", +] } + +# Primitives +account = { workspace = true } + +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-std = { workspace = true } +sp-api = { workspace = true, default-features = false } +sp-io = { workspace = true } +sp-runtime = { workspace = true, default-features = false } +pallet-nfts = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-keystore = { workspace = true, default-features = false } +pallet-balances = { workspace = true, default-features = false } +pallet-timestamp = { workspace = true, default-features = false } + +[dev-dependencies] +sp-io = { workspace = true } + +[features] +default = ["std"] +std = [ + "account/std", + "parity-scale-codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-io/std", + "sp-api/std", + "sp-std/std", + "pallet-balances/std", + "pallet-nfts/std", + "pallet-timestamp/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/dmarket/README.md b/pallets/dmarket/README.md new file mode 100644 index 0000000..9fcc8b5 --- /dev/null +++ b/pallets/dmarket/README.md @@ -0,0 +1,30 @@ +# Dmarket Pallet + +The Dmarket pallet provides a marketplace for buying and selling NFTs that are managed by the `pallet-nfts`, based on the dmarket Smart contracts developed by Mythical. + +## Overview + +This project enables users to securely trade NFTs that are part of the Dmarket collection by allowing both the seller and buyer to agree on the terms and digitally sign a message approving the trade. The signed messages include specific parameters that ensure both parties are in agreement before the trade is executed on the blockchain. + +For the seller: + +- Domain: The network domain identifier, specifying the environment in which the trade is executed. Helps to prevent transaction replay on other chains that use this very same pallet. +- Sender: The account authorized to submit the trade transaction to the blockchain. +- FeeAccount: The account designated to receive the trade fee. +- ItemId: The unique identifier of the NFT being traded. Must be part of the Dmarket Colection +- Price: The selling price set by the seller for the NFT. +- AskExpirationAt: The expiration timestamp, after which the seller's signature is no longer valid. + +For the Buyer: + +- Domain, Sender, FeeAccount, ItemId, and Price: These parameters must match those in the seller's message to ensure both parties are in agreement. + +- Fee: The amount of tokens the buyer agrees to pay as a fee for the trade. +- BiExpirationAt: The expiration timestamp, after which the buyer's signature is no longer valid. + +Once the seller and buyer have signed their respective messages, an agreed-upon sender can submit the trade to the blockchain. The transaction validates the signatures against the provided trade parameters. If the signatures are valid and the trade conditions are met, the NFT is transferred from the seller to the buyer. Simultaneously, the agreed-upon price is transferred from the buyer to the seller, and the fee is transferred to the FeeAccount. + +## Dispatchable Functions + +- `force_set_collection()`: Sets the Dmarket collection. Only callable by root. +- `execute_trade()`: Execute a trade between a seller and a buyer for a specific NFT (item) in the configured DmarketCollection. Callable by anyone as long as the origin matches the sender field inside both Ask and Bid signed messages. diff --git a/pallets/dmarket/src/benchmarking.rs b/pallets/dmarket/src/benchmarking.rs new file mode 100644 index 0000000..df65bde --- /dev/null +++ b/pallets/dmarket/src/benchmarking.rs @@ -0,0 +1,179 @@ +#![cfg(feature = "runtime-benchmarks")] +use super::*; +use crate::Pallet as Dmarket; +use frame_benchmarking::v2::*; +use frame_support::{ + assert_ok, + dispatch::RawOrigin, + traits::{ + fungible::{Inspect as InspectFungible, Mutate as MutateFungible}, + tokens::nonfungibles_v2::{Create, Mutate}, + }, +}; +use pallet_nfts::ItemId; +use pallet_nfts::{CollectionConfig, CollectionSettings, ItemConfig, MintSettings, Pallet as Nfts}; +use sp_core::ecdsa::Public; +use sp_io::{ + crypto::{ecdsa_generate, ecdsa_sign_prehashed}, + hashing::keccak_256, +}; + +use crate::BenchmarkHelper; + +const SEED: u32 = 0; + +type BalanceOf = + <::Currency as InspectFungible<::AccountId>>::Balance; + +impl BenchmarkHelper for () +where + CollectionId: From, + ItemId: From, + Moment: From, +{ + fn collection(id: u16) -> CollectionId { + id.into() + } + fn timestamp(value: u64) -> Moment { + value.into() + } +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn funded_and_whitelisted_account(name: &'static str, index: u32) -> T::AccountId { + let caller: T::AccountId = account(name, index, SEED); + // Give the account half of the maximum value of the `Balance` type. + // Otherwise some transfers will fail with an overflow error. + let ed = ::Currency::minimum_balance(); + let multiplier = BalanceOf::::from(1000000u32); + + ::Currency::set_balance(&caller, ed * multiplier); + whitelist_account!(caller); + caller +} + +fn mint_nft(nft_id: ItemId, caller: T::AccountId) { + let default_config = CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: Some(u128::MAX), + mint_settings: MintSettings::default(), + }; + + assert_ok!(Nfts::::create_collection(&caller, &caller, &default_config)); + let collection = T::BenchmarkHelper::collection(0); + assert_ok!(Nfts::::mint_into(&collection, &nft_id, &caller, &ItemConfig::default(), true)); +} + +#[benchmarks(where T::AccountId: From, T::Signature: From)] +pub mod benchmarks { + use super::*; + use account::{AccountId20, EthereumSignature, EthereumSigner}; + use pallet_timestamp::Pallet as Timestamp; + + use sp_runtime::traits::IdentifyAccount; + + fn sign_trade( + sender: &T::AccountId, + fee_address: &T::AccountId, + trade: &TradeParamsOf, + seller_signer: Public, + buyer_signer: Public, + ) -> TradeSignatures + where + T::Signature: From, + { + let ask_message: Vec = Dmarket::::get_ask_message(sender, fee_address, trade); + let ask_hashed = keccak_256(&ask_message); + + let bid_message: Vec = Dmarket::::get_bid_message(sender, fee_address, trade); + let bid_hashed = keccak_256(&bid_message); + + TradeSignatures { + ask_signature: EthereumSignature::from( + ecdsa_sign_prehashed(0.into(), &seller_signer, &ask_hashed).unwrap(), + ) + .into(), + bid_signature: EthereumSignature::from( + ecdsa_sign_prehashed(1.into(), &buyer_signer, &bid_hashed).unwrap(), + ) + .into(), + } + } + + fn trade_participants() -> (T::AccountId, Public, T::AccountId, Public) + where + T::AccountId: From, + { + let ed = ::Currency::minimum_balance(); + let multiplier = BalanceOf::::from(10000u16); + + let seller_public = ecdsa_generate(0.into(), None); + let seller_signer: EthereumSigner = seller_public.into(); + let seller: T::AccountId = seller_signer.clone().into_account().into(); + whitelist_account!(seller); + + let buyer_public = ecdsa_generate(1.into(), None); + let buyer_signer: EthereumSigner = buyer_public.into(); + let buyer: T::AccountId = buyer_signer.clone().into_account().into(); + whitelist_account!(buyer); + + ::Currency::set_balance(&seller, ed * multiplier); + ::Currency::set_balance(&buyer, ed * multiplier); + + (seller, seller_public, buyer, buyer_public) + } + + #[benchmark] + fn force_set_collection() { + let collection_id = T::BenchmarkHelper::collection(0); + let caller: T::AccountId = funded_and_whitelisted_account::("caller", 0); + let _ = mint_nft::(1, caller); + + #[extrinsic_call] + _(RawOrigin::Root, collection_id); + + assert_last_event::(Event::CollectionUpdated { collection_id }.into()); + } + + #[benchmark] + fn execute_trade() { + let sender: T::AccountId = funded_and_whitelisted_account::("sender", 0); + let (seller, seller_public, buyer, buyer_public) = trade_participants::(); + let fee_address: T::AccountId = funded_and_whitelisted_account::("fee_address", 0); + + let collection_id = T::BenchmarkHelper::collection(0); + let item = 1; + mint_nft::(item, seller.clone()); + assert_ok!(Dmarket::::force_set_collection(RawOrigin::Root.into(), collection_id)); + + let expiration = Timestamp::::get() + T::BenchmarkHelper::timestamp(1000); + let trade = TradeParams { + price: BalanceOf::::from(100u16), + fee: BalanceOf::::from(1u8), + ask_expiration: expiration, + bid_expiration: expiration, + item, + }; + let signatures = + sign_trade::(&sender, &fee_address, &trade, seller_public, buyer_public); + + #[extrinsic_call] + _( + RawOrigin::Signed(sender), + seller.clone(), + buyer.clone(), + trade.clone(), + signatures, + fee_address, + ); + + assert_last_event::( + Event::Trade { seller, buyer, item, price: trade.price, fee: trade.fee }.into(), + ); + } + + impl_benchmark_test_suite!(Dmarket, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/pallets/dmarket/src/lib.rs b/pallets/dmarket/src/lib.rs new file mode 100644 index 0000000..ec07368 --- /dev/null +++ b/pallets/dmarket/src/lib.rs @@ -0,0 +1,353 @@ +//! # Marketplace Module +//! +//! A module that facilitates trading of non-fungible items (NFTs) through the creation and management of orders. +//! +//! ## Related Modules +//! - NFTs: Provides functionalities for managing non-fungible tokens. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +mod types; +use parity_scale_codec::Codec; +pub use types::*; + +pub mod weights; +pub use weights::*; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use core::usize; + + use crate::Item; + + use super::*; + use frame_support::{ + ensure, + pallet_prelude::*, + traits::{ + fungible::{Inspect, Mutate}, + nonfungibles_v2::{Inspect as NftInspect, Transfer}, + tokens::Preservation::Preserve, + }, + }; + use frame_system::{ensure_signed, pallet_prelude::*}; + + use frame_support::{dispatch::GetDispatchInfo, traits::UnfilteredDispatchable}; + + use sp_runtime::traits::Hash; + use sp_runtime::{ + traits::{IdentifyAccount, Verify}, + DispatchError, + }; + use sp_std::vec::Vec; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + frame_system::Config + pallet_nfts::Config + pallet_timestamp::Config + { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type RuntimeCall: Parameter + + UnfilteredDispatchable + + GetDispatchInfo; + + /// The currency trait. + type Currency: Inspect + Mutate; + + /// Off-Chain signature type. + /// + /// Can verify whether a `Self::Signer` created a signature. + type Signature: Verify + Parameter; + + /// Off-Chain public key. + /// + /// Must identify as an on-chain `Self::AccountId`. + type Signer: IdentifyAccount; + + ///Chain Domain + #[pallet::constant] + type Domain: Get; + + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; + + #[cfg(feature = "runtime-benchmarks")] + /// A set of helper functions for benchmarking. + type BenchmarkHelper: BenchmarkHelper; + } + + #[pallet::storage] + pub type ClosedAsks = StorageMap<_, Blake2_128Concat, T::Hash, OrderDataOf>; + + #[pallet::storage] + pub type ClosedBids = StorageMap<_, Blake2_128Concat, T::Hash, OrderDataOf>; + + #[pallet::storage] + pub type DmarketCollection = StorageValue<_, T::CollectionId, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The fee signer account was updated. + CollectionUpdated { collection_id: T::CollectionId }, + /// + Trade { + buyer: T::AccountId, + seller: T::AccountId, + item: Item, + price: BalanceOf, + fee: BalanceOf, + }, + } + + #[pallet::error] + pub enum Error { + /// The item was not found. + ItemNotFound, + /// Item can only be operated by the Item owner. + SellerNotItemOwner, + /// The bid with the provided parameters has already been executed + BidAlreadyExecuted, + /// The ask with the provided parameters has already been executed + AskAlreadyExecuted, + /// Buyer balance is not enough to pay for trade costs + BuyerBalanceTooLow, + /// Bid expiration timestamp must be in the future + BidExpired, + /// Ask expiration timestamp must be in the future + AskExpired, + /// The signature provided by the buyer is invalid + InvalidBuyerSignature, + /// The signature provided by the seller is invalid + InvalidSellerSignature, + /// Same buyer and seller not allowed. + BuyerIsSeller, + /// Invalid Signed message + BadSignedMessage, + /// Dmarket collection already set to the provided value. + CollectionAlreadyInUse, + /// Dmarket collection has not been set + CollectionNotSet, + /// The provided Dmarket collect was not found + CollectionNotFound, + } + + #[pallet::call] + impl Pallet { + /// Sets the Dmarket collection. + /// + /// Only the root origin can execute this function. + /// + /// Precondition: + /// - The collection must already exist, otherwise the extrinsic will fail. + /// + /// Parameters: + /// - `collection_id`: The collectionID of the NFT collection to be set as the Dmarket Collection. + /// + /// + /// Emits CollectionUpdated when successful. + /// + /// Weight: `WeightInfo::force_set_collection` (defined in the `Config` trait). + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::force_set_collection())] + pub fn force_set_collection( + origin: OriginFor, + collection_id: T::CollectionId, + ) -> DispatchResult { + ensure_root(origin)?; + + as NftInspect>::collection_owner(&collection_id) + .ok_or(Error::::CollectionNotFound)?; + + ensure!( + DmarketCollection::::get().as_ref() != Some(&collection_id), + Error::::CollectionAlreadyInUse + ); + + DmarketCollection::::put(collection_id.clone()); + Self::deposit_event(Event::CollectionUpdated { collection_id }); + Ok(()) + } + + /// Execute a trade between a seller and a buyer for a specific NFT (item) in the configured DmarketCollection. + /// + /// Preconditions: + /// - The seller and buyer must be different accounts. + /// - The seller must be the current owner of the NFT item. + /// - The trade must not be expired, and signatures provided must be valid. + /// + /// Parameters: + /// - `origin`: The origin of the call, which must be part of the signed message of both seller and buyer. + /// - `seller`: The account ID of the seller who owns the NFT item. + /// - `buyer`: The account ID of the buyer who will purchase the NFT item. + /// - `trade`: The parameters of the trade, including item details, prices, and expiration times. + /// - `signatures`: The signatures from both the seller and buyer authorizing the trade. + /// - `fee_address`: The account ID where the transaction fee will be transferred. + /// + /// Signed message schema: + /// - Ask: (domain, sender, fee_address, item, price, expiration). + /// - Bid: (domain, sender, fee_address, item, price, fee, expiration). + /// + /// Only callable if origin matches `sender` in both Ask and Bid signed messages. + /// + /// Emits `Trade` event upon successful execution. + /// + /// Weight: `WeightInfo::execute_trade` (defined in the `Config` trait). + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::execute_trade())] + pub fn execute_trade( + origin: OriginFor, + seller: T::AccountId, + buyer: T::AccountId, + trade: TradeParamsOf, + signatures: TradeSignatures<::Signature>, + fee_address: T::AccountId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(seller != buyer, Error::::BuyerIsSeller); + + let timestamp = pallet_timestamp::Pallet::::get(); + ensure!(trade.ask_expiration > timestamp, Error::::AskExpired); + ensure!(trade.bid_expiration > timestamp, Error::::BidExpired); + + // Pay fees to FeeAddress + let collection = DmarketCollection::::get().ok_or(Error::::CollectionNotSet)?; + + let item_owner = pallet_nfts::Pallet::::owner(collection, trade.item) + .ok_or(Error::::ItemNotFound)?; + ensure!(seller == item_owner, Error::::SellerNotItemOwner); + + let (ask_hash, bid_hash) = Self::hash_ask_bid_data(&trade); + ensure!(!ClosedAsks::::contains_key(ask_hash), Error::::AskAlreadyExecuted); + ensure!(!ClosedBids::::contains_key(bid_hash), Error::::BidAlreadyExecuted); + + Self::verify_signature( + &seller, + &Self::get_ask_message(&who, &fee_address, &trade), + signatures.ask_signature, + ) + .map_err(|_| Error::::InvalidSellerSignature)?; + + Self::verify_signature( + &buyer, + &Self::get_bid_message(&who, &fee_address, &trade), + signatures.bid_signature, + ) + .map_err(|_| Error::::InvalidBuyerSignature)?; + + let order_data: OrderDataOf = + OrderData { caller: who, fee_address: fee_address.clone() }; + + //Store closed trades + ClosedAsks::::insert(ask_hash, order_data.clone()); + ClosedBids::::insert(bid_hash, order_data); + + as Transfer>::transfer( + &collection, + &trade.item, + &buyer, + )?; + ::Currency::transfer(&buyer, &seller, trade.price, Preserve) + .map_err(|_| Error::::BuyerBalanceTooLow)?; + ::Currency::transfer(&seller, &fee_address, trade.fee, Preserve)?; + + Self::deposit_event(Event::Trade { + seller, + buyer, + item: trade.item, + price: trade.price, + fee: trade.fee, + }); + + Ok(()) + } + } + + impl Pallet { + fn verify_signature( + who: &T::AccountId, + message: &Vec, + signature: T::Signature, + ) -> Result<(), DispatchError> { + if !signature.verify(message.as_ref(), &who) { + return Err(Error::::BadSignedMessage.into()); + } + + Ok(()) + } + + pub fn hash_ask_bid_data(trade: &TradeParamsOf) -> (T::Hash, T::Hash) { + let ask_hash = + T::Hashing::hash(&(trade.item, trade.price, trade.ask_expiration).encode()); + let bid_hash = T::Hashing::hash( + &(trade.item, trade.price, trade.fee, trade.bid_expiration).encode(), + ); + + (ask_hash, bid_hash) + } + + pub fn get_ask_message( + caller: &T::AccountId, + fee_address: &T::AccountId, + trade: &TradeParamsOf, + ) -> Vec { + AskMessage { + domain: T::Domain::get(), + sender: caller.clone(), + fee_address: fee_address.clone(), + item: trade.item, + price: trade.price, + expiration: trade.ask_expiration, + } + .encode() + } + + pub fn get_bid_message( + caller: &T::AccountId, + fee_address: &T::AccountId, + trade: &TradeParamsOf, + ) -> Vec { + BidMessage { + domain: T::Domain::get(), + sender: caller.clone(), + fee_address: fee_address.clone(), + item: trade.item, + price: trade.price, + fee: trade.fee, + expiration: trade.bid_expiration, + } + .encode() + } + } +} + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); + +use sp_std::vec::Vec; +sp_api::decl_runtime_apis! { + pub trait DmarketApi + where + AccountId: Codec, + Balance: Codec, + Moment: Codec, + Hash: Codec, + { + fn hash_ask_bid_data(trade: TradeParams)-> (Hash, Hash); + fn get_ask_message(caller: AccountId, fee_address: AccountId, trade: TradeParams) -> Vec; + fn get_bid_message(caller: AccountId, fee_address: AccountId, trade: TradeParams) -> Vec; + } +} diff --git a/pallets/dmarket/src/mock.rs b/pallets/dmarket/src/mock.rs new file mode 100644 index 0000000..5b58cd3 --- /dev/null +++ b/pallets/dmarket/src/mock.rs @@ -0,0 +1,145 @@ +use frame_support::{ + derive_impl, parameter_types, + traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64}, +}; +use frame_system as system; +use sp_core::H256; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, +}; + +use account::EthereumSignature; + +use crate::{self as pallet_dmarket}; +use pallet_nfts::PalletFeatures; + +type Signature = EthereumSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Dmarket: pallet_dmarket, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + Nfts: pallet_nfts, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl pallet_nfts::Config for Test { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type Locker = (); + type CollectionDeposit = ConstU128<0>; + type ItemDeposit = ConstU128<0>; + type MetadataDepositBase = ConstU128<1>; + type AttributeDepositBase = ConstU128<1>; + type DepositPerByte = ConstU128<1>; + type StringLimit = ConstU32<50>; + type KeyLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; + type ApprovalsLimit = ConstU32<10>; + type ItemAttributesApprovalsLimit = ConstU32<2>; + type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxAttributesPerCall = ConstU32<2>; + type Features = Features; + type OffchainSignature = Signature; + type OffchainPublic = ::Signer; + type WeightInfo = (); + pallet_nfts::runtime_benchmarks_enabled! { + type Helper = (); + } +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = ConstU32<2>; + type ReserveIdentifier = [u8; 8]; + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = (); + type FreezeIdentifier = (); + type MaxFreezes = (); +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +parameter_types! { + pub const DOMAIN: [u8;8] = *b"MYTH_NET"; +} + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type Signature = Signature; + type Signer = ::Signer; + type Domain = DOMAIN; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/pallets/dmarket/src/tests.rs b/pallets/dmarket/src/tests.rs new file mode 100644 index 0000000..5e7c2bb --- /dev/null +++ b/pallets/dmarket/src/tests.rs @@ -0,0 +1,547 @@ +use account::{AccountId20, EthereumSignature, EthereumSigner}; + +use frame_support::{ + assert_noop, assert_ok, + error::BadOrigin, + traits::fungible::{Inspect as InspectFungible, Mutate}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_nfts::{CollectionConfig, CollectionSettings, MintSettings, NextCollectionId}; +use sp_core::{ + ecdsa::{Pair as KeyPair, Signature}, + keccak_256, Pair, +}; + +use sp_runtime::traits::IdentifyAccount; + +use self::mock::Timestamp; +use crate::{mock::*, *}; + +type AccountIdOf = ::AccountId; +type CollectionId = ::CollectionId; +type Balance = ::Balance; + +fn account(id: u8) -> AccountIdOf { + [id; 20].into() +} + +fn create_collection(creator: &AccountIdOf) -> CollectionId { + let collection_id = NextCollectionId::::get().unwrap_or_default(); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + *creator, + collection_config_with_all_settings_enabled() + )); + + collection_id +} + +fn collection_config_with_all_settings_enabled( +) -> CollectionConfig, BlockNumberFor, CollectionId> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: Some(1000000), + mint_settings: MintSettings::default(), + } +} + +mod force_set_collection { + use super::*; + + #[test] + fn force_set_collection_works() { + new_test_ext().execute_with(|| { + let collection_id = create_collection(&account(0)); + + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), collection_id)); + assert!(DmarketCollection::::get() == Some(collection_id)); + + assert_noop!( + Dmarket::force_set_collection(RuntimeOrigin::root(), collection_id), + Error::::CollectionAlreadyInUse + ); + + let other_collection = create_collection(&account(0)); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), other_collection)); + assert!(DmarketCollection::::get() == Some(other_collection)); + }) + } + + #[test] + fn fails_no_root() { + new_test_ext().execute_with(|| { + let collection_id = create_collection(&account(0)); + + assert_noop!( + Dmarket::force_set_collection(RuntimeOrigin::signed(account(1)), collection_id), + BadOrigin + ); + }) + } + + #[test] + fn collection_not_found() { + new_test_ext().execute_with(|| { + assert_noop!( + Dmarket::force_set_collection(RuntimeOrigin::root(), 0), + Error::::CollectionNotFound + ); + }) + } +} + +mod execute_trade { + use super::*; + + fn get_trade_accounts() -> (AccountIdOf, AccountIdOf, KeyPair, KeyPair) { + let sender = account(0); + let fee_address = account(1); + + let seller_pair = Pair::from_string("//Seller", None).unwrap(); + let buyer_pair = Pair::from_string("//Buyer", None).unwrap(); + (sender, fee_address, seller_pair, buyer_pair) + } + + fn setup_nft( + sender: &AccountIdOf, + item_owner: &AccountIdOf, + item: u128, + ) -> CollectionId { + let collection_id = create_collection(&sender); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), collection_id)); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(*sender), 0, Some(item), *item_owner, None)); + collection_id + } + + fn sign_trade( + caller: &AccountIdOf, + fee_address: &AccountIdOf, + trade: &TradeParamsOf, + seller_pair: KeyPair, + buyer_pair: KeyPair, + ) -> TradeSignaturesOf { + let ask_message: Vec = Dmarket::get_ask_message(caller, fee_address, trade); + let hashed_ask = keccak_256(&ask_message); + + let bid_message: Vec = Dmarket::get_bid_message(caller, fee_address, trade); + let hashed_bid = keccak_256(&bid_message); + + TradeSignatures { + ask_signature: EthereumSignature::from(seller_pair.sign_prehashed(&hashed_ask)), + bid_signature: EthereumSignature::from(buyer_pair.sign_prehashed(&hashed_bid)), + } + } + + #[test] + fn execute_trade_works() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + let collection = setup_nft(&sender, &seller, item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let buyer_balance = Balances::balance(&buyer); + let fee_address_balance = Balances::balance(&fee_address); + let seller_balance = Balances::balance(&seller); + + let trade = TradeParams { + price, + fee, + item, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + + assert_ok!(Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + )); + + assert_eq!(Nfts::owner(collection, item).unwrap(), buyer); + assert_eq!(Balances::balance(&buyer), buyer_balance - price); + assert_eq!(Balances::balance(&seller), seller_balance + price - fee); + assert_eq!(Balances::balance(&fee_address), fee_address_balance + fee); + + let (ask_hash, bid_hash) = Dmarket::hash_ask_bid_data(&trade); + assert!(ClosedAsks::::contains_key(ask_hash)); + assert!(ClosedBids::::contains_key(bid_hash)); + }) + } + + #[test] + fn buyer_is_seller() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = seller.clone(); + + let item = 1; + let _ = setup_nft(&sender, &seller, item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let trade = TradeParams { + price, + fee, + item, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::BuyerIsSeller + ); + }) + } + + #[test] + fn ask_or_bid_expired() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + let _ = setup_nft(&sender, &seller, item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let mut trade = + TradeParams { price, fee, item, ask_expiration: 0, bid_expiration: expiration }; + let signatures = + sign_trade(&sender, &fee_address, &trade, seller_pair.clone(), buyer_pair.clone()); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::AskExpired + ); + + trade.ask_expiration = expiration; + trade.bid_expiration = 0; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(seller), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::BidExpired + ); + }) + } + + #[test] + fn collection_not_set() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let trade = TradeParams { + price, + fee, + item, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::CollectionNotSet + ); + }) + } + + #[test] + fn item_not_found() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + let _ = setup_nft(&sender, &seller, item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let trade = TradeParams { + price, + fee, + item: item + 1, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::ItemNotFound + ); + }) + } + + #[test] + fn already_executed() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + let collection = setup_nft(&sender, &seller, item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let trade = TradeParams { + price, + fee, + item, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + + assert_ok!(Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures.clone(), + fee_address + )); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(buyer), collection, item, seller)); + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures.clone(), + fee_address + ), + Error::::AskAlreadyExecuted + ); + + let (ask_hash, _) = Dmarket::hash_ask_bid_data(&trade); + ClosedAsks::::remove(ask_hash); + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::BidAlreadyExecuted + ); + }) + } + + #[test] + fn invalid_signatures() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + let _ = setup_nft(&sender, &seller, item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let trade = TradeParams { + price, + fee, + item, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let mut signatures = + sign_trade(&sender, &fee_address, &trade, seller_pair.clone(), buyer_pair.clone()); + signatures.ask_signature = EthereumSignature::from(Signature::from_raw([0; 65])); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::InvalidSellerSignature + ); + + let mut signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + signatures.bid_signature = EthereumSignature::from(Signature::from_raw([0; 65])); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::InvalidBuyerSignature + ); + }) + } + + #[test] + fn seller_not_owner() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + let _ = setup_nft(&sender, &account(1), item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price * 2); + + let trade = TradeParams { + price, + fee, + item, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::SellerNotItemOwner + ); + }) + } + + #[test] + fn buyer_not_enough_funds() { + new_test_ext().execute_with(|| { + let (sender, fee_address, seller_pair, buyer_pair) = get_trade_accounts(); + + let seller: AccountId20 = EthereumSigner::from(seller_pair.public()).into_account(); + let buyer: AccountId20 = EthereumSigner::from(buyer_pair.public()).into_account(); + + let item = 1; + let _ = setup_nft(&sender, &seller, item); + + let expiration = Timestamp::get() + 10; + let price = 10000000; + let fee = 100; + Balances::set_balance(&buyer, price - 10); + + let trade = TradeParams { + price, + fee, + item, + ask_expiration: expiration, + bid_expiration: expiration, + }; + let signatures = sign_trade(&sender, &fee_address, &trade, seller_pair, buyer_pair); + + assert_noop!( + Dmarket::execute_trade( + RuntimeOrigin::signed(sender), + seller, + buyer, + trade.clone(), + signatures, + fee_address + ), + Error::::BuyerBalanceTooLow + ); + }) + } +} diff --git a/pallets/dmarket/src/types.rs b/pallets/dmarket/src/types.rs new file mode 100644 index 0000000..e1f3ccb --- /dev/null +++ b/pallets/dmarket/src/types.rs @@ -0,0 +1,84 @@ +use crate::Config; +use frame_support::traits::fungible::Inspect; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +pub type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; + +pub type Item = u128; +pub type Domain = [u8; 8]; + +#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo)] +pub struct TradeParams { + pub price: Amount, + pub fee: Amount, + pub item: ItemId, + pub ask_expiration: Expiration, + pub bid_expiration: Expiration, +} + +pub type TradeParamsOf = TradeParams< + <::Currency as Inspect<::AccountId>>::Balance, + Item, + ::Moment, +>; + +#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo)] +pub struct TradeSignatures { + pub ask_signature: OffchainSignature, + pub bid_signature: OffchainSignature, +} + +pub type TradeSignaturesOf = TradeSignatures<::OffchainSignature>; + +#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo)] +pub struct AskMessage { + pub domain: Domain, + pub sender: Account, + pub fee_address: Account, + pub item: ItemId, + pub price: Amount, + pub expiration: Expiration, +} + +pub type AskMessageOf = AskMessage< + ::AccountId, + <::Currency as Inspect<::AccountId>>::Balance, + Item, + ::Moment, +>; + +#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub struct OrderData { + pub caller: Account, + pub fee_address: Account, +} + +pub type OrderDataOf = OrderData<::AccountId>; + +#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo)] +pub struct BidMessage { + pub domain: Domain, + pub sender: Account, + pub fee_address: Account, + pub item: ItemId, + pub price: Amount, + pub fee: Amount, + pub expiration: Expiration, +} + +pub type BidMessageOf = BidMessage< + ::AccountId, + <::Currency as Inspect<::AccountId>>::Balance, + Item, + ::Moment, +>; + +#[cfg(feature = "runtime-benchmarks")] +pub trait BenchmarkHelper { + /// Returns a collection id from a given integer. + fn collection(id: u16) -> CollectionId; + /// Returns an nft id from a given integer. + fn timestamp(value: u64) -> Moment; +} diff --git a/pallets/dmarket/src/weights.rs b/pallets/dmarket/src/weights.rs new file mode 100644 index 0000000..19b64d6 --- /dev/null +++ b/pallets/dmarket/src/weights.rs @@ -0,0 +1,154 @@ + +//! Autogenerated weights for `pallet_dmarket` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-06-05, STEPS: `10`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `pop-os`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/mythos-node +// benchmark +// pallet +// --steps +// 10 +// --template +// ./.maintain/template.hbs +// --repeat +// 20 +// --extrinsic +// * +// --wasm-execution +// compiled +// --heap-pages +// 4096 +// --pallet +// pallet_dmarket +// --output +// ./pallets/dmarket/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_dmarket`. +pub trait WeightInfo { + fn force_set_collection() -> Weight; + fn execute_trade() -> Weight; +} + +/// Weights for `pallet_dmarket` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); + impl WeightInfo for SubstrateWeight { + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(145), added: 2620, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:1) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + fn force_set_collection() -> Weight { + // Proof Size summary in bytes: + // Measured: `420` + // Estimated: `3610` + // Minimum execution time: 10_150_000 picoseconds. + Weight::from_parts(10_390_000, 3610) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + /// Storage: `Dmarket::BalanceManager` (r:1 w:0) + /// Proof: `Dmarket::BalanceManager` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::FeeAddress` (r:1 w:0) + /// Proof: `Dmarket::FeeAddress` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:0) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(637), added: 3112, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::ClosedAsks` (r:1 w:1) + /// Proof: `Dmarket::ClosedAsks` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::ClosedBids` (r:1 w:1) + /// Proof: `Dmarket::ClosedBids` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(145), added: 2620, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(495), added: 2970, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(117), added: 2592, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(151), added: 2626, mode: `MaxEncodedLen`) + fn execute_trade() -> Weight { + // Proof Size summary in bytes: + // Measured: `1228` + // Estimated: `4102` + // Minimum execution time: 172_460_000 picoseconds. + Weight::from_parts(174_840_000, 4102) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(145), added: 2620, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:1) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + fn force_set_collection() -> Weight { + // Proof Size summary in bytes: + // Measured: `420` + // Estimated: `3610` + // Minimum execution time: 10_150_000 picoseconds. + Weight::from_parts(10_390_000, 3610) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + /// Storage: `Dmarket::BalanceManager` (r:1 w:0) + /// Proof: `Dmarket::BalanceManager` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::FeeAddress` (r:1 w:0) + /// Proof: `Dmarket::FeeAddress` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:0) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(637), added: 3112, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::ClosedAsks` (r:1 w:1) + /// Proof: `Dmarket::ClosedAsks` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::ClosedBids` (r:1 w:1) + /// Proof: `Dmarket::ClosedBids` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(145), added: 2620, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(495), added: 2970, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(117), added: 2592, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(151), added: 2626, mode: `MaxEncodedLen`) + fn execute_trade() -> Weight { + // Proof Size summary in bytes: + // Measured: `1228` + // Estimated: `4102` + // Minimum execution time: 172_460_000 picoseconds. + Weight::from_parts(174_840_000, 4102) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } +} diff --git a/pallets/migration/Cargo.toml b/pallets/migration/Cargo.toml new file mode 100644 index 0000000..2471c3f --- /dev/null +++ b/pallets/migration/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "pallet-migration" +version = "0.0.1" +description = "Migration pallet used to recreate the state of marketplace and nfts" +authors = { workspace = true } +homepage = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true, default-features = false, features = [ + "derive", +] } +log = { workspace = true, default-features = false } +scale-info = { workspace = true, default-features = false, features = [ + "derive", +] } +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +pallet-nfts = { workspace = true, default-features = false } +pallet-dmarket = { workspace = true, default-features = false } +pallet-timestamp = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +pallet-balances = { workspace = true, default-features = false } + +# Primitives +account = { workspace = true } + +[dev-dependencies] +sp-io = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-api/std", + "sp-std/std", + "pallet-balances/std", + "pallet-nfts/std", + "pallet-dmarket/std", + "pallet-timestamp/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/migration/src/benchmarking.rs b/pallets/migration/src/benchmarking.rs new file mode 100644 index 0000000..0d636ad --- /dev/null +++ b/pallets/migration/src/benchmarking.rs @@ -0,0 +1,105 @@ +#![cfg(feature = "runtime-benchmarks")] +use super::*; +use crate::Pallet as Migration; +use frame_benchmarking::v2::*; +use frame_support::{ + assert_ok, + dispatch::RawOrigin, + traits::{ + fungible::{Inspect as InspectFungible, Mutate as MutateFungible}, + tokens::nonfungibles_v2::{Create, Mutate}, + }, +}; +use pallet_dmarket::DmarketCollection; +use pallet_dmarket::Pallet as Dmarket; +use pallet_nfts::{ + CollectionConfig, CollectionSettings, ItemConfig, ItemId, MintSettings, Pallet as Nfts, +}; +const SEED: u32 = 0; + +use crate::BenchmarkHelper; + +impl BenchmarkHelper for () +where + CollectionId: From, + ItemId: From, + Moment: From, +{ + fn collection(id: u16) -> CollectionId { + id.into() + } + fn timestamp(value: u64) -> Moment { + value.into() + } +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn get_migrator() -> T::AccountId { + let migrator: T::AccountId = account("migrator", 10, SEED); + whitelist_account!(migrator); + assert_ok!(Migration::::force_set_migrator(RawOrigin::Root.into(), migrator.clone())); + + migrator +} + +fn funded_and_whitelisted_account(name: &'static str, index: u32) -> T::AccountId { + let caller: T::AccountId = account(name, index, SEED); + // Give the account half of the maximum value of the `Balance` type. + let ed = ::Currency::minimum_balance(); + let multiplier = BalanceOf::::from(1000000u32); + + ::Currency::set_balance(&caller, ed * multiplier); + whitelist_account!(caller); + caller +} + +fn mint_nft(nft_id: ItemId) -> T::AccountId { + let caller: T::AccountId = funded_and_whitelisted_account::("tokenOwner", 0); + + let default_config = CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: Some(u128::MAX), + mint_settings: MintSettings::default(), + }; + + assert_ok!(Nfts::::create_collection(&caller, &caller, &default_config)); + let collection = ::BenchmarkHelper::collection(0); + assert_ok!(Nfts::::mint_into(&collection, &nft_id, &caller, &ItemConfig::default(), true)); + caller +} +#[benchmarks()] +pub mod benchmarks { + use super::*; + + #[benchmark] + fn force_set_migrator() { + let migrator: T::AccountId = account("migrator", 0, SEED); + + #[extrinsic_call] + _(RawOrigin::Root, migrator.clone()); + + assert_last_event::(Event::MigratorUpdated(migrator).into()); + } + + #[benchmark] + fn set_item_owner() { + let migrator: T::AccountId = get_migrator::(); + let collection: T::CollectionId = ::BenchmarkHelper::collection(0); + let item: ItemId = 1; + let _ = mint_nft::(item); + let receiver: T::AccountId = account("receiver", 0, SEED); + + assert_ok!(Dmarket::::force_set_collection(RawOrigin::Root.into(), collection)); + assert_eq!(DmarketCollection::::get().unwrap(), collection); + + #[extrinsic_call] + _(RawOrigin::Signed(migrator), item.clone(), receiver.clone()); + + assert_eq!(Nfts::::owner(collection, item), Some(receiver)); + } + + impl_benchmark_test_suite!(Migration, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/pallets/migration/src/lib.rs b/pallets/migration/src/lib.rs new file mode 100644 index 0000000..1123e25 --- /dev/null +++ b/pallets/migration/src/lib.rs @@ -0,0 +1,267 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; +pub use weights::*; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + dispatch::GetDispatchInfo, + traits::{nonfungibles_v2::Transfer, Currency, UnfilteredDispatchable}, + }; + use frame_support::{ + pallet_prelude::*, + traits::{ + fungible::{Inspect, Mutate}, + SortedMembers, + }, + }; + + use frame_system::{ensure_signed, pallet_prelude::*}; + use pallet_dmarket::DmarketCollection; + use pallet_nfts::ItemId; + use pallet_nfts::{ItemConfig, WeightInfo as NftWeight}; + use sp_runtime::traits::StaticLookup; + use sp_std::{vec, vec::Vec}; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + frame_system::Config + + pallet_nfts::Config + + pallet_dmarket::Config + + pallet_timestamp::Config + { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type RuntimeCall: Parameter + + UnfilteredDispatchable + + GetDispatchInfo; + + /// The fungible trait use for balance holds and transfers. + type Currency: Inspect + Mutate; + + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + /// A set of helper functions for benchmarking. + type BenchmarkHelper: BenchmarkHelper; + } + + pub type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; + + pub type NftBalanceOf = <::Currency as Currency< + ::AccountId, + >>::Balance; + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + /// Returns a collection id from a given integer. + fn collection(id: u16) -> CollectionId; + /// Returns an nft id from a given integer. + fn timestamp(value: u64) -> Moment; + } + + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + + #[pallet::storage] + #[pallet::getter(fn migrator)] + pub type Migrator = StorageValue<_, T::AccountId, OptionQuery>; + pub struct MigratorProvider(sp_std::marker::PhantomData); + + impl SortedMembers for MigratorProvider { + fn sorted_members() -> Vec { + if let Some(migrator) = Migrator::::get() { + return vec![migrator]; + } + vec![] + } + + fn contains(who: &T::AccountId) -> bool { + if let Some(migrator) = Migrator::::get() { + return migrator == *who; + } + false + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The pallet's migrator was updated. + MigratorUpdated(T::AccountId), + } + + #[pallet::error] + pub enum Error { + /// The caller is not the migrator account. + NotMigrator, + /// The item with the given collectionId and itemId was not found. + ItemNotFound, + /// Tried to store an account that is already set for this storage value. + AccountAlreadySet, + // Migrator is not set. + MigratorNotSet, + /// The account is already the owner of the item. + AlreadyOwner, + /// The DmarketCollection is not configured. + DmarketCollectionNotSet, + } + + #[pallet::call] + impl Pallet { + /// Sets the migrator role, granting rights to call this pallet's extrinsics. + /// + /// Only the root origin can execute this function. + /// + /// Parameters: + /// - `migrator`: The account ID to be set as the pallet's migrator. + /// + /// Emits MigratorUpdated when successful. + /// + /// Weight: `WeightInfo::force_set_migrator` (defined in the `Config` trait). + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::force_set_migrator())] + pub fn force_set_migrator(origin: OriginFor, migrator: T::AccountId) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + Migrator::::get().as_ref() != Some(&migrator), + Error::::AccountAlreadySet + ); + + Migrator::::put(migrator.clone()); + Self::deposit_event(Event::MigratorUpdated(migrator)); + Ok(()) + } + + /// Transfers a given Nft to an AccountId. + /// + /// Only the migrator origin can execute this function. Migrator will not be charged fees for executing the extrinsic + /// + /// Parameters: + /// - `collection`: Id of the collection for the item. + /// - `item`: Id of the item. + /// - `transfer_to`: AccountId of the user that will receive the item + /// + /// Emits `Transferred` event upon successful execution. + /// + /// Weight: `WeightInfo::set_item_owner` (defined in the `Config` trait). + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::set_item_owner())] + pub fn set_item_owner( + origin: OriginFor, + item: ItemId, + transfer_to: T::AccountId, + ) -> DispatchResultWithPostInfo { + Self::ensure_migrator(origin)?; + let collection = Self::get_dmarket_collection()?; + + let owner = pallet_nfts::Pallet::::owner(collection.clone(), item.clone()) + .ok_or(Error::::ItemNotFound)?; + + ensure!(owner != transfer_to, Error::::AlreadyOwner); + + as Transfer>::transfer( + &collection, + &item, + &transfer_to, + )?; + + Ok(Pays::No.into()) + } + + /// Dispatches a call to pallet-nfts::set_team. + /// + /// Only the migrator origin can execute this function. Migrator will not be charged fees for executing the extrinsic + /// + /// Parameters: + /// - `collection`: The collection whose team should be changed. + /// - `issuer`: The new Issuer of this collection. + /// - `admin`: The new Admin of this collection. + /// - `freezer`: The new Freezer of this collection. + /// + /// Emits `TeamChanged`. + /// + /// Weight: `WeightInfo::set_team` (defined in the `Config` trait). + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::set_team())] + pub fn set_team( + origin: OriginFor, + issuer: Option>, + admin: Option>, + freezer: Option>, + ) -> DispatchResultWithPostInfo { + Self::ensure_migrator(origin.clone())?; + let collection = Self::get_dmarket_collection()?; + + pallet_nfts::Pallet::::set_team(origin, collection, issuer, admin, freezer)?; + + Ok(Pays::No.into()) + } + + /// Dispatches a call to pallet-nfts::force_mint. + /// + /// Only the migrator origin can execute this function. Migrator will not be charged fees for executing the extrinsic + /// + /// Parameters: + /// - `collection`: The collection of the item to be minted. + /// - `item`: An identifier of the new item. + /// - `mint_to`: Account into which the item will be minted. + /// - `item_config`: A config of the new item. + /// + /// Emits `Issued` event when successful. + /// + /// Weight: `WeightInfo::force_mint` (defined in the `Config` trait). + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::force_mint())] + pub fn force_mint( + origin: OriginFor, + item: ItemId, + mint_to: AccountIdLookupOf, + item_config: ItemConfig, + ) -> DispatchResultWithPostInfo { + Self::ensure_migrator(origin.clone())?; + let collection = Self::get_dmarket_collection()?; + + pallet_nfts::Pallet::::force_mint( + origin, + collection, + Some(item), + mint_to, + item_config, + )?; + + Ok(Pays::No.into()) + } + } + impl Pallet { + pub fn ensure_migrator(origin: OriginFor) -> Result<(), DispatchError> { + let sender = ensure_signed(origin.clone())?; + let migrator = Migrator::::get().ok_or(Error::::MigratorNotSet)?; + ensure!(sender == migrator, Error::::NotMigrator); + Ok(()) + } + + pub fn get_dmarket_collection() -> Result { + let dmarket_collection = + DmarketCollection::::get().ok_or(Error::::DmarketCollectionNotSet)?; + Ok(dmarket_collection) + } + } +} + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/pallets/migration/src/mock.rs b/pallets/migration/src/mock.rs new file mode 100644 index 0000000..ef90bec --- /dev/null +++ b/pallets/migration/src/mock.rs @@ -0,0 +1,161 @@ +use frame_support::{ + derive_impl, parameter_types, + traits::{ConstU128, ConstU32, ConstU64}, + PalletId, +}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, +}; + +use account::EthereumSignature; +use system::EnsureSignedBy; + +use crate::{self as pallet_migration}; +use pallet_nfts::PalletFeatures; + +type Signature = EthereumSignature; +type AccountPublic = ::Signer; +type AccountId = ::AccountId; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Migration: pallet_migration, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + Nfts: pallet_nfts, + Dmarket: pallet_dmarket, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +pub type MigratorOrigin = EnsureSignedBy, AccountId>; + +impl pallet_nfts::Config for Test { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type Currency = Balances; + type CreateOrigin = MigratorOrigin; + type ForceOrigin = MigratorOrigin; + type Locker = (); + type CollectionDeposit = ConstU128<0>; + type ItemDeposit = ConstU128<0>; + type MetadataDepositBase = ConstU128<1>; + type AttributeDepositBase = ConstU128<1>; + type DepositPerByte = ConstU128<1>; + type StringLimit = ConstU32<50>; + type KeyLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; + type ApprovalsLimit = ConstU32<10>; + type ItemAttributesApprovalsLimit = ConstU32<2>; + type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxAttributesPerCall = ConstU32<2>; + type Features = Features; + type OffchainSignature = Signature; + type OffchainPublic = AccountPublic; + type WeightInfo = (); + pallet_nfts::runtime_benchmarks_enabled! { + type Helper = (); + } +} + +parameter_types! { + pub const DOMAIN: [u8;8] = *b"MYTH_NET"; +} + +impl pallet_dmarket::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type Signature = Signature; + type Signer = ::Signer; + type Domain = DOMAIN; + type WeightInfo = (); + pallet_dmarket::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = (); + type FreezeIdentifier = (); + type MaxFreezes = (); +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotMigra"); +} + +impl pallet_migration::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type WeightInfo = (); + pallet_migration::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/pallets/migration/src/tests.rs b/pallets/migration/src/tests.rs new file mode 100644 index 0000000..acfbcfa --- /dev/null +++ b/pallets/migration/src/tests.rs @@ -0,0 +1,260 @@ +use crate::{mock::*, *}; +use frame_support::{ + assert_noop, assert_ok, dispatch::Pays, error::BadOrigin, traits::fungible::Mutate, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_nfts::{CollectionConfig, CollectionSettings, ItemConfig, ItemSettings, MintSettings}; + +type AccountIdOf = ::AccountId; +type Balance = ::Balance; +type CollectionId = ::CollectionId; + +fn account(id: u8) -> AccountIdOf { + [id; 32].into() +} + +fn mint_item(item: u128, owner: AccountIdOf) { + Balances::set_balance(&account(1), 100000); + if Nfts::collection_owner(0).is_none() { + assert_ok!(Nfts::create( + RuntimeOrigin::signed(account(1)), + account(1), + collection_config_with_all_settings_enabled() + )); + }; + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, Some(item), owner, None)); +} + +fn collection_config_with_all_settings_enabled( +) -> CollectionConfig, BlockNumberFor, CollectionId> { + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: Some(u128::MAX), + mint_settings: MintSettings::default(), + } +} + +fn default_item_config() -> ItemConfig { + ItemConfig { settings: ItemSettings::all_enabled() } +} + +mod force_set_migrator { + use super::*; + + #[test] + fn force_set_migrator_works() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + assert_eq!(Migration::migrator(), Some(account(1))); + }) + } + + #[test] + fn fails_no_root() { + new_test_ext().execute_with(|| { + assert_noop!( + Migration::force_set_migrator(RuntimeOrigin::signed(account(1)), account(1)), + BadOrigin + ); + }) + } +} + +mod set_item_owner { + use super::*; + + #[test] + fn sender_is_not_migrator_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + mint_item(1, account(1)); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), 0)); + + assert_noop!( + Migration::set_item_owner(RuntimeOrigin::signed(account(2)), 0, account(2)), + Error::::NotMigrator + ); + }) + } + + #[test] + fn dmarket_collection_not_set() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + assert_noop!( + Migration::set_item_owner(RuntimeOrigin::signed(account(1)), 0, account(2)), + Error::::DmarketCollectionNotSet + ); + }) + } + + #[test] + fn item_not_found_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + mint_item(1, account(1)); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), 0)); + + assert_noop!( + Migration::set_item_owner(RuntimeOrigin::signed(account(1)), 0, account(2)), + Error::::ItemNotFound + ); + }) + } + + #[test] + fn already_owner_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + mint_item(1, account(1)); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), 0)); + + assert_noop!( + Migration::set_item_owner(RuntimeOrigin::signed(account(1)), 1, account(1)), + Error::::AlreadyOwner + ); + }) + } + + #[test] + fn set_item_owner_passes() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + mint_item(1, account(1)); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), 0)); + + let res = Migration::set_item_owner(RuntimeOrigin::signed(account(1)), 1, account(2)); + assert!(res.is_ok()); + assert_eq!(res.unwrap().pays_fee, Pays::No); + + assert_eq!(Nfts::owner(0, 1), Some(account(2))); + }) + } +} + +mod set_team { + use super::*; + + #[test] + fn sender_is_not_migrator_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + assert_noop!( + Migration::set_team( + RuntimeOrigin::signed(account(2)), + Some(account(3)), + Some(account(3)), + Some(account(3)) + ), + Error::::NotMigrator + ); + }) + } + + #[test] + fn dmarket_collection_not_set() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(2))); + assert_ok!(Nfts::force_create( + RuntimeOrigin::signed(account(2)), + account(3), + collection_config_with_all_settings_enabled() + )); + + assert_noop!( + Migration::set_team( + RuntimeOrigin::signed(account(2)), + Some(account(3)), + Some(account(3)), + Some(account(3)), + ), + Error::::DmarketCollectionNotSet + ); + }) + } + + #[test] + fn set_team_redispatch_works() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(2))); + assert_ok!(Nfts::force_create( + RuntimeOrigin::signed(account(2)), + account(3), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), 0)); + + let res = Migration::set_team( + RuntimeOrigin::signed(account(2)), + Some(account(3)), + Some(account(3)), + Some(account(3)), + ); + assert!(res.is_ok()); + assert_eq!(res.unwrap().pays_fee, Pays::No) + }) + } +} + +mod force_mint { + use super::*; + + #[test] + fn sender_is_not_migrator_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(1))); + assert_noop!( + Migration::force_mint( + RuntimeOrigin::signed(account(2)), + 0, + account(3), + default_item_config() + ), + Error::::NotMigrator + ); + }) + } + + #[test] + fn dmarket_collection_not_set() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(2))); + assert_ok!(Nfts::force_create( + RuntimeOrigin::signed(account(2)), + account(3), + collection_config_with_all_settings_enabled() + )); + + assert_noop!( + Migration::force_mint( + RuntimeOrigin::signed(account(2)), + 1, + account(3), + default_item_config(), + ), + Error::::DmarketCollectionNotSet + ); + }) + } + #[test] + fn force_mint_redispatch_works() { + new_test_ext().execute_with(|| { + assert_ok!(Migration::force_set_migrator(RuntimeOrigin::root(), account(2))); + assert_ok!(Nfts::force_create( + RuntimeOrigin::signed(account(2)), + account(3), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Dmarket::force_set_collection(RuntimeOrigin::root(), 0)); + + let res = Migration::force_mint( + RuntimeOrigin::signed(account(2)), + 1, + account(3), + default_item_config(), + ); + assert!(res.is_ok()); + assert_eq!(res.unwrap().pays_fee, Pays::No) + }) + } +} diff --git a/pallets/migration/src/weights.rs b/pallets/migration/src/weights.rs new file mode 100644 index 0000000..efe14be --- /dev/null +++ b/pallets/migration/src/weights.rs @@ -0,0 +1,132 @@ + +//! Autogenerated weights for `pallet_migration` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-07-25, STEPS: `10`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `pop-os`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/mythos-node +// benchmark +// pallet +// --steps +// 10 +// --template +// ./.maintain/template.hbs +// --repeat +// 20 +// --extrinsic +// * +// --wasm-execution +// compiled +// --heap-pages +// 4096 +// --pallet +// pallet_migration +// --output +// ./pallets/migration/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_migration`. +pub trait WeightInfo { + fn force_set_migrator() -> Weight; + fn set_item_owner() -> Weight; +} + +/// Weights for `pallet_migration` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); + impl WeightInfo for SubstrateWeight { + /// Storage: `Migration::Migrator` (r:1 w:1) + /// Proof: `Migration::Migrator` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + fn force_set_migrator() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `1505` + // Minimum execution time: 4_220_000 picoseconds. + Weight::from_parts(4_420_000, 1505) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Migration::Migrator` (r:1 w:0) + /// Proof: `Migration::Migrator` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:0) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(637), added: 3112, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(169), added: 2644, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(495), added: 2970, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(117), added: 2592, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(151), added: 2626, mode: `MaxEncodedLen`) + fn set_item_owner() -> Weight { + // Proof Size summary in bytes: + // Measured: `900` + // Estimated: `4102` + // Minimum execution time: 37_960_000 picoseconds. + Weight::from_parts(39_180_000, 4102) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Migration::Migrator` (r:1 w:1) + /// Proof: `Migration::Migrator` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + fn force_set_migrator() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `1505` + // Minimum execution time: 4_220_000 picoseconds. + Weight::from_parts(4_420_000, 1505) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Migration::Migrator` (r:1 w:0) + /// Proof: `Migration::Migrator` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:0) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(637), added: 3112, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(169), added: 2644, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(495), added: 2970, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(117), added: 2592, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(151), added: 2626, mode: `MaxEncodedLen`) + fn set_item_owner() -> Weight { + // Proof Size summary in bytes: + // Measured: `900` + // Estimated: `4102` + // Minimum execution time: 37_960_000 picoseconds. + Weight::from_parts(39_180_000, 4102) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } +} diff --git a/runtime/testnet/Cargo.toml b/runtime/testnet/Cargo.toml index 0f40031..1983e43 100644 --- a/runtime/testnet/Cargo.toml +++ b/runtime/testnet/Cargo.toml @@ -23,7 +23,9 @@ smallvec = { workspace = true } # Local runtime-common = { workspace = true, default-features = false } +pallet-dmarket = { workspace = true, default-features = false } pallet-marketplace = { workspace = true, default-features = false } +pallet-migration = { workspace = true, default-features = false } pallet-multibatching = { workspace = true, default-features = false } xcm-primitives = { path = "../../primitives/xcm", default-features = false } pallet-escrow = { workspace = true, default-features = false } @@ -120,8 +122,10 @@ std = [ "pallet-balances/std", "pallet-collator-selection/std", "pallet-collective/std", + "pallet-dmarket/std", "pallet-multibatching/std", "pallet-marketplace/std", + "pallet-migration/std", "pallet-multisig/std", "pallet-nfts/std", "pallet-session/std", @@ -170,7 +174,9 @@ runtime-benchmarks = [ "pallet-collator-selection/runtime-benchmarks", "pallet-collective/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", + "pallet-dmarket/runtime-benchmarks", "pallet-marketplace/runtime-benchmarks", + "pallet-migration/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-multibatching/runtime-benchmarks", "pallet-nfts/runtime-benchmarks", @@ -204,8 +210,10 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-collator-selection/try-runtime", "pallet-collective/try-runtime", + "pallet-dmarket/try-runtime", "pallet-multibatching/try-runtime", "pallet-marketplace/try-runtime", + "pallet-migration/try-runtime", "pallet-multisig/try-runtime", "pallet-nfts/try-runtime", "pallet-session/try-runtime", diff --git a/runtime/testnet/src/lib.rs b/runtime/testnet/src/lib.rs index f4381e0..a93a37d 100644 --- a/runtime/testnet/src/lib.rs +++ b/runtime/testnet/src/lib.rs @@ -41,9 +41,11 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; +use pallet_dmarket::{Item, TradeParams}; use pallet_nfts::PalletFeatures; use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use polkadot_primitives::Moment; pub use runtime_common::{ AccountId, Balance, BlockNumber, DealWithFees, Hash, IncrementableU256, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, @@ -112,7 +114,6 @@ pub type Executive = frame_executive::Executive< >; pub mod fee { - use super::{Balance, ExtrinsicBaseWeight, MILLI_MUSE, MILLI_ROC}; use frame_support::weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, FeePolynomial, Weight, WeightToFeeCoefficient, @@ -224,7 +225,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("muse"), impl_name: create_runtime_str!("muse"), authoring_version: 1, - spec_version: 1012, + spec_version: 1013, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -654,6 +655,27 @@ impl pallet_marketplace::Config for Runtime { type BenchmarkHelper = (); } +impl pallet_dmarket::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type Signature = Signature; + type Signer = ::Signer; + type Domain = DOMAIN; + type WeightInfo = weights::pallet_dmarket::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +impl pallet_migration::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type WeightInfo = weights::pallet_migration::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + parameter_types! { pub const ProxyDepositBase: Balance = deposit(1, 8); pub const ProxyDepositFactor: Balance = deposit(0, 33); @@ -847,9 +869,11 @@ construct_runtime!( // Other pallets Proxy: pallet_proxy = 40, Vesting: pallet_vesting = 41, + Migration: pallet_migration = 42, Escrow: pallet_escrow = 50, MythProxy: pallet_myth_proxy = 51, + Dmarket: pallet_dmarket = 52, } ); @@ -921,11 +945,13 @@ mod benches { [pallet_collator_selection, CollatorSelection] [pallet_nfts, Nfts] [pallet_marketplace, Marketplace] + [pallet_migration, Migration] [pallet_proxy, Proxy] [pallet_escrow, Escrow] [pallet_vesting, Vesting] [pallet_collective, Council] [pallet_myth_proxy, MythProxy] + [pallet_dmarket, Dmarket] ); } @@ -1081,6 +1107,19 @@ impl_runtime_apis! { } } + impl pallet_dmarket::DmarketApi for Runtime { + fn hash_ask_bid_data(trade: TradeParams)-> (Hash, Hash) { + Dmarket::hash_ask_bid_data(&trade) + } + fn get_ask_message(caller: AccountId, fee_address: AccountId, trade: TradeParams) -> Vec { + Dmarket::get_ask_message(&caller, &fee_address, &trade) + } + fn get_bid_message(caller: AccountId, fee_address: AccountId, trade: TradeParams) -> Vec { + Dmarket::get_bid_message(&caller, &fee_address, &trade) + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { fn can_build_upon( included_hash: ::Hash, diff --git a/runtime/testnet/src/weights/mod.rs b/runtime/testnet/src/weights/mod.rs index c3fe409..538d296 100644 --- a/runtime/testnet/src/weights/mod.rs +++ b/runtime/testnet/src/weights/mod.rs @@ -33,9 +33,11 @@ pub mod frame_system; pub mod pallet_balances; pub mod pallet_collator_selection; pub mod pallet_collective; +pub mod pallet_dmarket; pub mod pallet_escrow; pub mod pallet_marketplace; pub mod pallet_message_queue; +pub mod pallet_migration; pub mod pallet_multibatching; pub mod pallet_multisig; pub mod pallet_myth_proxy; diff --git a/runtime/testnet/src/weights/pallet_dmarket.rs b/runtime/testnet/src/weights/pallet_dmarket.rs new file mode 100644 index 0000000..5a354b8 --- /dev/null +++ b/runtime/testnet/src/weights/pallet_dmarket.rs @@ -0,0 +1,88 @@ + +//! Autogenerated weights for `pallet_dmarket` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bdl-ref-hw`, CPU: `AMD EPYC 7232P 8-Core Processor` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local-v")`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/mythos-node +// benchmark +// pallet +// --chain +// local-v +// --pallet +// pallet_dmarket +// --extrinsic +// * +// --wasm-execution +// compiled +// --steps +// 50 +// --repeat +// 20 +// --output +// ./runtime/testnet/src/weights/pallet_dmarket.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_dmarket`. +pub struct WeightInfo(PhantomData); +impl pallet_dmarket::WeightInfo for WeightInfo { + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(169), added: 2644, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:1) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + fn force_set_collection() -> Weight { + // Proof Size summary in bytes: + // Measured: `444` + // Estimated: `3634` + // Minimum execution time: 22_571_000 picoseconds. + Weight::from_parts(23_310_000, 0) + .saturating_add(Weight::from_parts(0, 3634)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:0) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(637), added: 3112, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::ClosedAsks` (r:1 w:1) + /// Proof: `Dmarket::ClosedAsks` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::ClosedBids` (r:1 w:1) + /// Proof: `Dmarket::ClosedBids` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(169), added: 2644, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(495), added: 2970, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(117), added: 2592, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(151), added: 2626, mode: `MaxEncodedLen`) + fn execute_trade() -> Weight { + // Proof Size summary in bytes: + // Measured: `1205` + // Estimated: `4102` + // Minimum execution time: 321_303_000 picoseconds. + Weight::from_parts(325_174_000, 0) + .saturating_add(Weight::from_parts(0, 4102)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(7)) + } +} diff --git a/runtime/testnet/src/weights/pallet_migration.rs b/runtime/testnet/src/weights/pallet_migration.rs new file mode 100644 index 0000000..8b0ccf2 --- /dev/null +++ b/runtime/testnet/src/weights/pallet_migration.rs @@ -0,0 +1,82 @@ + +//! Autogenerated weights for `pallet_migration` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bdl-ref`, CPU: `AMD EPYC 7232P 8-Core Processor` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local-v")`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/mythos-node +// benchmark +// pallet +// --chain +// local-v +// --pallet +// pallet_migration +// --extrinsic +// * +// --wasm-execution +// compiled +// --steps +// 50 +// --repeat +// 20 +// --output +// ./runtime/testnet/src/weights/pallet_migration.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_migration`. +pub struct WeightInfo(PhantomData); +impl pallet_migration::WeightInfo for WeightInfo { + /// Storage: `Migration::Migrator` (r:1 w:1) + /// Proof: `Migration::Migrator` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + fn force_set_migrator() -> Weight { + // Proof Size summary in bytes: + // Measured: `3` + // Estimated: `1505` + // Minimum execution time: 10_780_000 picoseconds. + Weight::from_parts(11_620_000, 0) + .saturating_add(Weight::from_parts(0, 1505)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Migration::Migrator` (r:1 w:0) + /// Proof: `Migration::Migrator` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Dmarket::DmarketCollection` (r:1 w:0) + /// Proof: `Dmarket::DmarketCollection` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Item` (r:1 w:1) + /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(637), added: 3112, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Collection` (r:1 w:0) + /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(169), added: 2644, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Attribute` (r:1 w:0) + /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(495), added: 2970, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) + /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `Nfts::Account` (r:0 w:2) + /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) + /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(117), added: 2592, mode: `MaxEncodedLen`) + /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) + /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(151), added: 2626, mode: `MaxEncodedLen`) + fn set_item_owner() -> Weight { + // Proof Size summary in bytes: + // Measured: `900` + // Estimated: `4102` + // Minimum execution time: 82_700_000 picoseconds. + Weight::from_parts(84_051_000, 0) + .saturating_add(Weight::from_parts(0, 4102)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(5)) + } +}