--- a/Cargo.lock +++ b/Cargo.lock @@ -5097,6 +5097,7 @@ "frame-system-rpc-runtime-api", "hex-literal", "nft-data-structs", + "orml-vesting", "pallet-aura", "pallet-balances", "pallet-common", @@ -5109,7 +5110,6 @@ "pallet-fungible", "pallet-inflation", "pallet-nft", - "pallet-nft-transaction-payment", "pallet-nonfungible", "pallet-randomness-collective-flip", "pallet-refungible", @@ -5120,7 +5120,6 @@ "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", "pallet-unq-scheduler", - "pallet-vesting", "pallet-xcm", "parachain-info", "parity-scale-codec", @@ -5142,6 +5141,7 @@ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", + "up-evm-mapping", "up-rpc", "xcm", "xcm-builder", @@ -5308,6 +5308,21 @@ ] [[package]] +name = "orml-vesting" +version = "0.4.1-dev" +source = "git+https://github.com/UniqueNetwork/open-runtime-module-library#d69f226e332ae29b7b33d53d2f06f309d2986ea0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] name = "owning_ref" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5575,6 +5590,7 @@ "sp-core", "sp-runtime", "sp-std", + "up-evm-mapping", ] [[package]] @@ -5824,6 +5840,7 @@ "sp-io", "sp-runtime", "sp-std", + "up-evm-mapping", "up-sponsorship", ] @@ -6072,24 +6089,7 @@ "sp-io", "sp-runtime", "sp-std", - "up-sponsorship", -] - -[[package]] -name = "pallet-nft-transaction-payment" -version = "3.0.0" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-transaction-payment", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", + "up-evm-mapping", "up-sponsorship", ] @@ -11832,6 +11832,14 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] +name = "up-evm-mapping" +version = "0.1.0" +dependencies = [ + "frame-support", + "sp-core", +] + +[[package]] name = "up-rpc" version = "0.1.0" dependencies = [ --- a/launch-config.json +++ b/launch-config.json @@ -9,7 +9,6 @@ "rpcPort": 9843, "port": 30444, "flags": [ - "--rpc-port=9843", "-lparachain::candidate_validation=debug" ] }, @@ -19,7 +18,6 @@ "rpcPort": 9854, "port": 30555, "flags": [ - "--rpc-port=9854", "-lparachain::candidate_validation=debug" ] }, @@ -29,7 +27,6 @@ "rpcPort": 9865, "port": 30666, "flags": [ - "--rpc-port=9865", "-lparachain::candidate_validation=debug" ] }, @@ -39,7 +36,6 @@ "rpcPort": 9876, "port": 30777, "flags": [ - "--rpc-port=9876", "-lparachain::candidate_validation=debug" ] } @@ -70,7 +66,6 @@ "name": "alice", "flags": [ "--rpc-cors=all", - "--rpc-port=9933", "--unsafe-rpc-external", "--unsafe-ws-external" ] @@ -82,7 +77,6 @@ "name": "bob", "flags": [ "--rpc-cors=all", - "--rpc-port=9934", "--unsafe-rpc-external", "--unsafe-ws-external" ] --- a/pallets/common/Cargo.toml +++ b/pallets/common/Cargo.toml @@ -15,10 +15,10 @@ sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } sp-std = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } +up-evm-mapping = { default-features = false, path = '../../primitives/evm-mapping' } nft-data-structs = { default-features = false, path = '../../primitives/nft' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } evm-coder = { default-features = false, path = '../../crates/evm-coder' } - pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.12" } serde = { version = "1.0.130", default-features = false } scale-info = { version = "1.0.0", default-features = false, features = [ @@ -32,6 +32,7 @@ "frame-system/std", "sp-runtime/std", "sp-std/std", + "up-evm-mapping/std", "nft-data-structs/std", "pallet-evm/std", ] --- a/pallets/common/src/account.rs +++ b/pallets/common/src/account.rs @@ -2,12 +2,12 @@ use codec::{Encode, EncodeLike, Decode}; use sp_core::H160; use scale_info::{Type, TypeInfo}; -use sp_core::crypto::AccountId32; use core::cmp::Ordering; use serde::{Serialize, Deserialize}; use pallet_evm::AddressMapping; use sp_std::vec::Vec; use sp_std::clone::Clone; +use up_evm_mapping::EvmBackwardsAddressMapping; pub trait CrossAccountId: Encode + EncodeLike + Decode + TypeInfo + Clone + PartialEq + Ord + core::fmt::Debug + Default @@ -174,19 +174,5 @@ } else { BasicCrossAccountIdRepr::Substrate(v.as_sub().clone()) } - } -} - -pub trait EvmBackwardsAddressMapping { - fn from_account_id(account_id: AccountId) -> H160; -} - -/// Should have same mapping as EnsureAddressTruncated -pub struct MapBackwardsAddressTruncated; -impl EvmBackwardsAddressMapping for MapBackwardsAddressTruncated { - fn from_account_id(account_id: AccountId32) -> H160 { - let mut out = [0; 20]; - out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]); - H160(out) } } --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -141,7 +141,7 @@ pub mod pallet { use super::*; use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key}; - use account::{EvmBackwardsAddressMapping, CrossAccountId}; + use account::CrossAccountId; use frame_support::traits::Currency; use nft_data_structs::TokenId; use scale_info::TypeInfo; @@ -153,7 +153,7 @@ type CrossAccountId: CrossAccountId; type EvmAddressMapping: pallet_evm::AddressMapping; - type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping; + type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping; type Currency: Currency; type CollectionCreationPrice: Get< --- a/pallets/evm-contract-helpers/src/eth.rs +++ b/pallets/evm-contract-helpers/src/eth.rs @@ -59,7 +59,8 @@ fn allowed(&self, contract_address: address, user: address) -> Result { self.0.consume_sload()?; - Ok(>::allowed(contract_address, user, true)) + Ok(>::allowed(contract_address, user) + || !>::get(contract_address)) } fn allowlist_enabled(&self, contract_address: address) -> Result { @@ -113,7 +114,7 @@ value: sp_core::U256, ) -> Option { // TODO: Extract to another OnMethodCall handler - if !>::allowed(*target, *source, true) { + if >::get(target) && !>::allowed(*target, *source) { return Some(PrecompileOutput { exit_status: ExitReason::Revert(ExitRevert::Reverted), cost: 0, @@ -151,22 +152,26 @@ pub struct HelpersContractSponsoring(PhantomData<*const T>); impl SponsorshipHandler)> for HelpersContractSponsoring { fn get_sponsor(who: &H160, call: &(H160, Vec)) -> Option { - if >::get(&call.0) && >::allowed(call.0, *who, false) { - let block_number = >::block_number() as T::BlockNumber; - if let Some(last_tx_block) = >::get(&call.0, who) { - let rate_limit = >::get(&call.0); - let limit_time = last_tx_block + rate_limit; + if !>::get(&call.0) { + return None; + } + if !>::allowed(call.0, *who) { + return None; + } + let block_number = >::block_number() as T::BlockNumber; - if block_number > limit_time { - >::insert(&call.0, who, block_number); - return Some(call.0); - } - } else { - >::insert(&call.0, who, block_number); - return Some(call.0); + if let Some(last_tx_block) = >::get(&call.0, who) { + let limit = >::get(&call.0); + + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; } } - None + + >::insert(&call.0, who, block_number); + + Some(call.0) } } --- a/pallets/evm-contract-helpers/src/lib.rs +++ b/pallets/evm-contract-helpers/src/lib.rs @@ -76,11 +76,7 @@ >::insert(contract, rate_limit); } - /// Default is returned if allowlist is disabled - pub fn allowed(contract: H160, user: H160, default: bool) -> bool { - if !>::get(contract) { - return default; - } + pub fn allowed(contract: H160, user: H160) -> bool { >::get(&contract, &user) || >::get(&contract) == user } --- a/pallets/evm-transaction-payment/Cargo.toml +++ b/pallets/evm-transaction-payment/Cargo.toml @@ -15,6 +15,7 @@ fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.12" } pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.12" } up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring" } +up-evm-mapping = { default-features = false, path = "../../primitives/evm-mapping" } [dependencies.codec] default-features = false @@ -35,4 +36,5 @@ "pallet-ethereum/std", "fp-evm/std", "up-sponsorship/std", + "up-evm-mapping/std", ] --- a/pallets/evm-transaction-payment/src/lib.rs +++ b/pallets/evm-transaction-payment/src/lib.rs @@ -1,98 +1,140 @@ #![cfg_attr(not(feature = "std"), no_std)] +use core::marker::PhantomData; +use fp_evm::WithdrawReason; +use frame_support::traits::{Currency, IsSubType}; pub use pallet::*; +use pallet_evm::{EVMCurrencyAdapter, EnsureAddressOrigin}; +use sp_core::{H160, U256}; +use sp_runtime::TransactionOutcome; +use up_sponsorship::SponsorshipHandler; +use up_evm_mapping::EvmBackwardsAddressMapping; +use pallet_evm::AddressMapping; #[frame_support::pallet] pub mod pallet { - use core::marker::PhantomData; + use super::*; + use frame_support::traits::Currency; - use pallet_evm::EVMCurrencyAdapter; - use fp_evm::WithdrawReason; - use sp_core::{H160, U256}; - use sp_runtime::TransactionOutcome; - use up_sponsorship::SponsorshipHandler; use sp_std::vec::Vec; - type NegativeImbalanceOf = - ::AccountId>>::NegativeImbalance; - #[pallet::config] pub trait Config: frame_system::Config { - type SponsorshipHandler: SponsorshipHandler)>; + type EvmSponsorshipHandler: SponsorshipHandler)>; type Currency: Currency; + type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping; + type EvmAddressMapping: AddressMapping; } #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); +} - pub struct ChargeEvmLiquidityInfo - where - T: Config, - T: pallet_evm::Config, - { - who: H160, - negative_imbalance: NegativeImbalanceOf<::Currency, T>, - } +type NegativeImbalanceOf = + ::AccountId>>::NegativeImbalance; - pub struct TransactionValidityHack(PhantomData<*const T>); - impl fp_evm::TransactionValidityHack for TransactionValidityHack { - fn who_pays_fee(origin: H160, reason: &WithdrawReason) -> Option { - match reason { - WithdrawReason::Call { target, input } => { - // This method is only used for checking, we shouldn't touch storage in it - frame_support::storage::with_transaction(|| { - TransactionOutcome::Rollback(T::SponsorshipHandler::get_sponsor( - &origin, - &(*target, input.clone()), - )) - }) - } - _ => None, +pub struct ChargeEvmLiquidityInfo +where + T: Config, + T: pallet_evm::Config, +{ + who: H160, + negative_imbalance: NegativeImbalanceOf<::Currency, T>, +} + +pub struct TransactionValidityHack(PhantomData<*const T>); +impl fp_evm::TransactionValidityHack for TransactionValidityHack { + fn who_pays_fee(origin: H160, reason: &WithdrawReason) -> Option { + match reason { + WithdrawReason::Call { target, input } => { + // This method is only used for checking, we shouldn't touch storage in it + frame_support::storage::with_transaction(|| { + TransactionOutcome::Rollback(T::EvmSponsorshipHandler::get_sponsor( + &origin, + &(*target, input.clone()), + )) + }) } + _ => None, + } + } +} +pub struct OnChargeTransaction(PhantomData<*const T>); +impl pallet_evm::OnChargeEVMTransaction for OnChargeTransaction +where + T: Config, + T: pallet_evm::Config, +{ + type LiquidityInfo = Option>; + + fn withdraw_fee( + who: &H160, + reason: WithdrawReason, + fee: U256, + ) -> core::result::Result> { + let mut who_pays_fee = *who; + if let WithdrawReason::Call { target, input } = &reason { + who_pays_fee = T::EvmSponsorshipHandler::get_sponsor(who, &(*target, input.clone())) + .unwrap_or(who_pays_fee); } + let negative_imbalance = EVMCurrencyAdapter::<::Currency, ()>::withdraw_fee( + &who_pays_fee, + reason, + fee, + )?; + Ok(negative_imbalance.map(|i| ChargeEvmLiquidityInfo { + who: who_pays_fee, + negative_imbalance: i, + })) } - pub struct OnChargeTransaction(PhantomData<*const T>); - impl pallet_evm::OnChargeEVMTransaction for OnChargeTransaction - where - T: Config, - T: pallet_evm::Config, - { - type LiquidityInfo = Option>; + fn correct_and_deposit_fee( + who: &H160, + corrected_fee: U256, + already_withdrawn: Self::LiquidityInfo, + ) { + ::Currency, ()> as pallet_evm::OnChargeEVMTransaction>::correct_and_deposit_fee( + &already_withdrawn.as_ref().map(|e| e.who).unwrap_or(*who), + corrected_fee, + already_withdrawn.map(|e| e.negative_imbalance), + ) + } +} - fn withdraw_fee( - who: &H160, - reason: WithdrawReason, - fee: U256, - ) -> core::result::Result> { - let mut who_pays_fee = *who; - if let WithdrawReason::Call { target, input } = &reason { - who_pays_fee = T::SponsorshipHandler::get_sponsor(who, &(*target, input.clone())) - .unwrap_or(who_pays_fee); +/// Implements sponsoring for evm calls performed from pallet-evm (via api.tx.ethereum.transact/api.tx.evm.call) +pub struct BridgeSponsorshipHandler(PhantomData); +impl SponsorshipHandler for BridgeSponsorshipHandler +where + T: Config + pallet_evm::Config, + C: IsSubType>, +{ + fn get_sponsor(who: &T::AccountId, call: &C) -> Option { + match call.is_sub_type()? { + pallet_evm::Call::call { + source, + target, + input, + .. + } => { + let _ = T::CallOrigin::ensure_address_origin( + source, + >::Signed(who.clone()).into(), + ) + .ok()?; + let who = T::EvmBackwardsAddressMapping::from_account_id(who.clone()); + // Effects from EvmSponsorshipHandler are applied in OnChargeEvmTransaction by pallet_evm::runner + // TODO: Should we implement simulation mode (test, but do not apply effects) in `up-sponsorship`? + let sponsor = frame_support::storage::with_transaction(|| { + TransactionOutcome::Rollback(T::EvmSponsorshipHandler::get_sponsor( + &who, + &(target.clone(), input.clone()), + )) + })?; + let sponsor = T::EvmAddressMapping::into_account_id(sponsor); + Some(sponsor) } - let negative_imbalance = - EVMCurrencyAdapter::<::Currency, ()>::withdraw_fee( - &who_pays_fee, - reason, - fee, - )?; - Ok(negative_imbalance.map(|i| ChargeEvmLiquidityInfo { - who: who_pays_fee, - negative_imbalance: i, - })) - } - - fn correct_and_deposit_fee( - who: &H160, - corrected_fee: U256, - already_withdrawn: Self::LiquidityInfo, - ) { - ::Currency, ()> as pallet_evm::OnChargeEVMTransaction>::correct_and_deposit_fee( - &already_withdrawn.as_ref().map(|e| e.who).unwrap_or(*who), - corrected_fee, - already_withdrawn.map(|e| e.negative_imbalance), - ) + _ => None, } } } --- a/pallets/nft-transaction-payment/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -authors = ['Substrate DevHub '] -description = 'Unqiue pallet nft specific transaction payment' -edition = '2018' -homepage = 'https://substrate.io' -license = 'Unlicense' -name = 'pallet-nft-transaction-payment' -repository = 'https://github.com/usetech-llc/nft_private/' -version = '3.0.0' - -[package.metadata.docs.rs] -targets = ['x86_64-unknown-linux-gnu'] - -# alias "parity-scale-code" to "codec" -[dependencies.codec] -default-features = false -features = ['derive'] -package = 'parity-scale-codec' -version = '2.3.0' - -[dependencies] -scale-info = { version = "1.0.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.130", default-features = false } -frame-support = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } -frame-system = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } -pallet-transaction-payment = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } -sp-std = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } -frame-benchmarking = { default-features = false, optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } -sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } -sp-io = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } -sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } - -up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring" } - -[features] -default = ['std'] -std = [ - 'codec/std', - 'serde/std', - 'frame-support/std', - 'frame-system/std', - 'sp-core/std', - 'sp-io/std', - 'pallet-transaction-payment/std', - 'sp-std/std', - 'sp-runtime/std', - 'frame-benchmarking/std', - - 'up-sponsorship/std', -] -runtime-benchmarks = ["frame-benchmarking"] --- a/pallets/nft-transaction-payment/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Nft Transaction Payment - -## Overview - -A module containing the sponsoring logic for paying for sponsored collections - -**NOTE:** The scheduled calls will be dispatched with the default filter -for the origin: namely `frame_system::Config::BaseCallFilter` for all origin -except root which will get no filter. And not the filter contained in origin -use to call `fn schedule`. - -If a call is scheduled using proxy or whatever mecanism which adds filter, -then those filter will not be used when dispatching the schedule call. --- a/pallets/nft-transaction-payment/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -// -// This file is subject to the terms and conditions defined in -// file 'LICENSE', which is part of this source code package. -// - -#![cfg_attr(not(feature = "std"), no_std)] - -#[cfg(feature = "std")] -pub use std::*; - -#[cfg(feature = "std")] -pub use serde::*; - -use frame_support::{decl_module, decl_storage}; -use sp_std::prelude::*; -use up_sponsorship::SponsorshipHandler; - -pub trait Config: frame_system::Config + pallet_transaction_payment::Config { - type SponsorshipHandler: SponsorshipHandler; -} - -decl_storage! { - trait Store for Module as NftTransactionPayment{ - } -} - -decl_module! { - pub struct Module for enum Call - where - origin: T::Origin, - { - } -} - -impl Module { - pub fn withdraw_type(who: &T::AccountId, call: &T::Call) -> Option { - T::SponsorshipHandler::get_sponsor(who, call) - } -} --- a/pallets/nft/Cargo.toml +++ b/pallets/nft/Cargo.toml @@ -34,6 +34,7 @@ 'fp-evm/std', 'nft-data-structs/std', 'up-sponsorship/std', + 'up-evm-mapping/std', 'sp-std/std', 'sp-api/std', 'sp-runtime/std', @@ -135,7 +136,7 @@ sp-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.12" } up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring" } - +up-evm-mapping = { default-features = false, path = "../../primitives/evm-mapping" } evm-coder = { default-features = false, path = "../../crates/evm-coder" } pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" } primitive-types = { version = "0.10.1", default-features = false, features = [ --- a/pallets/nft/src/eth/sponsoring.rs +++ b/pallets/nft/src/eth/sponsoring.rs @@ -1,123 +1,64 @@ //! Implements EVM sponsoring logic via OnChargeEVMTransaction -use crate::{Collection, Config, FungibleTransferBasket, NftTransferBasket}; +use crate::{Config, sponsorship::*}; use evm_coder::{Call, abi::AbiReader}; -use frame_support::{ - storage::{StorageDoubleMap}, -}; -use pallet_common::eth::map_eth_to_id; +use pallet_common::{CollectionHandle, eth::map_eth_to_id}; use sp_core::H160; use sp_std::prelude::*; use up_sponsorship::SponsorshipHandler; use core::marker::PhantomData; use core::convert::TryInto; -use nft_data_structs::{CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT}; -use pallet_common::{ - CollectionById, - account::{CrossAccountId, EvmBackwardsAddressMapping}, -}; +use nft_data_structs::TokenId; +use up_evm_mapping::EvmBackwardsAddressMapping; +use pallet_evm::AddressMapping; use pallet_nonfungible::erc::{UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721Call}; use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call}; -struct AnyError; - -fn try_sponsor( - caller: &H160, - collection_id: CollectionId, - collection: &Collection, - call: &[u8], -) -> Result<(), AnyError> { - let (method_id, mut reader) = AbiReader::new_call(call).map_err(|_| AnyError)?; - match &collection.mode { - crate::CollectionMode::NFT => { - let call: UniqueNFTCall = UniqueNFTCall::parse(method_id, &mut reader) - .map_err(|_| AnyError)? - .ok_or(AnyError)?; - match call { - UniqueNFTCall::ERC721UniqueExtensions(ERC721UniqueExtensionsCall::Transfer { - token_id, - .. - }) - | UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, .. }) => { - let token_id: u32 = token_id.try_into().map_err(|_| AnyError)?; - let block_number = >::block_number() as T::BlockNumber; - let collection_limits = &collection.limits; - let limit = - collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT); - - let mut sponsor = true; - if >::contains_key(collection_id, token_id) { - let last_tx_block = >::get(collection_id, token_id); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsor = false; - } +pub struct NftEthSponsorshipHandler(PhantomData<*const T>); +impl SponsorshipHandler)> for NftEthSponsorshipHandler { + fn get_sponsor(who: &H160, call: &(H160, Vec)) -> Option { + let collection_id = map_eth_to_id(&call.0)?; + let collection = >::new(collection_id)?; + let sponsor = collection.sponsorship.sponsor()?.clone(); + let sponsor = + ::EvmBackwardsAddressMapping::from_account_id(sponsor); + let who = ::EvmAddressMapping::into_account_id(*who); + let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?; + match &collection.mode { + crate::CollectionMode::NFT => { + let call = UniqueNFTCall::parse(method_id, &mut reader).ok()??; + match call { + UniqueNFTCall::ERC721UniqueExtensions( + ERC721UniqueExtensionsCall::Transfer { token_id, .. }, + ) + | UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, .. }) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_transfer::(&collection, &who, &token_id).map(|()| sponsor) } - if sponsor { - >::insert(collection_id, token_id, block_number); - return Ok(()); + UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_approve::(&collection, &who, &token_id).map(|()| sponsor) } + _ => None, } - _ => {} } - } - crate::CollectionMode::Fungible(_) => { - let call: UniqueFungibleCall = UniqueFungibleCall::parse(method_id, &mut reader) - .map_err(|_| AnyError)? - .ok_or(AnyError)?; - #[allow(clippy::single_match)] - match call { - UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => { - let who = T::CrossAccountId::from_eth(*caller); - let collection_limits = &collection.limits; - let limit = collection_limits - .sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT); - - let block_number = >::block_number() as T::BlockNumber; - let mut sponsored = true; - if >::contains_key(collection_id, who.as_sub()) { - let last_tx_block = - >::get(collection_id, who.as_sub()); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsored = false; - } - } - if sponsored { - >::insert( - collection_id, - who.as_sub(), - block_number, - ); - return Ok(()); + crate::CollectionMode::Fungible(_) => { + let call = UniqueFungibleCall::parse(method_id, &mut reader).ok()??; + #[allow(clippy::single_match)] + match call { + UniqueFungibleCall::ERC20( + ERC20Call::Transfer { .. } | ERC20Call::TransferFrom { .. }, + ) => withdraw_transfer::(&collection, &who, &TokenId::default()) + .map(|()| sponsor), + UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => { + withdraw_approve::(&collection, &who, &TokenId::default()) + .map(|()| sponsor) } - } - _ => {} - } - } - _ => {} - } - Err(AnyError) -} - -pub struct NftEthSponsorshipHandler(PhantomData<*const T>); -impl SponsorshipHandler)> for NftEthSponsorshipHandler { - fn get_sponsor(who: &H160, call: &(H160, Vec)) -> Option { - if let Some(collection_id) = map_eth_to_id(&call.0) { - if let Some(collection) = >::get(collection_id) { - if !collection.sponsorship.confirmed() { - return None; - } - if try_sponsor::(who, collection_id, &collection, &call.1).is_ok() { - return collection - .sponsorship - .sponsor() - .cloned() - .map(T::EvmBackwardsAddressMapping::from_account_id); + _ => None, } } + _ => None, } - None } } --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -136,18 +136,22 @@ //#region Tokens transfer rate limit baskets /// (Collection id (controlled?2), who created (real)) /// TODO: Off chain worker should remove from this map when collection gets removed - pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber; + pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option; /// Collection id (controlled?2), token id (controlled?2) - pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber; + pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option; /// Collection id (controlled?2), owning user (real) - pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber; + pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option; /// Collection id (controlled?2), token id (controlled?2) - pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber; + pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option; //#endregion /// Variable metadata sponsoring /// Collection id (controlled?2), token id (controlled?2) - pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option = None; + pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option; + /// Approval sponsoring + pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option; + pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option; + pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option; } } @@ -249,9 +253,12 @@ >::remove_prefix(collection_id, None); >::remove_prefix(collection_id, None); - >::remove_prefix(collection_id, None); + >::remove_prefix((collection_id,), None); >::remove_prefix(collection_id, None); + >::remove_prefix(collection_id, None); + >::remove_prefix(collection_id, None); + >::remove_prefix((collection_id,), None); Ok(()) } @@ -592,7 +599,15 @@ pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - dispatch_call::(collection_id, |d| d.burn_item(sender, item_id, value)) + let post_info = dispatch_call::(collection_id, |d| d.burn_item(sender, item_id, value))?; + if value == 1 { + >::remove(collection_id, item_id); + >::remove(collection_id, item_id); + } + // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets? + // >::remove(collection_id, sender.as_sub()); + // >::remove((collection_id, item_id, sender.as_sub())); + Ok(post_info) } /// Destroys a concrete instance of NFT on behalf of the owner --- a/pallets/nft/src/sponsorship.rs +++ b/pallets/nft/src/sponsorship.rs @@ -1,175 +1,189 @@ use crate::{ Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket, - FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, + FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, NftApproveBasket, + FungibleApproveBasket, RefungibleApproveBasket, }; use core::marker::PhantomData; use up_sponsorship::SponsorshipHandler; use frame_support::{ traits::{IsSubType}, - storage::{StorageMap, StorageDoubleMap}, + storage::{StorageMap, StorageDoubleMap, StorageNMap}, }; use nft_data_structs::{ - TokenId, CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT, + REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, }; -use pallet_common::{CollectionById}; +use pallet_common::{CollectionHandle}; +use pallet_common::account::CrossAccountId; -pub struct NftSponsorshipHandler(PhantomData); -impl NftSponsorshipHandler { - pub fn withdraw_create_item( - who: &T::AccountId, - collection_id: &CollectionId, - _properties: &CreateItemData, - ) -> Option { - let collection = CollectionById::::get(collection_id)?; +pub fn withdraw_transfer( + collection: &CollectionHandle, + who: &T::AccountId, + item_id: &TokenId, +) -> Option<()> { + // preliminary sponsoring correctness check + if !((pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner).as_sub() == who) + || (pallet_refungible::Owned::::get(( + collection.id, + T::CrossAccountId::from_sub(who.clone()), + item_id, + ))) { + return None; + } - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match collection.mode { + CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); - let limit = collection - .limits - .sponsor_transfer_timeout(match _properties { - CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT, - CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }); - if CreateItemBasket::::contains_key((collection_id, &who)) { - let last_tx_block = CreateItemBasket::::get((collection_id, &who)); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - return None; - } + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, item_id), + CollectionMode::Fungible(_) => >::get(collection.id, who), + CollectionMode::ReFungible => { + >::get((collection.id, item_id, who)) } - CreateItemBasket::::insert((collection_id, who.clone()), block_number); + }; - // check free create limit - if collection.limits.sponsored_data_size() >= (_properties.data_size() as u32) { - collection.sponsorship.sponsor().cloned() - } else { - None + if let Some(last_tx_block) = last_tx_block { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; } } - pub fn withdraw_transfer( - who: &T::AccountId, - collection_id: &CollectionId, - item_id: &TokenId, - ) -> Option { - let collection = CollectionById::::get(collection_id)?; + match collection.mode { + CollectionMode::NFT => >::insert(collection.id, item_id, block_number), + CollectionMode::Fungible(_) => { + >::insert(collection.id, who, block_number) + } + CollectionMode::ReFungible => { + >::insert((collection.id, item_id, who), block_number) + } + }; - let mut sponsor_transfer = false; - if collection.sponsorship.confirmed() { - let collection_limits = collection.limits.clone(); - let collection_mode = collection.mode.clone(); + Some(()) +} - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - sponsor_transfer = match collection_mode { - CollectionMode::NFT => { - // get correct limit - let limit = - collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT); +pub fn withdraw_create_item( + collection: &CollectionHandle, + who: &T::AccountId, + _properties: &CreateItemData, +) -> Option<()> { + if _properties.data_size() as u32 > collection.limits.sponsored_data_size() { + return None; + } - let mut sponsored = true; - if NftTransferBasket::::contains_key(collection_id, item_id) { - let last_tx_block = NftTransferBasket::::get(collection_id, item_id); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsored = false; - } - } - if sponsored { - NftTransferBasket::::insert(collection_id, item_id, block_number); - } + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match _properties { + CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT, + CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); - sponsored - } - CollectionMode::Fungible(_) => { - // get correct limit - let limit = collection_limits - .sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT); + if let Some(last_tx_block) = >::get((collection.id, &who)) { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } - let block_number = >::block_number() as T::BlockNumber; - let mut sponsored = true; - if FungibleTransferBasket::::contains_key(collection_id, who) { - let last_tx_block = FungibleTransferBasket::::get(collection_id, who); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsored = false; - } - } - if sponsored { - FungibleTransferBasket::::insert(collection_id, who, block_number); - } + CreateItemBasket::::insert((collection.id, who.clone()), block_number); - sponsored - } - CollectionMode::ReFungible => { - // get correct limit - let limit = collection_limits - .sponsor_transfer_timeout(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT); + Some(()) +} - let mut sponsored = true; - if ReFungibleTransferBasket::::contains_key(collection_id, item_id) { - let last_tx_block = - ReFungibleTransferBasket::::get(collection_id, item_id); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsored = false; - } - } - if sponsored { - ReFungibleTransferBasket::::insert(collection_id, item_id, block_number); - } +pub fn withdraw_set_variable_meta_data( + who: &T::AccountId, + collection: &CollectionHandle, + item_id: &TokenId, + data: &[u8], +) -> Option<()> { + // preliminary sponsoring correctness check + if !((pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner).as_sub() == who) + || (pallet_refungible::Owned::::get(( + collection.id, + T::CrossAccountId::from_sub(who.clone()), + item_id, + ))) { + return None; + } - sponsored - } - }; - } + // Can't sponsor fungible collection, this tx will be rejected + // as invalid + if matches!(collection.mode, CollectionMode::Fungible(_)) { + return None; + } + if data.len() > collection.limits.sponsored_data_size() as usize { + return None; + } - if !sponsor_transfer { - None - } else { - collection.sponsorship.sponsor().cloned() + let block_number = >::block_number() as T::BlockNumber; + let limit = collection.limits.sponsored_data_rate_limit()?; + + if let Some(last_tx_block) = VariableMetaDataBasket::::get(collection.id, item_id) { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; } } - pub fn withdraw_set_variable_meta_data( - collection_id: &CollectionId, - item_id: &TokenId, - data: &[u8], - ) -> Option { - let mut sponsor_metadata_changes = false; + >::insert(collection.id, item_id, block_number); - let collection = CollectionById::::get(collection_id)?; + Some(()) +} - if collection.sponsorship.confirmed() && - // Can't sponsor fungible collection, this tx will be rejected - // as invalid - !matches!(collection.mode, CollectionMode::Fungible(_)) && - data.len() <= collection.limits.sponsored_data_size() as usize - { - if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit() { - let block_number = >::block_number() as T::BlockNumber; +pub fn withdraw_approve( + collection: &CollectionHandle, + who: &T::AccountId, + item_id: &TokenId, +) -> Option<()> { + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection.limits.sponsor_approve_timeout(); - if VariableMetaDataBasket::::get(collection_id, item_id) - .map(|last_block| block_number - last_block > rate_limit.into()) - .unwrap_or(true) - { - sponsor_metadata_changes = true; - VariableMetaDataBasket::::insert(collection_id, item_id, block_number); - } - } + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, item_id), + CollectionMode::Fungible(_) => >::get(collection.id, who), + CollectionMode::ReFungible => { + >::get((collection.id, item_id, who)) } + }; - if !sponsor_metadata_changes { - None - } else { - collection.sponsorship.sponsor().cloned() + if let Some(last_tx_block) = last_tx_block { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; } } + + match collection.mode { + CollectionMode::NFT => >::insert(collection.id, item_id, block_number), + CollectionMode::Fungible(_) => { + >::insert(collection.id, who, block_number) + } + CollectionMode::ReFungible => { + >::insert((collection.id, item_id, who), block_number) + } + }; + + Some(()) +} + +fn load(id: CollectionId) -> Option<(T::AccountId, CollectionHandle)> { + let collection = CollectionHandle::new(id)?; + let sponsor = collection.sponsorship.sponsor().cloned()?; + Some((sponsor, collection)) } +pub struct NftSponsorshipHandler(PhantomData); impl SponsorshipHandler for NftSponsorshipHandler where T: Config, @@ -181,17 +195,40 @@ collection_id, data, .. - } => Self::withdraw_create_item(who, collection_id, data), + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_create_item::(&collection, who, data).map(|()| sponsor) + } Call::transfer { collection_id, item_id, .. - } => Self::withdraw_transfer(who, collection_id, item_id), + } + | Call::transfer_from { + collection_id, + item_id, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_transfer::(&collection, who, item_id).map(|()| sponsor) + } + Call::approve { + collection_id, + item_id, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_approve::(&collection, who, item_id).map(|()| sponsor) + } Call::set_variable_meta_data { collection_id, item_id, data, - } => Self::withdraw_set_variable_meta_data(collection_id, item_id, data), + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_set_variable_meta_data::(&who, &collection, item_id, data) + .map(|()| sponsor) + } _ => None, } } --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -58,18 +58,18 @@ } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::generate_store(pub trait Store)] pub struct Pallet(_); #[pallet::storage] - pub(super) type TokensMinted = + pub type TokensMinted = StorageMap; #[pallet::storage] - pub(super) type TokensBurnt = + pub type TokensBurnt = StorageMap; #[pallet::storage] - pub(super) type TokenData = StorageNMap< + pub type TokenData = StorageNMap< Key = (Key, Key), Value = ItemData, QueryKind = OptionQuery, @@ -77,7 +77,7 @@ /// Used to enumerate tokens owned by account #[pallet::storage] - pub(super) type Owned = StorageNMap< + pub type Owned = StorageNMap< Key = ( Key, Key, @@ -88,7 +88,7 @@ >; #[pallet::storage] - pub(super) type AccountBalance = StorageNMap< + pub type AccountBalance = StorageNMap< Key = ( Key, Key, @@ -98,7 +98,7 @@ >; #[pallet::storage] - pub(super) type Allowance = StorageNMap< + pub type Allowance = StorageNMap< Key = (Key, Key), Value = T::CrossAccountId, QueryKind = OptionQuery, @@ -328,7 +328,7 @@ .checked_add(data.len() as u32) .ok_or(ArithmeticError::Overflow)?; ensure!( - tokens_minted < collection.limits.token_limit(), + tokens_minted <= collection.limits.token_limit(), >::CollectionTokenLimitExceeded ); collection.consume_sstore()?; --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -54,25 +54,25 @@ } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::generate_store(pub trait Store)] pub struct Pallet(_); #[pallet::storage] - pub(super) type TokensMinted = + pub type TokensMinted = StorageMap; #[pallet::storage] - pub(super) type TokensBurnt = + pub type TokensBurnt = StorageMap; #[pallet::storage] - pub(super) type TokenData = StorageNMap< + pub type TokenData = StorageNMap< Key = (Key, Key), Value = ItemData, QueryKind = ValueQuery, >; #[pallet::storage] - pub(super) type TotalSupply = StorageNMap< + pub type TotalSupply = StorageNMap< Key = (Key, Key), Value = u128, QueryKind = ValueQuery, @@ -80,7 +80,7 @@ /// Used to enumerate tokens owned by account #[pallet::storage] - pub(super) type Owned = StorageNMap< + pub type Owned = StorageNMap< Key = ( Key, Key, @@ -91,7 +91,7 @@ >; #[pallet::storage] - pub(super) type AccountBalance = StorageNMap< + pub type AccountBalance = StorageNMap< Key = ( Key, // Owner @@ -102,7 +102,7 @@ >; #[pallet::storage] - pub(super) type Balance = StorageNMap< + pub type Balance = StorageNMap< Key = ( Key, Key, @@ -114,7 +114,7 @@ >; #[pallet::storage] - pub(super) type Allowance = StorageNMap< + pub type Allowance = StorageNMap< Key = ( Key, Key, --- /dev/null +++ b/primitives/evm-mapping/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "up-evm-mapping" +version = "0.1.0" +edition = "2018" + +[dependencies] +sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } +frame-support = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' } + +[features] +default = ["std"] +std = [ + "sp-core/std", + "frame-support/std", +] --- /dev/null +++ b/primitives/evm-mapping/src/lib.rs @@ -0,0 +1,23 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::sp_runtime::AccountId32; +use sp_core::H160; + +/// Transforms substrate addresses to ethereum (Reverse of `EvmAddressMapping`) +/// pallet_evm doesn't have this, as it only checks if eth address +/// is owned by substrate via `EnsureAddressOrigin` trait +/// +/// This trait implementations shouldn't conflict with used `EnsureAddressOrigin` +pub trait EvmBackwardsAddressMapping { + fn from_account_id(account_id: AccountId) -> H160; +} + +/// Should have same mapping as EnsureAddressTruncated +pub struct MapBackwardsAddressTruncated; +impl EvmBackwardsAddressMapping for MapBackwardsAddressTruncated { + fn from_account_id(account_id: AccountId32) -> H160 { + let mut out = [0; 20]; + out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]); + H160(out) + } +} --- a/primitives/nft/src/lib.rs +++ b/primitives/nft/src/lib.rs @@ -54,6 +54,8 @@ pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5; pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5; +pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5; + // Schema limits pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024; pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024; @@ -246,6 +248,7 @@ pub variable_data: Vec, } +/// All fields are wrapped in `Option`s, where None means chain default #[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct CollectionLimits { @@ -254,11 +257,12 @@ /// None - setVariableMetadata is not sponsored /// Some(v) - setVariableMetadata is sponsored /// if there is v block between txs - pub sponsored_data_rate_limit: Option, + pub sponsored_data_rate_limit: Option<(Option,)>, pub token_limit: Option, // Timeouts for item types in passed blocks pub sponsor_transfer_timeout: Option, + pub sponsor_approve_timeout: Option, pub owner_can_transfer: Option, pub owner_can_destroy: Option, pub transfers_enabled: Option, @@ -285,6 +289,11 @@ .unwrap_or(default) .min(MAX_SPONSOR_TIMEOUT) } + pub fn sponsor_approve_timeout(&self) -> u32 { + self.sponsor_approve_timeout + .unwrap_or(SPONSOR_APPROVE_TIMEOUT) + .min(MAX_SPONSOR_TIMEOUT) + } pub fn owner_can_transfer(&self) -> bool { self.owner_can_transfer.unwrap_or(true) } @@ -296,6 +305,8 @@ } pub fn sponsored_data_rate_limit(&self) -> Option { self.sponsored_data_rate_limit + .unwrap_or((None,)) + .0 .map(|v| v.min(MAX_SPONSOR_TIMEOUT)) } } --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -60,7 +60,7 @@ 'pallet-transaction-payment/std', 'pallet-transaction-payment-rpc-runtime-api/std', 'pallet-treasury/std', - 'pallet-vesting/std', + # 'pallet-vesting/std', 'pallet-evm/std', 'pallet-evm-migration/std', 'pallet-evm-contract-helpers/std', @@ -69,6 +69,7 @@ 'pallet-ethereum/std', 'fp-rpc/std', 'up-rpc/std', + 'up-evm-mapping/std', 'fp-self-contained/std', 'parachain-info/std', 'serde', @@ -80,7 +81,6 @@ 'pallet-nft/std', 'pallet-unq-scheduler/std', 'pallet-nft-charge-transaction/std', - 'pallet-nft-transaction-payment/std', 'nft-data-structs/std', 'sp-api/std', 'sp-block-builder/std', @@ -97,6 +97,8 @@ 'xcm/std', 'xcm-builder/std', 'xcm-executor/std', + + "orml-vesting/std", ] limit-testing = ['pallet-nft/limit-testing', 'nft-data-structs/limit-testing'] @@ -210,10 +212,10 @@ git = 'https://github.com/paritytech/substrate.git' branch = 'polkadot-v0.9.12' -[dependencies.pallet-vesting] -default-features = false -git = 'https://github.com/paritytech/substrate.git' -branch = 'polkadot-v0.9.12' +# [dependencies.pallet-vesting] +# default-features = false +# git = 'https://github.com/paritytech/substrate.git' +# branch = 'polkadot-v0.9.12' [dependencies.sp-arithmetic] default-features = false @@ -359,6 +361,10 @@ branch = 'release-v0.9.12' default-features = false +[dependencies.orml-vesting] +git = 'https://github.com/UniqueNetwork/open-runtime-module-library' +version = "0.4.1-dev" +default-features = false ################################################################################ # local dependencies @@ -370,6 +376,7 @@ derivative = "2.2.0" pallet-nft = { path = '../pallets/nft', default-features = false, version = '3.0.0' } up-rpc = { path = "../primitives/rpc", default-features = false } +up-evm-mapping = { path = "../primitives/evm-mapping", default-features = false } pallet-inflation = { path = '../pallets/inflation', default-features = false, version = '3.0.0' } nft-data-structs = { path = '../primitives/nft', default-features = false, version = '0.9.0' } pallet-common = { default-features = false, path = "../pallets/common" } @@ -378,8 +385,7 @@ pallet-nonfungible = { default-features = false, path = "../pallets/nonfungible" } pallet-unq-scheduler = { path = '../pallets/scheduler', default-features = false, version = '3.0.0' } # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' } -pallet-nft-transaction-payment = { path = '../pallets/nft-transaction-payment', default-features = false, version = '3.0.0' } -pallet-nft-charge-transaction = {git = "https://github.com/UniqueNetwork/pallet-sponsoring", package = "pallet-template-transaction-payment", default-features = false, version = '3.0.0' } +pallet-nft-charge-transaction = { git = "https://github.com/UniqueNetwork/pallet-sponsoring", package = "pallet-template-transaction-payment", default-features = false, version = '3.0.0' } pallet-evm-migration = { path = '../pallets/evm-migration', default-features = false } pallet-evm-contract-helpers = { path = '../pallets/evm-contract-helpers', default-features = false } pallet-evm-transaction-payment = { path = '../pallets/evm-transaction-payment', default-features = false } --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -22,8 +22,7 @@ use sp_runtime::{ Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys, traits::{ - AccountIdLookup, ConvertInto, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify, - AccountIdConversion, + AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify, AccountIdConversion, }, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, MultiSignature, @@ -70,7 +69,7 @@ use fp_rpc::TransactionStatus; use sp_core::crypto::Public; use sp_runtime::{ - traits::{Dispatchable, PostDispatchInfoOf}, + traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf}, transaction_validity::TransactionValidityError, }; @@ -515,17 +514,33 @@ type Call = Call; } +pub struct RelayChainBlockNumberProvider(sp_std::marker::PhantomData); + +impl BlockNumberProvider + for RelayChainBlockNumberProvider +{ + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + cumulus_pallet_parachain_system::Pallet::::validation_data() + .map(|d| d.relay_parent_number) + .unwrap_or_default() + } +} + parameter_types! { pub const MinVestedTransfer: Balance = 10 * UNIQUE; + pub const MaxVestingSchedules: u32 = 28; } -impl pallet_vesting::Config for Runtime { +impl orml_vesting::Config for Runtime { type Event = Event; - type Currency = Balances; - type BlockNumberToBalance = ConvertInto; + type Currency = pallet_balances::Pallet; type MinVestedTransfer = MinVestedTransfer; + type VestedTransferOrigin = EnsureSigned; type WeightInfo = (); - const MAX_VESTING_SCHEDULES: u32 = 28; + type MaxVestingSchedules = MaxVestingSchedules; + type BlockNumberProvider = RelayChainBlockNumberProvider; } parameter_types! { @@ -721,7 +736,7 @@ impl pallet_common::Config for Runtime { type Event = Event; - type EvmBackwardsAddressMapping = pallet_common::account::MapBackwardsAddressTruncated; + type EvmBackwardsAddressMapping = up_evm_mapping::MapBackwardsAddressTruncated; type EvmAddressMapping = HashedAddressMapping; type CrossAccountId = pallet_common::account::BasicCrossAccountId; @@ -762,20 +777,14 @@ pub const MaxScheduledPerBlock: u32 = 50; } -pub struct Sponsoring; -impl SponsoringResolve for Sponsoring { - fn resolve(who: &AccountId, call: &Call) -> Option - where - Call: Dispatchable, - AccountId: AsRef<[u8]>, - { - pallet_nft_transaction_payment::Module::::withdraw_type(who, call) - } -} - +type EvmSponsorshipHandler = ( + pallet_nft::NftEthSponsorshipHandler, + pallet_evm_contract_helpers::HelpersContractSponsoring, +); type SponsorshipHandler = ( pallet_nft::NftSponsorshipHandler, //pallet_contract_helpers::ContractSponsorshipHandler, + pallet_evm_transaction_payment::BridgeSponsorshipHandler, ); impl pallet_unq_scheduler::Config for Runtime { @@ -788,22 +797,17 @@ type MaxScheduledPerBlock = MaxScheduledPerBlock; type SponsorshipHandler = SponsorshipHandler; type WeightInfo = (); -} - -impl pallet_nft_transaction_payment::Config for Runtime { - type SponsorshipHandler = SponsorshipHandler; } impl pallet_evm_transaction_payment::Config for Runtime { - type SponsorshipHandler = ( - pallet_nft::NftEthSponsorshipHandler, - pallet_evm_contract_helpers::HelpersContractSponsoring, - ); + type EvmSponsorshipHandler = EvmSponsorshipHandler; type Currency = Balances; + type EvmAddressMapping = HashedAddressMapping; + type EvmBackwardsAddressMapping = up_evm_mapping::MapBackwardsAddressTruncated; } impl pallet_nft_charge_transaction::Config for Runtime { - type SponsorshipHandler = pallet_nft::NftSponsorshipHandler; + type SponsorshipHandler = SponsorshipHandler; } // impl pallet_contract_helpers::Config for Runtime { @@ -841,7 +845,8 @@ Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 34, Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event} = 35, System: system::{Pallet, Call, Storage, Config, Event} = 36, - Vesting: pallet_vesting::{Pallet, Call, Config, Storage, Event} = 37, + Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 37, + // Vesting: pallet_vesting::{Pallet, Call, Config, Storage, Event} = 37, // Contracts: pallet_contracts::{Pallet, Call, Storage, Event} = 38, // XCM helpers. @@ -854,7 +859,7 @@ Inflation: pallet_inflation::{Pallet, Call, Storage} = 60, Nft: pallet_nft::{Pallet, Call, Storage} = 61, Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event} = 62, - NftPayment: pallet_nft_transaction_payment::{Pallet, Call, Storage} = 63, + // free = 63 Charging: pallet_nft_charge_transaction::{Pallet, Call, Storage } = 64, // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65, Common: pallet_common::{Pallet, Storage, Event} = 66, --- a/tests/src/approve.test.ts +++ b/tests/src/approve.test.ts @@ -19,9 +19,12 @@ addCollectionAdminExpectSuccess, adminApproveFromExpectSuccess, getCreatedCollectionCount, + transferFromExpectSuccess, + transferFromExpectFail, } from './util/helpers'; chai.use(chaiAsPromised); +const expect = chai.expect; describe('Integration Test approve(spender, collection_id, item_id, amount):', () => { let alice: IKeyringPair; @@ -79,6 +82,320 @@ }); }); +describe('Normal user can approve other users to transfer:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + }); + }); + + it('NFT', async () => { + const collectionId = await createCollectionExpectSuccess(); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + }); + + it('Fungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }}); + const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + }); + + it('ReFungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } }); + const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + }); +}); + +describe('Approved users can transferFrom up to approved amount:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + }); + }); + + it('NFT', async () => { + const collectionId = await createCollectionExpectSuccess(); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'NFT'); + }); + + it('Fungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }}); + const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'Fungible'); + }); + + it('ReFungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } }); + const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'ReFungible'); + }); +}); + +describe('Approved users cannot use transferFrom to repeat transfers if approved amount was already transferred:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + }); + }); + + it('NFT', async () => { + const collectionId = await createCollectionExpectSuccess(); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'NFT'); + await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1); + }); + + it('Fungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }}); + const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'Fungible'); + await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1); + }); + + it('ReFungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } }); + const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address); + await approveExpectSuccess(collectionId, itemId, bob, charlie.address); + await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'ReFungible'); + await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1); + }); +}); + +describe('Approved amount decreases by the transferred amount.:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + let dave: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + dave = privateKey('//Dave'); + }); + }); + + it('If a user B is approved to transfer 10 Fungible tokens from user A, they can transfer 2 tokens to user C, which will result in decreasing approval from 10 to 8. Then user B can transfer 8 tokens to user D.', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }}); + const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', alice.address); + await approveExpectSuccess(collectionId, itemId, alice, bob.address, 10); + await transferFromExpectSuccess(collectionId, itemId, bob, alice, charlie, 2, 'Fungible'); + await transferFromExpectSuccess(collectionId, itemId, bob, alice, dave, 8, 'Fungible'); + }); +}); + +describe('User may clear the approvals to approving for 0 amount:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + }); + }); + + it('NFT', async () => { + const collectionId = await createCollectionExpectSuccess(); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT'); + await approveExpectSuccess(collectionId, itemId, alice, bob.address, 1); + await approveExpectSuccess(collectionId, itemId, alice, bob.address, 0); + await transferFromExpectFail(collectionId, itemId, bob, bob, charlie, 1); + }); + + it('Fungible', async () => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); + await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 1); + await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 0); + await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, bob, charlie, 1); + }); + + it('ReFungible', async () => { + const reFungibleCollectionId = + await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); + await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 1); + await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 0); + await transferFromExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, bob, charlie, 1); + }); +}); + +describe('User cannot approve for the amount greater than they own:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + }); + }); + + it('1 for NFT', async () => { + const collectionId = await createCollectionExpectSuccess(); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address); + await approveExpectFail(collectionId, itemId, bob, charlie, 2); + }); + + it('Fungible', async () => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); + await approveExpectFail(fungibleCollectionId, newFungibleTokenId, bob, charlie, 11); + }); + + it('ReFungible', async () => { + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); + await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, charlie, 101); + }); +}); + +describe('Administrator and collection owner do not need approval in order to execute TransferFrom:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + let dave: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + dave = privateKey('//Dave'); + }); + }); + + it('NFT', async () => { + const collectionId = await createCollectionExpectSuccess(); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', charlie.address); + await transferFromExpectSuccess(collectionId, itemId, alice, charlie, dave, 1, 'NFT'); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'NFT'); + }); + + it('Fungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }}); + const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', charlie.address); + await transferFromExpectSuccess(collectionId, itemId, alice, charlie, dave, 1, 'Fungible'); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'Fungible'); + }); + + it('ReFungible up to an approved amount', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } }); + const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', charlie.address); + await transferFromExpectSuccess(collectionId, itemId, alice, charlie, dave, 1, 'ReFungible'); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'ReFungible'); + }); +}); + +describe('Repeated approvals add up', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + let dave: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + dave = privateKey('//Dave'); + }); + }); + + it.skip('Owned 10, approval 1: 1, approval 2: 1, resulting approved value: 2. Fungible', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }}); + await createItemExpectSuccess(alice, collectionId, 'Fungible', alice.address); + await approveExpectSuccess(collectionId, 0, alice, bob.address, 1); + await approveExpectSuccess(collectionId, 0, alice, charlie.address, 1); + // const allowances1 = await getAllowance(collectionId, 0, Alice.address, Bob.address); + // const allowances2 = await getAllowance(collectionId, 0, Alice.address, Charlie.address); + // expect(allowances1 + allowances2).to.be.eq(BigInt(2)); + }); + + it.skip('Owned 10, approval 1: 1, approval 2: 1, resulting approved value: 2. ReFungible', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } }); + const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', alice.address); + await approveExpectSuccess(collectionId, itemId, alice, bob.address, 1); + await approveExpectSuccess(collectionId, itemId, alice, charlie.address, 1); + // const allowances1 = await getAllowance(collectionId, itemId, Alice.address, Bob.address); + // const allowances2 = await getAllowance(collectionId, itemId, Alice.address, Charlie.address); + // expect(allowances1 + allowances2).to.be.eq(BigInt(2)); + }); + + // Canceled by changing approve logic + it.skip('Cannot approve for more than total user`s amount (owned: 10, approval 1: 5 - should succeed, approval 2: 6 - should fail). Fungible', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }}); + await createItemExpectSuccess(alice, collectionId, 'Fungible', dave.address); + await approveExpectSuccess(collectionId, 0, dave, bob.address, 5); + await approveExpectFail(collectionId, 0, dave, charlie, 6); + }); + + // Canceled by changing approve logic + it.skip('Cannot approve for more than total users amount (owned: 100, approval 1: 50 - should succeed, approval 2: 51 - should fail). ReFungible', async () => { + const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } }); + const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', dave.address); + await approveExpectSuccess(collectionId, itemId, dave, bob.address, 50); + await approveExpectFail(collectionId, itemId, dave, charlie, 51); + }); +}); + +describe('Integration Test approve(spender, collection_id, item_id, amount) with collection admin permissions:', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + charlie = privateKey('//Charlie'); + }); + }); + + it('can be called by collection admin on non-owned item', async () => { + const collectionId = await createCollectionExpectSuccess(); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); + + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + await adminApproveFromExpectSuccess(collectionId, itemId, bob, alice.address, charlie.address); + }); +}); + describe('Negative Integration Test approve(spender, collection_id, item_id, amount):', () => { let alice: IKeyringPair; let bob: IKeyringPair; @@ -170,27 +487,5 @@ await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: false}); await approveExpectFail(collectionId, itemId, alice, charlie); - }); -}); - -describe('Integration Test approve(spender, collection_id, item_id, amount) with collection admin permissions:', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - let charlie: IKeyringPair; - - before(async () => { - await usingApi(async () => { - alice = privateKey('//Alice'); - bob = privateKey('//Bob'); - charlie = privateKey('//Charlie'); - }); - }); - - it('can be called by collection admin on non-owned item', async () => { - const collectionId = await createCollectionExpectSuccess(); - const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); - - await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); - await adminApproveFromExpectSuccess(collectionId, itemId, bob, alice.address, charlie.address); }); }); --- a/tests/src/confirmSponsorship.test.ts +++ b/tests/src/confirmSponsorship.test.ts @@ -252,23 +252,20 @@ const zeroBalance = await findUnusedAddress(api); // Mint token for alice - const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', alice.address); + const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', zeroBalance.address); - // Transfer this token from Alice to unused address and back - // Alice to Zero gets sponsored - const aliceToZero = api.tx.nft.transfer(normalizeAccountId(zeroBalance.address), collectionId, itemId, 1); - const events1 = await submitTransactionAsync(alice, aliceToZero); + const zeroToAlice = api.tx.nft.transfer(normalizeAccountId(alice.address), collectionId, itemId, 1); + + // Zero to alice gets sponsored + const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice); const result1 = getGenericResult(events1); expect(result1.success).to.be.true; // Second transfer should fail const sponsorBalanceBefore = (await api.query.system.account(bob.address)).data.free.toBigInt(); - const zeroToAlice = api.tx.nft.transfer(normalizeAccountId(alice.address), collectionId, itemId, 1); - const badTransaction = async function () { - await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice); - }; - await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees'); + await expect(submitTransactionExpectFailAsync(zeroBalance, zeroToAlice)).to.be.rejectedWith('Inability to pay some fees'); const sponsorBalanceAfter = (await api.query.system.account(bob.address)).data.free.toBigInt(); + expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore); // Try again after Zero gets some balance - now it should succeed const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15); @@ -276,8 +273,6 @@ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice); const result2 = getGenericResult(events2); expect(result2.success).to.be.true; - - expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore); }); }); --- /dev/null +++ b/tests/src/eth/marketplace/MarketPlace.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_escrow","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_price","type":"uint256"},{"indexed":false,"internalType":"address","name":"_currencyCode","type":"address"},{"indexed":false,"internalType":"address","name":"_idCollection","type":"address"},{"indexed":false,"internalType":"uint256","name":"_idNFT","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"AddedAsk","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_idCollection","type":"address"},{"indexed":false,"internalType":"uint256","name":"_idNFT","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"orderID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"orderPrice","type":"uint256"}],"name":"BoughtNFT","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_idCollection","type":"address"},{"indexed":false,"internalType":"uint256","name":"_idNFT","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"orderID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"orderPrice","type":"uint256"}],"name":"BoughtNFT4KSM","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_idCollection","type":"address"},{"indexed":false,"internalType":"uint256","name":"_idNFT","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"CanceledAsk","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"_sender","type":"address"}],"name":"DepositedKSM","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_price","type":"uint256"},{"indexed":false,"internalType":"address","name":"_currencyCode","type":"address"},{"indexed":false,"internalType":"address","name":"_idCollection","type":"address"},{"indexed":false,"internalType":"uint256","name":"_idNFT","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"_active","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"EditedAsk","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"_currencyCode","type":"address"},{"indexed":false,"internalType":"address","name":"_sender","type":"address"}],"name":"Withdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"balance","type":"uint256"}],"name":"WithdrawnAllKSM","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"address","name":"_currencyCode","type":"address"},{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"addAsk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asksbySeller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceKSM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"buy","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"address","name":"_buyer","type":"address"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"buyKSM","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"cancelAsk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"}],"name":"depositKSM","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"address","name":"_currencyCode","type":"address"},{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"uint8","name":"_active","type":"uint8"}],"name":"editAsk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"getOrder","outputs":[{"components":[{"internalType":"uint256","name":"idNFT","type":"uint256"},{"internalType":"address","name":"currencyCode","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"address","name":"idCollection","type":"address"},{"internalType":"address","name":"ownerAddr","type":"address"},{"internalType":"uint8","name":"flagActive","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"string","name":"tokenURI","type":"string"}],"internalType":"struct MarketPlace.Order","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOrdersLen","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"orders","outputs":[{"internalType":"uint256","name":"idNFT","type":"uint256"},{"internalType":"address","name":"currencyCode","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"address","name":"idCollection","type":"address"},{"internalType":"address","name":"ownerAddr","type":"address"},{"internalType":"uint8","name":"flagActive","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"string","name":"tokenURI","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newEscrow","type":"address"}],"name":"setEscrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_coin","type":"address"}],"name":"setNativeCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newEscrow","type":"address"}],"name":"setowner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_currencyCode","type":"address"},{"internalType":"address payable","name":"_sender","type":"address"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"result","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"}],"name":"withdrawAllKSM","outputs":[{"internalType":"uint256","name":"lastBalance","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file --- /dev/null +++ b/tests/src/eth/marketplace/MarketPlace.bin @@ -0,0 +1 @@ +60806040523480156200001157600080fd5b50604051620029b8380380620029b8833981016040819052620000349162000327565b600580546001600160a01b038084166001600160a01b03199283161790925560068054821633179055604080516101408101825260008082526020808301828152838501838152606085018481526080860185815260a0870186815260c0880187815289518088018b5288815260e08a019081528a518089018c528981526101008b01528a51808901909b52888b526101208a019a909a528754600181018955978052885160099098027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563810198895595517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56487018054918e16918d1691909117905593517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56586015591517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566850155517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56784018054918b16919099161790975595517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56882018054975160ff16600160a01b026001600160a81b0319909816919098161795909517909555915180519194929362000239937f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5699091019291019062000281565b5061010082015180516200025891600784019160209091019062000281565b5061012082015180516200027791600884019160209091019062000281565b5050505062000396565b8280546200028f9062000359565b90600052602060002090601f016020900481019282620002b35760008555620002fe565b82601f10620002ce57805160ff1916838001178555620002fe565b82800160010185558215620002fe579182015b82811115620002fe578251825591602001919060010190620002e1565b506200030c92915062000310565b5090565b5b808211156200030c576000815560010162000311565b6000602082840312156200033a57600080fd5b81516001600160a01b03811681146200035257600080fd5b9392505050565b600181811c908216806200036e57607f821691505b602082108114156200039057634e487b7160e01b600052602260045260246000fd5b50919050565b61261280620003a66000396000f3fe6080604052600436106101025760003560e01c806399f2d4eb11610095578063c10c354611610064578063c10c354614610221578063cce7ec1314610378578063e22d4f7d1461038b578063edb25841146103c3578063fd8868c7146103f057610184565b806399f2d4eb146102c5578063a85c38ef146102e5578063b460af941461031b578063b4c3e8ea1461034b57610184565b80636b084b25116100d15780636b084b2514610241578063791bd866146102615780638c088f001461028157806393cdd334146102a557610184565b806337c4eb58146101bf5780633ce61756146101e157806340b8074614610201578063592bd7051461022157610184565b366101845760405162461bcd60e51b815260206004820152604160248201527f43616e277420616363657074207061796d656e7420776974686f757420636f6c60448201527f6c656374696f6e20616e64204944732c20757365206441707020746f2073656e6064820152601960fa1b608482015260a4015b60405180910390fd5b60405162461bcd60e51b815260206004820152601060248201526f27379039bab1b410333ab731ba34b7b760811b604482015260640161017b565b3480156101cb57600080fd5b506101df6101da366004612054565b610410565b005b3480156101ed57600080fd5b506101df6101fc36600461209c565b610979565b34801561020d57600080fd5b506101df61021c3660046120c0565b6109c5565b34801561022d57600080fd5b506101df61023c36600461209c565b610c27565b34801561024d57600080fd5b506101df61025c3660046120ec565b610c73565b34801561026d57600080fd5b506101df61027c366004612151565b610ee6565b34801561028d57600080fd5b506000545b6040519081526020015b60405180910390f35b3480156102b157600080fd5b506102926102c036600461209c565b610fb1565b3480156102d157600080fd5b506102926102e03660046120c0565b61105e565b3480156102f157600080fd5b50610305610300366004612181565b61108f565b60405161029c9a999897969594939291906121f6565b34801561032757600080fd5b5061033b610336366004612283565b6112a0565b604051901515815260200161029c565b34801561035757600080fd5b5061029261036636600461209c565b60026020526000908152604090205481565b61033b6103863660046120c0565b6113d2565b34801561039757600080fd5b506102926103a63660046120c0565b600360209081526000928352604080842090915290825290205481565b3480156103cf57600080fd5b506103e36103de3660046120c0565b61181b565b60405161029c91906122c5565b3480156103fc57600080fd5b506101df61040b3660046123a5565b611b0c565b6040516331a9108f60e11b8152600481018290526000906001600160a01b03841690636352211e90602401602060405180830381865afa158015610458573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061047c91906123f8565b90506001600160a01b03811633146104d65760405162461bcd60e51b815260206004820152601d60248201527f4f6e6c7920746f6b656e206f776e65722063616e206d616b652061736b000000604482015260640161017b565b6060806060856001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa92505050801561053a57506040513d6000823e601f3d908101601f19168201604052610537919081019061242b565b60015b61055557604051806020016040528060008152509250610558565b92505b856001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa9250505080156105b757506040513d6000823e601f3d908101601f191682016040526105b4919081019061242b565b60015b6105d2576040518060200160405280600081525091506105d5565b91505b60405163c87b56dd60e01b8152600481018690526001600160a01b0387169063c87b56dd90602401600060405180830381865afa92505050801561063b57506040513d6000823e601f3d908101601f19168201604052610638919081019061242b565b60015b6106545750604080516020810190915260008152610657565b90505b60408051610140810182528681526001600160a01b03808a1660208084019182529383018c815242606085019081528b8416608086019081523360a08701908152600160c0880181815260e089018d81526101008a018d90526101208a018c90526000805493840181558052895160099093027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563810193845597517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56489018054918b166001600160a01b031992831617905596517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56589015594517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56688015592517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e567870180549189169190961617909455517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56885018054925160ff16600160a01b026001600160a81b03199093169190961617179093559151805193949293610828937f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e569909301929190910190611fa3565b506101008201518051610845916007840191602090910190611fa3565b506101208201518051610862916008840191602090910190611fa3565b505060008054909150610877906001906124ee565b6001600160a01b03881660008181526003602090815260408083208b84528252808320859055338084526004808452828520805460018101825590865293909420909201859055516323b872dd60e01b815293945091926323b872dd926108e492909130918c9101612505565b600060405180830381600087803b1580156108fe57600080fd5b505af1158015610912573d6000803e3d6000fd5b5050604080518c81526001600160a01b038c811660208301528b1681830152606081018a90526080810185905290517f83b3a8754d705365b581ab63c329c1e55255a22aa78c67b5fcef378bfb2d9b9993509081900360a0019150a1505050505050505050565b6006546001600160a01b031633146109a35760405162461bcd60e51b815260040161017b90612529565b600780546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b0382166000908152600360209081526040808320848452909152812054815490913391839081106109ff576109ff612551565b60009182526020909120600560099092020101546001600160a01b031614610a695760405162461bcd60e51b815260206004820152601d60248201527f4f6e6c7920746f6b656e206f776e65722063616e20656469742061736b000000604482015260640161017b565b60008181548110610a7c57610a7c612551565b6000918252602090912060099091020160050154600160a01b900460ff16610adb5760405162461bcd60e51b8152602060048201526012602482015271151a1a5cc8185cdac81a5cc818db1bdcd95960721b604482015260640161017b565b4260008281548110610aef57610aef612551565b9060005260206000209060090201600301819055506000808281548110610b1857610b18612551565b906000526020600020906009020160050160146101000a81548160ff021916908360ff160217905550826001600160a01b03166323b872dd3060008481548110610b6457610b64612551565b60009182526020909120600560099092020101546040516001600160e01b031960e085901b168152610ba592916001600160a01b0316908790600401612505565b600060405180830381600087803b158015610bbf57600080fd5b505af1158015610bd3573d6000803e3d6000fd5b5050604080516001600160a01b0387168152602081018690529081018490527f6330f58b476cc7f9dd8db8130a0fbb49b291ebc32531eb36e9646f56ee20174b9250606001905060405180910390a1505050565b6006546001600160a01b03163314610c515760405162461bcd60e51b815260040161017b90612529565b600580546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b038316600090815260036020908152604080832085845290915281205481549091339183908110610cad57610cad612551565b60009182526020909120600560099092020101546001600160a01b031614610d175760405162461bcd60e51b815260206004820152601d60248201527f4f6e6c7920746f6b656e206f776e65722063616e20656469742061736b000000604482015260640161017b565b60008181548110610d2a57610d2a612551565b6000918252602090912060099091020160050154600160a01b900460ff16610d895760405162461bcd60e51b8152602060048201526012602482015271151a1a5cc8185cdac81a5cc818db1bdcd95960721b604482015260640161017b565b8515610db9578560008281548110610da357610da3612551565b9060005260206000209060090201600201819055505b6001600160a01b03851615610e12578460008281548110610ddc57610ddc612551565b906000526020600020906009020160010160006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b4260008281548110610e2657610e26612551565b9060005260206000209060090201600301819055508160008281548110610e4f57610e4f612551565b600091825260209182902060099190910201600501805460ff60a01b1916600160a01b60ff94851602179055604080518981526001600160a01b03898116938201939093529187169082015260608101859052908316608082015260a081018290527f04211d838289b72af597bf28a4ccff5a30d6056f5fce2eddb127ab4ffea0963d9060c00160405180910390a1505050505050565b6005546001600160a01b03163314610f325760405162461bcd60e51b815260206004820152600f60248201526e27b7363c9032b9b1b937bb9031b0b760891b604482015260640161017b565b6001600160a01b038116600090815260026020526040902054610f56908390612567565b6001600160a01b038216600081815260026020908152604091829020939093558051858152928301919091527f291ce0b690061cb1b3f1111bf6500efe526983251fb0f863251ec3670d035fc4910160405180910390a15050565b6005546000906001600160a01b031633146110005760405162461bcd60e51b815260206004820152600f60248201526e27b7363c9032b9b1b937bb9031b0b760891b604482015260640161017b565b506001600160a01b03811660008181526002602090815260408083208054939055805193845290830182905290917f027aeacbc6fbfa7fe735ff8f14a600469ad1a3197ce1c8c8a159641cf68fe592910160405180910390a1919050565b6004602052816000526040600020818154811061107a57600080fd5b90600052602060002001600091509150505481565b6000818154811061109f57600080fd5b600091825260209091206009909102018054600182015460028301546003840154600485015460058601546006870180549698506001600160a01b0395861697949693959283169492821693600160a01b90920460ff1692916111019061257f565b80601f016020809104026020016040519081016040528092919081815260200182805461112d9061257f565b801561117a5780601f1061114f5761010080835404028352916020019161117a565b820191906000526020600020905b81548152906001019060200180831161115d57829003601f168201915b50505050509080600701805461118f9061257f565b80601f01602080910402602001604051908101604052809291908181526020018280546111bb9061257f565b80156112085780601f106111dd57610100808354040283529160200191611208565b820191906000526020600020905b8154815290600101906020018083116111eb57829003601f168201915b50505050509080600801805461121d9061257f565b80601f01602080910402602001604051908101604052809291908181526020018280546112499061257f565b80156112965780601f1061126b57610100808354040283529160200191611296565b820191906000526020600020905b81548152906001019060200180831161127957829003601f168201915b505050505090508a565b6006546000906001600160a01b031633146112cd5760405162461bcd60e51b815260040161017b90612529565b6007546001600160a01b0384811691161461135b5760405163a9059cbb60e01b81526001600160a01b0383811660048301526024820186905284169063a9059cbb906044016020604051808303816000875af1158015611331573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061135591906125ba565b50611382565b6040516001600160a01b0383169085156108fc029086906000818181858888f19450505050505b604080518581526001600160a01b03858116602083015284168183015290517fb524bace85e4ce0f8da306e32f39263c5724780ca00b7551c051c8e3dc36adaf9181900360600190a19392505050565b6001600160a01b0382166000908152600360209081526040808320848452909152812054815482918291811061140a5761140a612551565b600091825260209182902060408051610140810182526009939093029091018054835260018101546001600160a01b039081169484019490945260028101549183019190915260038101546060830152600481015483166080830152600581015492831660a0830152600160a01b90920460ff1660c082015260068201805491929160e08401919061149b9061257f565b80601f01602080910402602001604051908101604052809291908181526020018280546114c79061257f565b80156115145780601f106114e957610100808354040283529160200191611514565b820191906000526020600020905b8154815290600101906020018083116114f757829003601f168201915b5050505050815260200160078201805461152d9061257f565b80601f01602080910402602001604051908101604052809291908181526020018280546115599061257f565b80156115a65780601f1061157b576101008083540402835291602001916115a6565b820191906000526020600020905b81548152906001019060200180831161158957829003601f168201915b505050505081526020016008820180546115bf9061257f565b80601f01602080910402602001604051908101604052809291908181526020018280546115eb9061257f565b80156116385780601f1061160d57610100808354040283529160200191611638565b820191906000526020600020905b81548152906001019060200180831161161b57829003601f168201915b5050505050815250509050806040015134146116ac5760405162461bcd60e51b815260206004820152602d60248201527f4e6f7420726967687420616d6f756e742073656e742c206861766520746f206260448201526c6520657175616c20707269636560981b606482015260840161017b565b6001600160a01b0384166000908152600360209081526040808320868452909152812054815482919081106116e3576116e3612551565b906000526020600020906009020160050160146101000a81548160ff021916908360ff160217905550836001600160a01b03166323b872dd3033866040518463ffffffff1660e01b815260040161173c93929190612505565b600060405180830381600087803b15801561175657600080fd5b505af115801561176a573d6000803e3d6000fd5b505050508060a001516001600160a01b03166108fc82604001519081150290604051600060405180830381858888f16001600160a01b03891660008181526003602090815260408083208c8452825291829020548983015183519485529184018c90529183019190915260608201529096507fc220cf2a12d6492e23ed1141c55018997799699d009b00ebd5a4874c60f5c31e9450608001925061180c915050565b60405180910390a15092915050565b61188f6040518061014001604052806000815260200160006001600160a01b03168152602001600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600060ff1681526020016060815260200160608152602001606081525090565b6001600160a01b038316600090815260036020908152604080832085845290915281205481549091908190839081106118ca576118ca612551565b600091825260209182902060408051610140810182526009939093029091018054835260018101546001600160a01b039081169484019490945260028101549183019190915260038101546060830152600481015483166080830152600581015492831660a0830152600160a01b90920460ff1660c082015260068201805491929160e08401919061195b9061257f565b80601f01602080910402602001604051908101604052809291908181526020018280546119879061257f565b80156119d45780601f106119a9576101008083540402835291602001916119d4565b820191906000526020600020905b8154815290600101906020018083116119b757829003601f168201915b505050505081526020016007820180546119ed9061257f565b80601f0160208091040260200160405190810160405280929190818152602001828054611a199061257f565b8015611a665780601f10611a3b57610100808354040283529160200191611a66565b820191906000526020600020905b815481529060010190602001808311611a4957829003601f168201915b50505050508152602001600882018054611a7f9061257f565b80601f0160208091040260200160405190810160405280929190818152602001828054611aab9061257f565b8015611af85780601f10611acd57610100808354040283529160200191611af8565b820191906000526020600020905b815481529060010190602001808311611adb57829003601f168201915b505050919092525091979650505050505050565b6001600160a01b038416600090815260036020908152604080832086845290915281205481548291908110611b4357611b43612551565b600091825260209182902060408051610140810182526009939093029091018054835260018101546001600160a01b039081169484019490945260028101549183019190915260038101546060830152600481015483166080830152600581015492831660a0830152600160a01b90920460ff1660c082015260068201805491929160e084019190611bd49061257f565b80601f0160208091040260200160405190810160405280929190818152602001828054611c009061257f565b8015611c4d5780601f10611c2257610100808354040283529160200191611c4d565b820191906000526020600020905b815481529060010190602001808311611c3057829003601f168201915b50505050508152602001600782018054611c669061257f565b80601f0160208091040260200160405190810160405280929190818152602001828054611c929061257f565b8015611cdf5780601f10611cb457610100808354040283529160200191611cdf565b820191906000526020600020905b815481529060010190602001808311611cc257829003601f168201915b50505050508152602001600882018054611cf89061257f565b80601f0160208091040260200160405190810160405280929190818152602001828054611d249061257f565b8015611d715780601f10611d4657610100808354040283529160200191611d71565b820191906000526020600020905b815481529060010190602001808311611d5457829003601f168201915b505050919092525050600554919250506001600160a01b0316331480611d9f5750336001600160a01b038416145b611df75760405162461bcd60e51b8152602060048201526024808201527f4f6e6c7920657363726f77206f722062757965722063616e2063616c6c206275604482015263794b534d60e01b606482015260840161017b565b6040808201516001600160a01b038516600090815260026020529190912054611e2091906124ee565b6001600160a01b03808516600090815260026020526040808220939093558383015160a085015190921681529190912054611e5b9190612567565b60a08201516001600160a01b039081166000908152600260209081526040808320949094559188168152600382528281208782529091529081205481548291908110611ea957611ea9612551565b906000526020600020906009020160050160146101000a81548160ff021916908360ff160217905550846001600160a01b03166323b872dd3084876040518463ffffffff1660e01b8152600401611f0293929190612505565b600060405180830381600087803b158015611f1c57600080fd5b505af1158015611f30573d6000803e3d6000fd5b505050506001600160a01b038516600081815260036020908152604080832088845282529182902054848301518351948552918401889052838301526060830152517f68bdd3548f3f05742b376651745ca73472eb18f2230f64b893d7a48e575eb0149181900360800190a15050505050565b828054611faf9061257f565b90600052602060002090601f016020900481019282611fd15760008555612017565b82601f10611fea57805160ff1916838001178555612017565b82800160010185558215612017579182015b82811115612017578251825591602001919060010190611ffc565b50612023929150612027565b5090565b5b808211156120235760008155600101612028565b6001600160a01b038116811461205157600080fd5b50565b6000806000806080858703121561206a57600080fd5b84359350602085013561207c8161203c565b9250604085013561208c8161203c565b9396929550929360600135925050565b6000602082840312156120ae57600080fd5b81356120b98161203c565b9392505050565b600080604083850312156120d357600080fd5b82356120de8161203c565b946020939093013593505050565b600080600080600060a0868803121561210457600080fd5b8535945060208601356121168161203c565b935060408601356121268161203c565b925060608601359150608086013560ff8116811461214357600080fd5b809150509295509295909350565b6000806040838503121561216457600080fd5b8235915060208301356121768161203c565b809150509250929050565b60006020828403121561219357600080fd5b5035919050565b60005b838110156121b557818101518382015260200161219d565b838111156121c4576000848401525b50505050565b600081518084526121e281602086016020860161219a565b601f01601f19169290920160200192915050565b8a81526001600160a01b038a81166020830152604082018a9052606082018990528781166080830152861660a082015260ff851660c082015261014060e08201819052600090612248838201876121ca565b905082810361010084015261225d81866121ca565b905082810361012084015261227281856121ca565b9d9c50505050505050505050505050565b60008060006060848603121561229857600080fd5b8335925060208401356122aa8161203c565b915060408401356122ba8161203c565b809150509250925092565b6020815281516020820152600060208301516122ec60408401826001600160a01b03169052565b506040830151606083015260608301516080830152608083015161231b60a08401826001600160a01b03169052565b5060a08301516001600160a01b03811660c08401525060c083015160ff811660e08401525060e0830151610140610100818186015261235e6101608601846121ca565b9250808601519050601f1961012081878603018188015261237f85846121ca565b90880151878203909201848801529350905061239b83826121ca565b9695505050505050565b600080600080608085870312156123bb57600080fd5b84356123c68161203c565b93506020850135925060408501356123dd8161203c565b915060608501356123ed8161203c565b939692955090935050565b60006020828403121561240a57600080fd5b81516120b98161203c565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561243d57600080fd5b815167ffffffffffffffff8082111561245557600080fd5b818401915084601f83011261246957600080fd5b81518181111561247b5761247b612415565b604051601f8201601f19908116603f011681019083821181831017156124a3576124a3612415565b816040528281528760208487010111156124bc57600080fd5b6124cd83602083016020880161219a565b979650505050505050565b634e487b7160e01b600052601160045260246000fd5b600082821015612500576125006124d8565b500390565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6020808252600e908201526d27b7363c9037bbb732b91031b0b760911b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6000821982111561257a5761257a6124d8565b500190565b600181811c9082168061259357607f821691505b602082108114156125b457634e487b7160e01b600052602260045260246000fd5b50919050565b6000602082840312156125cc57600080fd5b815180151581146120b957600080fdfea26469706673582212205a8295bbb792776496ccaaa9cb7384efe95af6490ca8015ceb6eb22e5ab3ae6b64736f6c634300080a0033 \ No newline at end of file --- /dev/null +++ b/tests/src/eth/marketplace/MarketPlace.sol @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: Apache License +pragma solidity >=0.8.0; +import "../api/UniqueNFT.sol"; + +// Inline +interface ERC20Events { + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + +interface ERC20 is Dummy, ERC165, ERC20Events { + // Selector: name() 06fdde03 + function name() external view returns (string memory); + + // Selector: symbol() 95d89b41 + function symbol() external view returns (string memory); + + // Selector: totalSupply() 18160ddd + function totalSupply() external view returns (uint256); + + // Selector: decimals() 313ce567 + function decimals() external view returns (uint8); + + // Selector: balanceOf(address) 70a08231 + function balanceOf(address owner) external view returns (uint256); + + // Selector: transfer(address,uint256) a9059cbb + function transfer(address to, uint256 amount) external returns (bool); + + // Selector: transferFrom(address,address,uint256) 23b872dd + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); + + // Selector: approve(address,uint256) 095ea7b3 + function approve(address spender, uint256 amount) external returns (bool); + + // Selector: allowance(address,address) dd62ed3e + function allowance(address owner, address spender) + external + view + returns (uint256); +} + +interface UniqueFungible is Dummy, ERC165, ERC20 {} + +contract MarketPlace { + struct Order { + uint256 idNFT; + address currencyCode; // UNIQ tokens as address address (1); wKSM + uint256 price; + uint256 time; + address idCollection; + address ownerAddr; + uint8 flagActive; + string name; + string symbol; + string tokenURI; + } + Order[] public orders; + uint256 test; + mapping(address => uint256) public balanceKSM; // [ownerAddr][currency] => [KSMs] + mapping(address => mapping(uint256 => uint256)) public asks; // [buyer][idCollection][idNFT] => idorder + + mapping(address => uint256[]) public asksbySeller; // [addressSeller] =>idorder + + address escrow; + address owner; + address nativecoin; + + constructor(address _escrow) { + escrow = _escrow; + owner = msg.sender; + + orders.push( + Order(0, address(0), 0, 0, address(0), address(0), 0, "", "", "") + ); + } + + function setowner(address _newEscrow) public onlyOwner { + escrow = _newEscrow; + } + + function setEscrow(address _newEscrow) public onlyOwner { + escrow = _newEscrow; + } + + function setNativeCoin(address _coin) public onlyOwner { + nativecoin = _coin; + } + + modifier onlyEscrow() { + require(msg.sender == escrow, "Only escrow can"); + _; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Only owner can"); + _; + } + + /** + * Make bids (orders) to sell NFTs + */ + + receive() external payable { + revert( + "Can't accept payment without collection and IDs, use dApp to send" + ); + } + + fallback() external payable { + revert("No such function"); + } + + event AddedAsk( + uint256 _price, + address _currencyCode, + address _idCollection, + uint256 _idNFT, + uint256 orderId + ); + event EditedAsk( + uint256 _price, + address _currencyCode, + address _idCollection, + uint256 _idNFT, + uint8 _active, + uint256 orderId + ); + + event CanceledAsk(address _idCollection, uint256 _idNFT, uint256 orderId); + + event DepositedKSM(uint256 _amount, address _sender); + + event BoughtNFT4KSM( + address _idCollection, + uint256 _idNFT, + uint256 orderID, + uint256 orderPrice + ); + + event BoughtNFT( + address _idCollection, + uint256 _idNFT, + uint256 orderID, + uint256 orderPrice + ); + + event WithdrawnAllKSM(address _sender, uint256 balance); + + event Withdrawn(uint256 _amount, address _currencyCode, address _sender); + + function addAsk( + uint256 _price, + address _currencyCode, + address _idCollection, + uint256 _idNFT + ) public { + // + address ownerNFT = UniqueNFT(_idCollection).ownerOf(_idNFT); + require(ownerNFT == msg.sender, "Only token owner can make ask"); + string memory nameNFT; + string memory symbolNFT; + string memory uriNFT; + try UniqueNFT(_idCollection).name() returns (string memory name_) { + nameNFT = name_; + } catch { + nameNFT = ""; + } + try UniqueNFT(_idCollection).symbol() returns (string memory symbol_) { + symbolNFT = symbol_; + } catch { + symbolNFT = ""; + } + try UniqueNFT(_idCollection).tokenURI(_idNFT) returns ( + string memory uri_ + ) { + uriNFT = uri_; + } catch { + uriNFT = ""; + } + orders.push( + Order( + _idNFT, + _currencyCode, + _price, + block.timestamp, + _idCollection, + msg.sender, + 1, // 1 = is active + nameNFT, + symbolNFT, + uriNFT + ) + ); + + uint256 orderId = orders.length - 1; + asks[_idCollection][_idNFT] = orderId; + asksbySeller[msg.sender].push(orderId); + UniqueNFT(_idCollection).transferFrom( + msg.sender, + address(this), + _idNFT + ); + emit AddedAsk(_price, _currencyCode, _idCollection, _idNFT, orderId); + } + + function editAsk( + uint256 _price, + address _currencyCode, + address _idCollection, + uint256 _idNFT, + uint8 _active + ) public { + uint256 orderID = asks[_idCollection][_idNFT]; + + require( + orders[orderID].ownerAddr == msg.sender, + "Only token owner can edit ask" + ); + require(orders[orderID].flagActive != 0, "This ask is closed"); + if (_price > 0) { + orders[orderID].price = _price; + } + + if (_currencyCode != address(0)) { + orders[orderID].currencyCode = _currencyCode; + } + + orders[orderID].time = block.timestamp; + orders[orderID].flagActive = _active; + + emit EditedAsk( + _price, + _currencyCode, + _idCollection, + _idNFT, + _active, + orderID + ); + } + + function cancelAsk(address _idCollection, uint256 _idNFT) public { + uint256 orderID = asks[_idCollection][_idNFT]; + + require( + orders[orderID].ownerAddr == msg.sender, + "Only token owner can edit ask" + ); + require(orders[orderID].flagActive != 0, "This ask is closed"); + + orders[orderID].time = block.timestamp; + orders[orderID].flagActive = 0; + UniqueNFT(_idCollection).transferFrom( + address(this), + orders[orderID].ownerAddr, + _idNFT + ); + emit CanceledAsk(_idCollection, _idNFT, orderID); + } + + function depositKSM(uint256 _amount, address _sender) public onlyEscrow { + balanceKSM[_sender] = balanceKSM[_sender] + _amount; + emit DepositedKSM(_amount, _sender); + } + + function buyKSM( + address _idCollection, + uint256 _idNFT, + address _buyer, + address _receiver + ) public { + Order memory order = orders[asks[_idCollection][_idNFT]]; + require(msg.sender == escrow || msg.sender == _buyer, "Only escrow or buyer can call buyKSM" ); + //1. reduce balance + balanceKSM[_buyer] = balanceKSM[_buyer] - order.price; + balanceKSM[order.ownerAddr] = balanceKSM[order.ownerAddr] + order.price; + // 2. close order + orders[asks[_idCollection][_idNFT]].flagActive = 0; + // 3. transfer NFT to buyer + UniqueNFT(_idCollection).transferFrom(address(this), _receiver, _idNFT); + emit BoughtNFT4KSM( + _idCollection, + _idNFT, + asks[_idCollection][_idNFT], + order.price + ); + } + + function buy(address _idCollection, uint256 _idNFT) + public + payable + returns (bool result) + { + //buing for UNQ like as ethers + + Order memory order = orders[asks[_idCollection][_idNFT]]; + //1. check sent amount and send to seller + require( + msg.value == order.price, + "Not right amount sent, have to be equal price" + ); + // 2. close order + orders[asks[_idCollection][_idNFT]].flagActive = 0; + + // 3. transfer NFT to buyer + UniqueNFT(_idCollection).transferFrom( + address(this), + msg.sender, + _idNFT + ); + //uint balance = address(this).balance; + result = payable(order.ownerAddr).send(order.price); + emit BoughtNFT( + _idCollection, + _idNFT, + asks[_idCollection][_idNFT], + order.price + ); + } + + /* + function buyOther (address _idCollection, uint256 _idNFT, address _currencyCode, uint _amount ) public { //buy for sny token if seller wants + + Order memory order = orders[ asks[_idCollection][_idNFT]]; + //1. check sent amount and transfer from buyer to seller + require (order.price == _amount && order.currencyCode == _currencyCode, "Not right amount or currency sent, have to be equal currency and price" ); + // !!! transfer have to be approved to marketplace! + IERC20(order.currencyCode).transferFrom(msg.sender, address(this), order.price); //to not disclojure buyer's address + IERC20(order.currencyCode).transfer(order.ownerAddr, order.price); + // 2. close order + orders[ asks[_idCollection][_idNFT]].flagActive = 0; + // 3. transfer NFT to buyer + IERC721ext(_idCollection).transferFrom(address(this), msg.sender, _idNFT); + + + } + */ + + function withdrawAllKSM(address _sender) + public + onlyEscrow + returns (uint256 lastBalance) + { + lastBalance = balanceKSM[_sender]; + balanceKSM[_sender] = 0; + emit WithdrawnAllKSM(_sender, lastBalance); + } + + function withdraw( + uint256 _amount, + address _currencyCode, + address payable _sender + ) public onlyOwner returns (bool result) { + if (_currencyCode != nativecoin) { + //erc20 compat. tokens on UNIQUE chain + // uint balance = IERC20(_currencyCode).balanceOf(address(this)); + UniqueFungible(_currencyCode).transfer(_sender, _amount); + } else { + // uint balance = address(this).balance; + + result = (_sender).send(_amount); // for UNQ like as ethers + } + emit Withdrawn(_amount, _currencyCode, _sender); + return result; + } + + // event GettedOrder(uint , Order); + function getOrder(address _idCollection, uint256 _idNFT) + public + view + returns (Order memory) + { + uint256 orderId = asks[_idCollection][_idNFT]; + Order memory order = orders[orderId]; + // emit GettedOrder (orderId, order); + return order; + } + + function getOrdersLen() public view returns (uint256) { + return orders.length; + } +} --- a/tests/src/eth/marketplace/MarketPlaceKSM.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"_escrow","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asksbySeller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"balanceKSM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"buy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"}],"name":"escrowBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"offers","outputs":[{"internalType":"uint256","name":"idNFT","type":"uint256"},{"internalType":"uint256","name":"currencyCode","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"address","name":"idCollection","type":"address"},{"internalType":"address","name":"userAddr","type":"address"},{"internalType":"uint8","name":"flagActive","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"uint8","name":"_active","type":"uint8"}],"name":"setAsk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newEscrow","type":"address"}],"name":"setEscrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newEscrow","type":"address"}],"name":"setowner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_currencyCode","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file --- a/tests/src/eth/marketplace/MarketPlaceKSM.bin +++ /dev/null @@ -1 +0,0 @@ -608060405234801561001057600080fd5b50604051610e27380380610e2783398101604081905261002f9161005d565b600480546001600160a01b039092166001600160a01b0319928316179055600580549091163317905561008d565b60006020828403121561006f57600080fd5b81516001600160a01b038116811461008657600080fd5b9392505050565b610d8b8061009c6000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806399f2d4eb1161007157806399f2d4eb1461018e5780639df55f52146101a1578063c10c35461461010f578063cce7ec13146101b4578063db87db52146101c7578063e22d4f7d146101fa57600080fd5b80630ad58d2f146100ae57806310a5ff17146100d6578063592bd7051461010f5780638a72ea6a146101245780638dbdbe6d1461017b575b600080fd5b6100c16100bc366004610c4d565b610225565b60405190151581526020015b60405180910390f35b6101016100e4366004610bd8565b600160209081526000928352604080842090915290825290205481565b6040519081526020016100cd565b61012261011d366004610b97565b6102d8565b005b610137610132366004610c04565b610345565b6040805197885260208801969096529486019390935260608501919091526001600160a01b0390811660808501521660a083015260ff1660c082015260e0016100cd565b610122610189366004610c4d565b6103a4565b61010161019c366004610bd8565b610449565b6101226101af366004610c86565b61047a565b6101226101c2366004610bd8565b610984565b6101016101d5366004610c1d565b6001600160a01b03166000908152600160209081526040808320938352929052205490565b610101610208366004610bd8565b600260209081526000928352604080842090915290825290205481565b6004546000906001600160a01b031633146102795760405162461bcd60e51b815260206004820152600f60248201526e27b7363c9032b9b1b937bb9031b0b760891b60448201526064015b60405180910390fd5b6001600160a01b03821660009081526001602090815260408083208684529091529020546102a8908590610cfa565b6001600160a01b039290921660009081526001602081815260408084209684529590529390209190915550919050565b6005546001600160a01b031633146103235760405162461bcd60e51b815260206004820152600e60248201526d27b7363c9037bbb732b91031b0b760911b6044820152606401610270565b600480546001600160a01b0319166001600160a01b0392909216919091179055565b6000818154811061035557600080fd5b6000918252602090912060069091020180546001820154600283015460038401546004850154600590950154939550919390926001600160a01b0391821691811690600160a01b900460ff1687565b6004546001600160a01b031633146103f05760405162461bcd60e51b815260206004820152600f60248201526e27b7363c9032b9b1b937bb9031b0b760891b6044820152606401610270565b6001600160a01b038116600090815260016020908152604080832085845290915290205461041f908490610ce2565b6001600160a01b039091166000908152600160209081526040808320948352939052919091205550565b6003602052816000526040600020818154811061046557600080fd5b90600052602060002001600091509150505481565b6040516331a9108f60e11b81526004810183905233906001600160a01b03851690636352211e9060240160206040518083038186803b1580156104bc57600080fd5b505afa1580156104d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104f49190610bbb565b6001600160a01b0316146105425760405162461bcd60e51b81526020600482015260156024820152742737ba103934b3b43a103a37b5b2b71037bbb732b960591b6044820152606401610270565b6001600160a01b0383166000908152600260209081526040808320858452909152812054905415806105ac575060006001600160a01b03166000828154811061058d5761058d610d27565b60009182526020909120600460069092020101546001600160a01b0316145b15610793576040805160e0810182528481526020810187815291810188815242606083019081526001600160a01b03808916608085019081523360a0860190815260ff808a1660c088019081526000805460018181018355828052995160069091027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56381019190915599517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5648b015596517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5658a015594517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56689015591517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e567880180549185166001600160a01b0319909216919091179055517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56890960180549351909116600160a01b026001600160a81b031990931695909116949094171790925590546107399190610cfa565b6001600160a01b038516600090815260026020908152604080832087845282528083209390935533825260039052908120905461077890600190610cfa565b81546001810183556000928352602090922090910155610916565b6040805160e0810182526001600160a01b03861660009081526002602090815283822087835290529182205482549192839290919081106107d6576107d6610d27565b60009182526020808320600690920290910154835282810189905260408084018b90524260608501526001600160a01b0389168352600282528083208884529091528120548154608090930192811061083157610831610d27565b60009182526020808320600460069093020191909101546001600160a01b039081168452338483015260ff871660409485015288168252600281528282208783529052908120548154811061088857610888610d27565b60009182526020918290208351600690920201908155908201516001820155604082015160028201556060820151600382015560808201516004820180546001600160a01b039283166001600160a01b031990911617905560a08301516005909201805460c09094015160ff16600160a01b026001600160a81b031990941692909116919091179190911790555b6040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b038516906323b872dd90606401600060405180830381600087803b15801561096457600080fd5b505af1158015610978573d6000803e3d6000fd5b50505050505050505050565b6001600160a01b0382166000908152600260209081526040808320848452909152812054815482919081106109bb576109bb610d27565b600091825260208083206040805160e0810182526006949094029091018054845260018082015485850190815260028301548685018190526003840154606088015260048401546001600160a01b03908116608089015260059094015493841660a0880152600160a01b90930460ff1660c08701523387529084528286209051865290925290922054909250610a519190610cfa565b33600090815260016020818152604080842086830180518652908352818520959095558581015160a08701516001600160a01b031685529282528084209451845293905291902054610aa39190610ce2565b60a08201516001600160a01b0390811660009081526001602090815260408083208287015184528252808320949094559186168152600282528281208582529091529081205481548291908110610afc57610afc610d27565b60009182526020909120600560069092020101805460ff92909216600160a01b0260ff60a01b199092169190911790556040516323b872dd60e01b8152306004820152336024820152604481018390526001600160a01b038416906323b872dd90606401600060405180830381600087803b158015610b7a57600080fd5b505af1158015610b8e573d6000803e3d6000fd5b50505050505050565b600060208284031215610ba957600080fd5b8135610bb481610d3d565b9392505050565b600060208284031215610bcd57600080fd5b8151610bb481610d3d565b60008060408385031215610beb57600080fd5b8235610bf681610d3d565b946020939093013593505050565b600060208284031215610c1657600080fd5b5035919050565b60008060408385031215610c3057600080fd5b823591506020830135610c4281610d3d565b809150509250929050565b600080600060608486031215610c6257600080fd5b83359250602084013591506040840135610c7b81610d3d565b809150509250925092565b600080600080600060a08688031215610c9e57600080fd5b85359450602086013593506040860135610cb781610d3d565b925060608601359150608086013560ff81168114610cd457600080fd5b809150509295509295909350565b60008219821115610cf557610cf5610d11565b500190565b600082821015610d0c57610d0c610d11565b500390565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0381168114610d5257600080fd5b5056fea2646970667358221220d098ae4e0cc7bdbb7de1061c03402a667836f903122c1c606ca2c65db1ab78e964736f6c63430008070033 \ No newline at end of file --- a/tests/src/eth/marketplace/MarketPlaceKSM.sol +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: Apache License -pragma solidity >=0.8.0; - -interface IERC721 { - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - function ownerOf(uint256 tokenId) external view returns (address owner); - - function transferFrom( - address from, - address to, - uint256 tokenId - ) external; - - function debug(string memory value) external; -} - -interface IERC20 { - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - - function transfer(address recipient, uint256 amount) - external - returns (bool); - - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool); -} - -contract MarketPlaceKSM { - struct Offer { - uint256 idNFT; - uint256 currencyCode; - uint256 price; - uint256 time; - address idCollection; - address userAddr; - uint8 flagActive; - } - Offer[] public offers; - - mapping(address => mapping(uint256 => uint256)) public balanceKSM; // [userAddr] => [KSMs] - mapping(address => mapping(uint256 => uint256)) public asks; // [buyer][idCollection][idNFT] => idOffer - - mapping(address => uint256[]) public asksbySeller; // [addressSeller] =>idOffer - - address escrow; - address owner; - - constructor(address _escrow) { - escrow = _escrow; - owner = msg.sender; - } - - function setowner(address _newEscrow) public onlyOwner { - escrow = _newEscrow; - } - - function setEscrow(address _newEscrow) public onlyOwner { - escrow = _newEscrow; - } - - modifier onlyEscrow() { - require(msg.sender == escrow, "Only escrow can"); - _; - } - - modifier onlyOwner() { - require(msg.sender == owner, "Only owner can"); - _; - } - - /** - * Make bids (offers) to sell NFTs - */ - function setAsk( - uint256 _price, - uint256 _currencyCode, - address _idCollection, - uint256 _idNFT, - uint8 _active - ) public { - require( - IERC721(_idCollection).ownerOf(_idNFT) == msg.sender, - "Not right token owner" - ); - uint256 offerID = asks[_idCollection][_idNFT]; - if (offers.length == 0 || offers[offerID].idCollection == address(0)) { - offers.push( - Offer( - _idNFT, - _currencyCode, - _price, - block.timestamp, - _idCollection, - msg.sender, - _active - ) - ); - asks[_idCollection][_idNFT] = offers.length - 1; - asksbySeller[msg.sender].push(offers.length - 1); - } - //edit existing offer - else { - offers[asks[_idCollection][_idNFT]] = Offer( - offers[asks[_idCollection][_idNFT]].idNFT, - _currencyCode, - _price, - block.timestamp, - offers[asks[_idCollection][_idNFT]].idCollection, - msg.sender, - _active - ); - } - - IERC721(_idCollection).transferFrom(msg.sender, address(this), _idNFT); - } - - function deposit( - uint256 _amount, - uint256 _currencyCode, - address _sender - ) public onlyEscrow { - balanceKSM[_sender][_currencyCode] = - balanceKSM[_sender][_currencyCode] + - _amount; - } - - function buy(address _idCollection, uint256 _idNFT) public { - Offer memory offer = offers[asks[_idCollection][_idNFT]]; - // 1. reduce balance - balanceKSM[msg.sender][offer.currencyCode] = - balanceKSM[msg.sender][offer.currencyCode] - - offer.price; - // 2. increase balance - balanceKSM[offer.userAddr][offer.currencyCode] = - balanceKSM[offer.userAddr][offer.currencyCode] + - offer.price; - // 3. close offer - offers[asks[_idCollection][_idNFT]].flagActive = 0; - // 4. transfer NFT to buyer - IERC721(_idCollection).transferFrom(address(this), msg.sender, _idNFT); - } - - function withdraw( - uint256 _amount, - uint256 _currencyCode, - address _sender - ) public onlyEscrow returns (bool) { - balanceKSM[_sender][_currencyCode] = - balanceKSM[_sender][_currencyCode] - - _amount; - return true; - } - - function escrowBalance(uint256 _currencyCode, address _sender) - public - view - returns (uint256) - { - return balanceKSM[_sender][_currencyCode]; - } -} --- a/tests/src/eth/marketplace/MarketPlaceUNQ.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"asksbySeller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"address","name":"_currencyCode","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"buy","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"}],"name":"buy","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"offers","outputs":[{"internalType":"uint256","name":"idNFT","type":"uint256"},{"internalType":"address","name":"currencyCode","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"address","name":"idCollection","type":"address"},{"internalType":"address","name":"userAddr","type":"address"},{"internalType":"uint8","name":"flagActive","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"address","name":"_currencyCode","type":"address"},{"internalType":"address","name":"_idCollection","type":"address"},{"internalType":"uint256","name":"_idNFT","type":"uint256"},{"internalType":"uint8","name":"_active","type":"uint8"}],"name":"setAsk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"setowner","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file --- a/tests/src/eth/marketplace/MarketPlaceUNQ.bin +++ /dev/null @@ -1 +0,0 @@ -608060405234801561001057600080fd5b50600380546001600160a01b03191633179055610f2d806100326000396000f3fe6080604052600436106100705760003560e01c806399f2d4eb1161004e57806399f2d4eb14610120578063b3ffb7601461014e578063cce7ec1314610161578063e22d4f7d1461017457600080fd5b8063592bd705146100755780637b1d6001146100975780638a72ea6a146100b7575b600080fd5b34801561008157600080fd5b50610095610090366004610d2b565b6101ac565b005b3480156100a357600080fd5b506100956100b2366004610e1b565b61021e565b3480156100c357600080fd5b506100d76100d2366004610e02565b61073d565b604080519788526001600160a01b03968716602089015287019490945260608601929092528316608085015290911660a083015260ff1660c082015260e0015b60405180910390f35b34801561012c57600080fd5b5061014061013b366004610d6c565b6107a0565b604051908152602001610117565b61009561015c366004610d98565b6107d1565b61009561016f366004610d6c565b610b0b565b34801561018057600080fd5b5061014061018f366004610d6c565b600160209081526000928352604080842090915290825290205481565b6003546001600160a01b031633146101fc5760405162461bcd60e51b815260206004820152600e60248201526d27b7363c9037bbb732b91031b0b760911b60448201526064015b60405180910390fd5b600380546001600160a01b0319166001600160a01b0392909216919091179055565b6040516331a9108f60e11b81526004810183905233906001600160a01b03851690636352211e9060240160206040518083038186803b15801561026057600080fd5b505afa158015610274573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102989190610d4f565b6001600160a01b0316146102e65760405162461bcd60e51b81526020600482015260156024820152742737ba103934b3b43a103a37b5b2b71037bbb732b960591b60448201526064016101f3565b6001600160a01b038316600090815260016020908152604080832085845290915281205490541580610350575060006001600160a01b03166000828154811061033157610331610ec9565b60009182526020909120600460069092020101546001600160a01b0316145b15610542576040805160e0810182528481526001600160a01b03878116602083019081529282018981524260608401908152888316608085019081523360a0860190815260ff89811660c08801908152600080546001808201835582805299517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56360069092029182015599517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5648b0180546001600160a01b0319908116928b1692909217905596517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5658b015594517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5668a015592517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5678901805490961690871617909455517f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56890960180549151969094166001600160a81b031990911617600160a01b959092169490940217905590546104e99190610ea4565b6001600160a01b0385166000908152600160208181526040808420888552825280842094909455338352600290529181209054909161052791610ea4565b815460018101835560009283526020909220909101556106d3565b6040805160e0810182526001600160a01b038616600090815260016020908152838220878352905291822054825491928392909190811061058557610585610ec9565b6000918252602080832060069092029091015483526001600160a01b03808a168483015260408085018c9052426060860152908916835260018252808320888452909152812054815460809093019281106105e2576105e2610ec9565b60009182526020808320600460069093020191909101546001600160a01b039081168452338483015260ff871660409485015288168252600181528282208783529052908120548154811061063957610639610ec9565b60009182526020918290208351600690920201908155908201516001820180546001600160a01b039283166001600160a01b031991821617909155604084015160028401556060840151600384015560808401516004840180549184169190921617905560a08301516005909201805460c09094015160ff16600160a01b026001600160a81b031990941692909116919091179190911790555b6040516323b872dd60e01b81526001600160a01b038516906323b872dd9061070390339030908890600401610e80565b600060405180830381600087803b15801561071d57600080fd5b505af1158015610731573d6000803e3d6000fd5b50505050505050505050565b6000818154811061074d57600080fd5b60009182526020909120600690910201805460018201546002830154600384015460048501546005909501549395506001600160a01b03928316949193909291821691811690600160a01b900460ff1687565b600260205281600052604060002081815481106107bc57600080fd5b90600052602060002001600091509150505481565b6001600160a01b03841660009081526001602090815260408083208684529091528120548154829190811061080857610808610ec9565b60009182526020918290206040805160e0810182526006939093029091018054835260018101546001600160a01b03908116948401949094526002810154918301829052600381015460608401526004810154841660808401526005015492831660a0830152600160a01b90920460ff1660c08201529150821480156108a35750826001600160a01b031681602001516001600160a01b0316145b6109245760405162461bcd60e51b815260206004820152604660248201527f4e6f7420726967687420616d6f756e74206f722063757272656e63792073656e60448201527f742c206861766520746f20626520657175616c2063757272656e637920616e6460648201526520707269636560d01b608482015260a4016101f3565b80602001516001600160a01b03166323b872dd333084604001516040518463ffffffff1660e01b815260040161095c93929190610e80565b602060405180830381600087803b15801561097657600080fd5b505af115801561098a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ae9190610de0565b50602081015160a0820151604080840151905163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb90604401602060405180830381600087803b158015610a0957600080fd5b505af1158015610a1d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a419190610de0565b506001600160a01b038516600090815260016020908152604080832087845290915281205481548291908110610a7957610a79610ec9565b906000526020600020906006020160050160146101000a81548160ff021916908360ff160217905550846001600160a01b03166323b872dd3033876040518463ffffffff1660e01b8152600401610ad293929190610e80565b600060405180830381600087803b158015610aec57600080fd5b505af1158015610b00573d6000803e3d6000fd5b505050505050505050565b6001600160a01b038216600090815260016020908152604080832084845290915281205481548291908110610b4257610b42610ec9565b60009182526020918290206040805160e0810182526006939093029091018054835260018101546001600160a01b03908116948401949094526002810154918301829052600381015460608401526004810154841660808401526005015492831660a0830152600160a01b90920460ff1660c082015291503414610c225760405162461bcd60e51b815260206004820152603160248201527f4e6f7420726967687420616d6f756e7420554e512073656e742c206861766520604482015270746f20626520657175616c20707269636560781b60648201526084016101f3565b8060a001516001600160a01b03166108fc82604001519081150290604051600060405180830381858888f19350505050158015610c63573d6000803e3d6000fd5b506001600160a01b038316600090815260016020908152604080832085845290915281205481548291908110610c9b57610c9b610ec9565b906000526020600020906006020160050160146101000a81548160ff021916908360ff160217905550826001600160a01b03166323b872dd3033856040518463ffffffff1660e01b8152600401610cf493929190610e80565b600060405180830381600087803b158015610d0e57600080fd5b505af1158015610d22573d6000803e3d6000fd5b50505050505050565b600060208284031215610d3d57600080fd5b8135610d4881610edf565b9392505050565b600060208284031215610d6157600080fd5b8151610d4881610edf565b60008060408385031215610d7f57600080fd5b8235610d8a81610edf565b946020939093013593505050565b60008060008060808587031215610dae57600080fd5b8435610db981610edf565b9350602085013592506040850135610dd081610edf565b9396929550929360600135925050565b600060208284031215610df257600080fd5b81518015158114610d4857600080fd5b600060208284031215610e1457600080fd5b5035919050565b600080600080600060a08688031215610e3357600080fd5b853594506020860135610e4581610edf565b93506040860135610e5581610edf565b925060608601359150608086013560ff81168114610e7257600080fd5b809150509295509295909350565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600082821015610ec457634e487b7160e01b600052601160045260246000fd5b500390565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0381168114610ef457600080fd5b5056fea2646970667358221220fbf33eb1e471c428b86ad6d948b8699fd08a127b923d5fe27ec1e1ec5f59627564736f6c63430008070033 \ No newline at end of file --- a/tests/src/eth/marketplace/MarketPlaceUNQ.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: Apache License -pragma solidity >=0.8.0; - -interface IERC721 { - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - function ownerOf(uint256 tokenId) external view returns (address owner); - - function transferFrom( - address from, - address to, - uint256 tokenId - ) external; - - function debug(string memory value) external; -} - -interface IERC20 { - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - - function transfer(address recipient, uint256 amount) - external - returns (bool); - - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool); -} - -contract MarketPlaceUNQ { - struct Offer { - uint256 idNFT; - address currencyCode; //address of currency token, = address(0) for UNQ - uint256 price; - uint256 time; - address idCollection; - address userAddr; - uint8 flagActive; - } - Offer[] public offers; - - mapping(address => mapping(uint256 => uint256)) public asks; // [idCollection][idNFT] => idOffer - - mapping(address => uint256[]) public asksbySeller; // [addressSeller] =>idOffer - - address owner; - - constructor() { - owner = msg.sender; - } - - function setowner(address _newOwner) public onlyOwner { - owner = _newOwner; - } - - modifier onlyOwner() { - require(msg.sender == owner, "Only owner can"); - _; - } - - /** - * Make bids (offers) to sell NFTs - */ - function setAsk( - uint256 _price, - address _currencyCode, - address _idCollection, - uint256 _idNFT, - uint8 _active - ) public { - require( - IERC721(_idCollection).ownerOf(_idNFT) == msg.sender, - "Not right token owner" - ); - uint256 offerID = asks[_idCollection][_idNFT]; - if (offers.length == 0 || offers[offerID].idCollection == address(0)) { - offers.push( - Offer( - _idNFT, - _currencyCode, - _price, - block.timestamp, - _idCollection, - msg.sender, - _active - ) - ); - asks[_idCollection][_idNFT] = offers.length - 1; - asksbySeller[msg.sender].push(offers.length - 1); - } - //edit existing offer - else { - offers[asks[_idCollection][_idNFT]] = Offer( - offers[asks[_idCollection][_idNFT]].idNFT, - _currencyCode, - _price, - block.timestamp, - offers[asks[_idCollection][_idNFT]].idCollection, - msg.sender, - _active - ); - } - - IERC721(_idCollection).transferFrom(msg.sender, address(this), _idNFT); - } - - function buy(address _idCollection, uint256 _idNFT) public payable { - //buing for UNQ like as ethers - Offer memory offer = offers[asks[_idCollection][_idNFT]]; - //1. check sent amount and send to seller - require( - msg.value == offer.price, - "Not right amount UNQ sent, have to be equal price" - ); - payable(offer.userAddr).transfer(offer.price); - // 2. close offer - offers[asks[_idCollection][_idNFT]].flagActive = 0; - // 3. transfer NFT to buyer - IERC721(_idCollection).transferFrom(address(this), msg.sender, _idNFT); - } - - function buy( - address _idCollection, - uint256 _idNFT, - address _currencyCode, - uint256 _amount - ) public payable { - Offer memory offer = offers[asks[_idCollection][_idNFT]]; - //1. check sent amount and transfer from buyer to seller - require( - offer.price == _amount && offer.currencyCode == _currencyCode, - "Not right amount or currency sent, have to be equal currency and price" - ); - // !!! transfer have to be approved to marketplace! - IERC20(offer.currencyCode).transferFrom( - msg.sender, - address(this), - offer.price - ); - //to not disclojure buyer's address - IERC20(offer.currencyCode).transfer(offer.userAddr, offer.price); - // 2. close offer - offers[asks[_idCollection][_idNFT]].flagActive = 0; - // 3. transfer NFT to buyer - IERC721(_idCollection).transferFrom(address(this), msg.sender, _idNFT); - } -} --- a/tests/src/eth/marketplace/marketplace.test.ts +++ b/tests/src/eth/marketplace/marketplace.test.ts @@ -1,8 +1,8 @@ import {readFile} from 'fs/promises'; import {getBalanceSingle, transferBalanceExpectSuccess} from '../../substrate/get-balance'; import privateKey from '../../substrate/privateKey'; -import {createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, getTokenOwner, transferExpectSuccess, transferFromExpectSuccess} from '../../util/helpers'; -import {collectionIdToAddress, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, subToEth, subToEthLowercase} from '../util/helpers'; +import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, getTokenOwner, setCollectionSponsorExpectSuccess, transferExpectSuccess, transferFromExpectSuccess} from '../../util/helpers'; +import {collectionIdToAddress, contractHelpers, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, subToEth, subToEthLowercase, transferBalanceToEth} from '../util/helpers'; import {evmToAddress} from '@polkadot/util-crypto'; import nonFungibleAbi from '../nonFungibleAbi.json'; import fungibleAbi from '../fungibleAbi.json'; @@ -12,34 +12,39 @@ describe('Matcher contract usage', () => { itWeb3('With UNQ', async ({api, web3}) => { + const alice = privateKey('//Alice'); + const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId)); const matcherOwner = await createEthAccountWithBalance(api, web3); - const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlaceUNQ.abi`)).toString()), undefined, { + const helpers = contractHelpers(web3, matcherOwner); + + const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, { from: matcherOwner, ...GAS_ARGS, }); - const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlaceUNQ.bin`)).toString()}).send({from: matcherOwner}); + const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments:[matcherOwner]}).send({from: matcherOwner}); - const alice = privateKey('//Alice'); - const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), {from: matcherOwner}); + await transferBalanceToEth(api, alice, matcher.options.address); + await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: matcherOwner}); - const seller = privateKey('//Bob'); + await transferBalanceToEth(api, alice, subToEth(alice.address)); + await setCollectionSponsorExpectSuccess(collectionId, alice.address); + await confirmSponsorshipExpectSuccess(collectionId); + const seller = privateKey('//Seller/' + Date.now()); + await addToAllowListExpectSuccess(alice, collectionId, {Ethereum:subToEth(seller.address)}); + await helpers.methods.toggleAllowed(matcher.options.address, subToEth(seller.address), true).send({from: matcherOwner}); + const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address); - // To transfer item to matcher it first needs to be transfered to EVM account of bob + // To transfer item to matcher it first needs to be transfered to EVM account of seller await transferExpectSuccess(collectionId, tokenId, seller, {Ethereum: subToEth(seller.address)}); - // Fees will be paid from EVM account, so we should have some balance here - await transferBalanceExpectSuccess(api, seller, evmToAddress(subToEth(seller.address)), 10n ** 18n); - await transferBalanceExpectSuccess(api, alice, evmToAddress(subToEth(alice.address)), 10n ** 18n); - - // Token is owned by seller initially expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)}); // Ask { await executeEthTxOnSub(api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId)); - await executeEthTxOnSub(api, seller, matcher, m => m.setAsk(PRICE, '0x0000000000000000000000000000000000000000', evmCollection.options.address, tokenId, 1)); + await executeEthTxOnSub(api, seller, matcher, m => m.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, tokenId)); } // Token is transferred to matcher @@ -48,14 +53,12 @@ // Buy { const sellerBalanceBeforePurchase = await getBalanceSingle(api, seller.address); - // There is two functions named 'buy', so we should provide full signature - await executeEthTxOnSub(api, alice, matcher, m => m['buy(address,uint256)'](evmCollection.options.address, tokenId), {value: PRICE}); + await executeEthTxOnSub(api, alice, matcher, m => m.buy(evmCollection.options.address, tokenId), {value: PRICE}); expect(await getBalanceSingle(api, seller.address) - sellerBalanceBeforePurchase === PRICE); } // Token is transferred to evm account of alice expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(alice.address)}); - // Transfer token to substrate side of alice await transferFromExpectSuccess(collectionId, tokenId, alice, {Ethereum: subToEth(alice.address)}, {Substrate: alice.address}); @@ -64,13 +67,14 @@ expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Substrate: alice.address}); }); - itWeb3('With custom ERC20', async ({api, web3}) => { + // selling for custom tokens excluded from release + itWeb3.skip('With custom ERC20', async ({api, web3}) => { const matcherOwner = await createEthAccountWithBalance(api, web3); - const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlaceUNQ.abi`)).toString()), undefined, { + const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, { from: matcherOwner, ...GAS_ARGS, }); - const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlaceUNQ.bin`)).toString()}).send({from: matcherOwner}); + const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments: [matcherOwner]}).send({from: matcherOwner}); const alice = privateKey('//Alice'); const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); @@ -129,55 +133,59 @@ }); itWeb3('With escrow', async ({api, web3}) => { + const alice = privateKey('//Alice'); + const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId)); const matcherOwner = await createEthAccountWithBalance(api, web3); + const helpers = contractHelpers(web3, matcherOwner); const escrow = await createEthAccountWithBalance(api, web3); - const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlaceKSM.abi`)).toString()), undefined, { + + const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, { from: matcherOwner, ...GAS_ARGS, }); - const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlaceKSM.bin`)).toString(), arguments: [escrow]}).send({from: matcherOwner}); + const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments: [matcherOwner]}).send({from: matcherOwner}); + await matcher.methods.setEscrow(escrow).send({from: matcherOwner}); - const ksmToken = 11; + await transferBalanceToEth(api, alice, matcher.options.address); + await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: matcherOwner}); - const alice = privateKey('//Alice'); - const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), {from: matcherOwner}); + await transferBalanceToEth(api, alice, subToEth(alice.address)); + await setCollectionSponsorExpectSuccess(collectionId, alice.address); + await confirmSponsorshipExpectSuccess(collectionId); - const seller = privateKey('//Bob'); + const seller = privateKey('//Seller/' + Date.now()); + await helpers.methods.toggleAllowed(matcher.options.address, subToEth(seller.address), true).send({from: matcherOwner}); + await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(seller.address))); const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address); - // To transfer item to matcher it first needs to be transfered to EVM account of bob + // To transfer item to matcher it first needs to be transfered to EVM account of seller await transferExpectSuccess(collectionId, tokenId, seller, {Ethereum: subToEth(seller.address)}); - // Fees will be paid from EVM account, so we should have some balance here - await transferBalanceExpectSuccess(api, seller, evmToAddress(subToEth(seller.address)), 10n ** 18n); - await transferBalanceExpectSuccess(api, alice, evmToAddress(subToEth(alice.address)), 10n ** 18n); - - // Token is owned by seller initially expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)}); // Ask { await executeEthTxOnSub(api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId)); - await executeEthTxOnSub(api, seller, matcher, m => m.setAsk(PRICE, ksmToken, evmCollection.options.address, tokenId, 1)); + await executeEthTxOnSub(api, seller, matcher, m => m.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, tokenId)); } // Token is transferred to matcher expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: matcher.options.address.toLowerCase()}); // Give buyer KSM - await matcher.methods.deposit(PRICE, ksmToken, subToEth(alice.address)).send({from: escrow}); + await matcher.methods.depositKSM(PRICE, subToEth(alice.address)).send({from: escrow}); // Buy { - expect(await matcher.methods.escrowBalance(ksmToken, subToEth(seller.address)).call()).to.be.equal('0'); - expect(await matcher.methods.escrowBalance(ksmToken, subToEth(alice.address)).call()).to.be.equal(PRICE.toString()); + expect(await matcher.methods.balanceKSM(subToEth(seller.address)).call()).to.be.equal('0'); + expect(await matcher.methods.balanceKSM(subToEth(alice.address)).call()).to.be.equal(PRICE.toString()); - await executeEthTxOnSub(api, alice, matcher, m => m.buy(evmCollection.options.address, tokenId)); + await executeEthTxOnSub(api, alice, matcher, m => m.buyKSM(evmCollection.options.address, tokenId, subToEth(alice.address), subToEth(alice.address))); // Price is removed from buyer balance, and added to seller - expect(await matcher.methods.escrowBalance(ksmToken, subToEth(alice.address)).call()).to.be.equal('0'); - expect(await matcher.methods.escrowBalance(ksmToken, subToEth(seller.address)).call()).to.be.equal(PRICE.toString()); + expect(await matcher.methods.balanceKSM(subToEth(alice.address)).call()).to.be.equal('0'); + expect(await matcher.methods.balanceKSM(subToEth(seller.address)).call()).to.be.equal(PRICE.toString()); } // Token is transferred to evm account of alice --- a/tests/src/eth/util/helpers.ts +++ b/tests/src/eth/util/helpers.ts @@ -228,6 +228,18 @@ return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS}); } +/** + * Execute ethereum method call using substrate account + * @param to target contract + * @param mkTx - closure, receiving `contract.methods`, and returning method call, + * to be used as following (assuming `to` = erc20 contract): + * `m => m.transfer(to, amount)` + * + * # Example + * ```ts + * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount)); + * ``` + */ export async function executeEthTxOnSub(api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) { const tx = api.tx.evm.call( subToEth(from.address), @@ -246,6 +258,11 @@ return (await getBalance(api, [evmToAddress(address)]))[0]; } +/** + * Measure how much gas given closure consumes + * + * @param user which user balance will be checked + */ export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise): Promise { const before = await ethBalanceViaSub(api, user); --- a/tests/src/limits.test.ts +++ b/tests/src/limits.test.ts @@ -78,7 +78,7 @@ }); }); -describe('Sponsor timeout (NFT)', () => { +describe.skip('Sponsor timeout (NFT) (only for special chain limits test)', () => { let alice: IKeyringPair; let bob: IKeyringPair; let charlie: IKeyringPair; @@ -91,7 +91,7 @@ }); }); - it('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => { + it.skip('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => { const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorTransferTimeout: 7}); const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT'); @@ -141,7 +141,7 @@ }); }); -describe('Sponsor timeout (Fungible)', () => { +describe.skip('Sponsor timeout (Fungible) (only for special chain limits test)', () => { let alice: IKeyringPair; let bob: IKeyringPair; let charlie: IKeyringPair; @@ -208,7 +208,7 @@ }); }); -describe('Sponsor timeout (ReFungible)', () => { +describe.skip('Sponsor timeout (ReFungible) (only for special chain limits test)', () => { let alice: IKeyringPair; let bob: IKeyringPair; let charlie: IKeyringPair; --- a/tests/src/overflow.test.ts +++ b/tests/src/overflow.test.ts @@ -13,7 +13,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; -describe('Integration Test fungible overflows', () => { +describe.skip('Integration Test fungible overflows', () => { let alice: IKeyringPair; let bob: IKeyringPair; let charlie: IKeyringPair; --- a/tests/src/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -39,7 +39,6 @@ 'nonfungible', 'refungible', 'scheduler', - 'nftpayment', 'charging', ]; --- a/tests/src/setOffchainSchema.test.ts +++ b/tests/src/setOffchainSchema.test.ts @@ -40,7 +40,7 @@ await setOffchainSchemaExpectSuccess(alice, collectionId, DATA); const collection = await queryCollectionExpectSuccess(api, collectionId); - expect(collection.offchainSchema).to.be.equal('0x' + Buffer.from(DATA).toString('hex')); + expect('0x' + Buffer.from(collection.offchainSchema).toString('hex')).to.be.equal('0x' + Buffer.from(DATA).toString('hex')); }); }); @@ -51,7 +51,7 @@ await setOffchainSchemaExpectSuccess(bob, collectionId, DATA); const collection = await queryCollectionExpectSuccess(api, collectionId); - expect(collection.offchainSchema).to.be.equal('0x' + Buffer.from(DATA).toString('hex')); + expect('0x' + Buffer.from(collection.offchainSchema).toString('hex')).to.be.equal('0x' + Buffer.from(DATA).toString('hex')); }); }); }); --- a/tests/src/transfer.test.ts +++ b/tests/src/transfer.test.ts @@ -20,6 +20,10 @@ transferExpectSuccess, addCollectionAdminExpectSuccess, getCreatedCollectionCount, + toSubstrateAddress, + getTokenOwner, + normalizeAccountId, + getBalance as getTokenBalance, } from './util/helpers'; let alice: IKeyringPair; @@ -231,3 +235,61 @@ ); }); }); + +describe('Zero value transfer(From)', () => { + before(async () => { + await usingApi(async () => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + }); + }); + + it('NFT', async () => { + await usingApi(async (api: ApiPromise) => { + const nftCollectionId = await createCollectionExpectSuccess(); + const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); + + const transferTx = api.tx.nft.transfer(normalizeAccountId(bob), nftCollectionId, newNftTokenId, 0); + await submitTransactionAsync(alice, transferTx); + const address = normalizeAccountId(await getTokenOwner(api, nftCollectionId, newNftTokenId)); + + expect(toSubstrateAddress(address)).to.be.equal(alice.address); + }); + }); + + it('RFT', async () => { + await usingApi(async (api: ApiPromise) => { + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); + const balanceBeforeAlice = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(alice), newReFungibleTokenId); + const balanceBeforeBob = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(bob), newReFungibleTokenId); + + const transferTx = api.tx.nft.transfer(normalizeAccountId(bob), reFungibleCollectionId, newReFungibleTokenId, 0); + await submitTransactionAsync(alice, transferTx); + + const balanceAfterAlice = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(alice), newReFungibleTokenId); + const balanceAfterBob = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(bob), newReFungibleTokenId); + + expect((balanceBeforeAlice)).to.be.equal(balanceAfterAlice); + expect((balanceBeforeBob)).to.be.equal(balanceAfterBob); + }); + }); + + it('Fungible', async () => { + await usingApi(async (api: ApiPromise) => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); + const balanceBeforeAlice = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(alice), newFungibleTokenId); + const balanceBeforeBob = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(bob), newFungibleTokenId); + + const transferTx = api.tx.nft.transfer(normalizeAccountId(bob), fungibleCollectionId, newFungibleTokenId, 0); + await submitTransactionAsync(alice, transferTx); + + const balanceAfterAlice = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(alice), newFungibleTokenId); + const balanceAfterBob = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(bob), newFungibleTokenId); + + expect((balanceBeforeAlice)).to.be.equal(balanceAfterAlice); + expect((balanceBeforeBob)).to.be.equal(balanceAfterBob); + }); + }); +}); \ No newline at end of file --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -905,11 +905,12 @@ await usingApi(async (api: ApiPromise) => { const transferTx = api.tx.nft.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value); const events = await expect(submitTransactionExpectFailAsync(sender, transferTx)).to.be.rejected; - if (events && Array.isArray(events)) { - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - } + const result = getGenericResult(events); + // if (events && Array.isArray(events)) { + // const result = getCreateCollectionResult(events); + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + //} }); }