Skip to content

Commit

Permalink
Merge pull request #212 from paritytech/feat/dmarket-pallet-mock
Browse files Browse the repository at this point in the history
Add Dmarket and modified Migration pallet to testnet
  • Loading branch information
valentinfernandez1 authored Aug 21, 2024
2 parents 4884f3e + 53258aa commit 3345cbf
Show file tree
Hide file tree
Showing 21 changed files with 2,801 additions and 2 deletions.
46 changes: 46 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
59 changes: 59 additions & 0 deletions pallets/dmarket/Cargo.toml
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"]
30 changes: 30 additions & 0 deletions pallets/dmarket/README.md
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.
179 changes: 179 additions & 0 deletions pallets/dmarket/src/benchmarking.rs
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);
}
Loading

0 comments on commit 3345cbf

Please sign in to comment.