From 35937ed5888443ecdf1335ea09cbcb2c94eab5d7 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 18 Nov 2021 14:20:09 +0000 Subject: [PATCH] refactor: share unq sponsoring code with evm --- --- 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,13 +136,13 @@ //#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 @@ -253,7 +253,7 @@ >::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); --- 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, } } --- a/primitives/evm-mapping/src/lib.rs +++ b/primitives/evm-mapping/src/lib.rs @@ -4,9 +4,9 @@ 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 +/// 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; -- gitstuff