-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #212 from paritytech/feat/dmarket-pallet-mock
Add Dmarket and modified Migration pallet to testnet
- Loading branch information
Showing
21 changed files
with
2,801 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T> = | ||
<<T as Config>::Currency as InspectFungible<<T as frame_system::Config>::AccountId>>::Balance; | ||
|
||
impl<CollectionId, Moment> BenchmarkHelper<CollectionId, Moment> for () | ||
where | ||
CollectionId: From<u16>, | ||
ItemId: From<u16>, | ||
Moment: From<u64>, | ||
{ | ||
fn collection(id: u16) -> CollectionId { | ||
id.into() | ||
} | ||
fn timestamp(value: u64) -> Moment { | ||
value.into() | ||
} | ||
} | ||
|
||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { | ||
frame_system::Pallet::<T>::assert_last_event(generic_event.into()); | ||
} | ||
|
||
fn funded_and_whitelisted_account<T: Config>(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 = <T as Config>::Currency::minimum_balance(); | ||
let multiplier = BalanceOf::<T>::from(1000000u32); | ||
|
||
<T as Config>::Currency::set_balance(&caller, ed * multiplier); | ||
whitelist_account!(caller); | ||
caller | ||
} | ||
|
||
fn mint_nft<T: Config>(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::<T>::create_collection(&caller, &caller, &default_config)); | ||
let collection = T::BenchmarkHelper::collection(0); | ||
assert_ok!(Nfts::<T>::mint_into(&collection, &nft_id, &caller, &ItemConfig::default(), true)); | ||
} | ||
|
||
#[benchmarks(where T::AccountId: From<AccountId20>, T::Signature: From<EthereumSignature>)] | ||
pub mod benchmarks { | ||
use super::*; | ||
use account::{AccountId20, EthereumSignature, EthereumSigner}; | ||
use pallet_timestamp::Pallet as Timestamp; | ||
|
||
use sp_runtime::traits::IdentifyAccount; | ||
|
||
fn sign_trade<T: Config>( | ||
sender: &T::AccountId, | ||
fee_address: &T::AccountId, | ||
trade: &TradeParamsOf<T>, | ||
seller_signer: Public, | ||
buyer_signer: Public, | ||
) -> TradeSignatures<T::Signature> | ||
where | ||
T::Signature: From<EthereumSignature>, | ||
{ | ||
let ask_message: Vec<u8> = Dmarket::<T>::get_ask_message(sender, fee_address, trade); | ||
let ask_hashed = keccak_256(&ask_message); | ||
|
||
let bid_message: Vec<u8> = Dmarket::<T>::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: Config>() -> (T::AccountId, Public, T::AccountId, Public) | ||
where | ||
T::AccountId: From<AccountId20>, | ||
{ | ||
let ed = <T as Config>::Currency::minimum_balance(); | ||
let multiplier = BalanceOf::<T>::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); | ||
|
||
<T as Config>::Currency::set_balance(&seller, ed * multiplier); | ||
<T as Config>::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::<T>("caller", 0); | ||
let _ = mint_nft::<T>(1, caller); | ||
|
||
#[extrinsic_call] | ||
_(RawOrigin::Root, collection_id); | ||
|
||
assert_last_event::<T>(Event::CollectionUpdated { collection_id }.into()); | ||
} | ||
|
||
#[benchmark] | ||
fn execute_trade() { | ||
let sender: T::AccountId = funded_and_whitelisted_account::<T>("sender", 0); | ||
let (seller, seller_public, buyer, buyer_public) = trade_participants::<T>(); | ||
let fee_address: T::AccountId = funded_and_whitelisted_account::<T>("fee_address", 0); | ||
|
||
let collection_id = T::BenchmarkHelper::collection(0); | ||
let item = 1; | ||
mint_nft::<T>(item, seller.clone()); | ||
assert_ok!(Dmarket::<T>::force_set_collection(RawOrigin::Root.into(), collection_id)); | ||
|
||
let expiration = Timestamp::<T>::get() + T::BenchmarkHelper::timestamp(1000); | ||
let trade = TradeParams { | ||
price: BalanceOf::<T>::from(100u16), | ||
fee: BalanceOf::<T>::from(1u8), | ||
ask_expiration: expiration, | ||
bid_expiration: expiration, | ||
item, | ||
}; | ||
let signatures = | ||
sign_trade::<T>(&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::<T>( | ||
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); | ||
} |
Oops, something went wrong.