From ffcdfcb6e698aac3db9aaf2159d1c0eda7303516 Mon Sep 17 00:00:00 2001 From: Igor Kozyrev Date: Mon, 22 Nov 2021 15:06:44 +0000 Subject: [PATCH] Merge branch 'develop' into feature/core-214 --- --- 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/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,167 @@ 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}; -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<()> { + // 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, + }); - // 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, - }); - 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( + collection: &CollectionHandle, + item_id: &TokenId, + data: &[u8], +) -> Option<()> { + // 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; + } - sponsored - } - }; - } + let block_number = >::block_number() as T::BlockNumber; + let limit = collection.limits.sponsored_data_rate_limit()?; - if !sponsor_transfer { - None - } else { - collection.sponsorship.sponsor().cloned() + 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 +173,39 @@ 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::(&collection, item_id, data).map(|()| sponsor) + } _ => None, } } --- /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 @@ -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', @@ -362,7 +362,7 @@ default-features = false [dependencies.orml-vesting] -git = "https://github.com/open-web3-stack/open-runtime-module-library" +git = 'https://github.com/UniqueNetwork/open-runtime-module-library' version = "0.4.1-dev" default-features = false @@ -376,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" } @@ -384,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 @@ -736,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; @@ -777,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 { @@ -803,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 { @@ -870,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/confirmSponsorship.test.ts +++ b/tests/src/confirmSponsorship.test.ts @@ -251,23 +251,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); @@ -275,8 +272,6 @@ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice); const result2 = getGenericResult(events2); expect(result2.success).to.be.true; - - expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore); }); }); --- a/tests/src/eth/marketplace/marketplace.test.ts +++ b/tests/src/eth/marketplace/marketplace.test.ts @@ -2,7 +2,7 @@ import {getBalanceSingle, transferBalanceExpectSuccess} from '../../substrate/get-balance'; import privateKey from '../../substrate/privateKey'; import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, getTokenOwner, setCollectionSponsorExpectSuccess, transferExpectSuccess, transferFromExpectSuccess} from '../../util/helpers'; -import {collectionIdToAddress, contractHelpers, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, subToEth, subToEthLowercase} 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,35 +12,33 @@ 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 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}/MarketPlace.bin`)).toString(), arguments:[matcherOwner]}).send({from: matcherOwner}); - const helpers = contractHelpers(web3, matcherOwner); + + await transferBalanceToEth(api, alice, matcher.options.address); await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: matcherOwner}); - await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).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); - await helpers.methods.toggleAllowed(matcher.options.address, subToEth(alice.address), true).send({from: matcherOwner}); - await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(alice.address))); - - const seller = privateKey('//Bob'); + 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}); - 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)}); - - // Token is owned by seller initially expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)}); // Ask @@ -55,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}); @@ -137,37 +133,35 @@ }); 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}/MarketPlace.abi`)).toString()), undefined, { from: matcherOwner, ...GAS_ARGS, }); 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 helpers = contractHelpers(web3, matcherOwner); + + await transferBalanceToEth(api, alice, matcher.options.address); await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: matcherOwner}); - await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).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); - - await helpers.methods.toggleAllowed(matcher.options.address, subToEth(alice.address), true).send({from: matcherOwner}); - await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(alice.address))); - 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)}); - - // Token is owned by seller initially expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)}); // Ask --- 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/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -39,7 +39,6 @@ 'nonfungible', 'refungible', 'scheduler', - 'nftpayment', 'charging', ]; -- gitstuff