git.delta.rocks / unique-network / refs/commits / 0b44e9f57a8a

difftreelog

feat add collection sponsoring for rft

Grigoriy Simonov2022-12-23parent: #aea0773.patch.diff
in: master

4 files changed

modifiedruntime/common/config/pallets/app_promotion.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/app_promotion.rs
+++ b/runtime/common/config/pallets/app_promotion.rs
@@ -22,7 +22,7 @@
 use frame_support::{parameter_types, PalletId};
 use sp_arithmetic::Perbill;
 use up_common::{
-	constants::{UNIQUE, RELAY_DAYS, DAYS},
+	constants::{UNIQUE, RELAY_DAYS},
 	types::Balance,
 };
 
modifiedruntime/common/ethereum/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/sponsoring.rs
+++ b/runtime/common/ethereum/sponsoring.rs
@@ -32,14 +32,22 @@
 	Config as FungibleConfig,
 	erc::{UniqueFungibleCall, ERC20Call},
 };
-use pallet_refungible::Config as RefungibleConfig;
+use pallet_refungible::{
+	Config as RefungibleConfig,
+	erc::UniqueRefungibleCall,
+	erc_token::{RefungibleTokenHandle, UniqueRefungibleTokenCall},
+	RefungibleHandle,
+};
 use pallet_unique::Config as UniqueConfig;
 use sp_std::prelude::*;
-use up_data_structs::{CollectionMode, CreateItemData, CreateNftData, TokenId};
+use up_data_structs::{
+	CollectionMode, CreateItemData, CreateNftData, mapping::TokenAddressMapping, TokenId,
+};
 use up_sponsorship::SponsorshipHandler;
-
 use crate::{Runtime, runtime_common::sponsoring::*};
 
+mod refungible;
+
 pub type EvmSponsorshipHandler = (
 	UniqueEthSponsorshipHandler<Runtime>,
 	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
@@ -53,80 +61,155 @@
 		who: &T::CrossAccountId,
 		call_context: &CallContext,
 	) -> Option<T::CrossAccountId> {
-		let collection_id = map_eth_to_id(&call_context.contract_address)?;
-		let collection = <CollectionHandle<T>>::new(collection_id)?;
-		let sponsor = collection.sponsorship.sponsor()?.clone();
-		let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?;
-		Some(T::CrossAccountId::from_sub(match &collection.mode {
-			CollectionMode::NFT => {
-				let call = <UniqueNFTCall<T>>::parse(method_id, &mut reader).ok()??;
-				match call {
-					UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty {
-						token_id,
-						key,
-						value,
-						..
-					}) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						withdraw_set_token_property::<T>(
+		if let Some(collection_id) = map_eth_to_id(&call_context.contract_address) {
+			let collection = <CollectionHandle<T>>::new(collection_id)?;
+			let sponsor = collection.sponsorship.sponsor()?.clone();
+			let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?;
+			Some(T::CrossAccountId::from_sub(match &collection.mode {
+				CollectionMode::NFT => {
+					let call = <UniqueNFTCall<T>>::parse(method_id, &mut reader).ok()??;
+					match call {
+						UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty {
+							token_id,
+							key,
+							value,
+							..
+						}) => {
+							let token_id: TokenId = token_id.try_into().ok()?;
+							withdraw_set_token_property::<T>(
+								&collection,
+								&who,
+								&token_id,
+								key.len() + value.len(),
+							)
+							.map(|()| sponsor)
+						}
+						UniqueNFTCall::ERC721UniqueExtensions(
+							ERC721UniqueExtensionsCall::Transfer { token_id, .. },
+						) => {
+							let token_id: TokenId = token_id.try_into().ok()?;
+							withdraw_transfer::<T>(&collection, &who, &token_id).map(|()| sponsor)
+						}
+						UniqueNFTCall::ERC721UniqueMintable(
+							ERC721UniqueMintableCall::Mint { .. }
+							| ERC721UniqueMintableCall::MintCheckId { .. }
+							| ERC721UniqueMintableCall::MintWithTokenUri { .. }
+							| ERC721UniqueMintableCall::MintWithTokenUriCheckId { .. },
+						) => withdraw_create_item::<T>(
 							&collection,
 							&who,
-							&token_id,
-							key.len() + value.len(),
+							&CreateItemData::NFT(CreateNftData::default()),
 						)
-						.map(|()| sponsor)
-					}
-					UniqueNFTCall::ERC721UniqueExtensions(
-						ERC721UniqueExtensionsCall::Transfer { token_id, .. },
-					) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						withdraw_transfer::<T>(&collection, &who, &token_id).map(|()| sponsor)
-					}
-					UniqueNFTCall::ERC721UniqueMintable(
-						ERC721UniqueMintableCall::Mint { .. }
-						| ERC721UniqueMintableCall::MintCheckId { .. }
-						| ERC721UniqueMintableCall::MintWithTokenUri { .. }
-						| ERC721UniqueMintableCall::MintWithTokenUriCheckId { .. },
-					) => withdraw_create_item::<T>(
-						&collection,
-						&who,
-						&CreateItemData::NFT(CreateNftData::default()),
-					)
-					.map(|()| sponsor),
-					UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						let from = T::CrossAccountId::from_eth(from);
-						withdraw_transfer::<T>(&collection, &from, &token_id).map(|()| sponsor)
-					}
-					UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
-							.map(|()| sponsor)
+						.map(|()| sponsor),
+						UniqueNFTCall::ERC721(ERC721Call::TransferFrom {
+							token_id, from, ..
+						}) => {
+							let token_id: TokenId = token_id.try_into().ok()?;
+							let from = T::CrossAccountId::from_eth(from);
+							withdraw_transfer::<T>(&collection, &from, &token_id).map(|()| sponsor)
+						}
+						UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
+							let token_id: TokenId = token_id.try_into().ok()?;
+							withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
+								.map(|()| sponsor)
+						}
+						_ => None,
 					}
-					_ => None,
 				}
-			}
-			CollectionMode::Fungible(_) => {
-				let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
-				#[allow(clippy::single_match)]
-				match call {
-					UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
-						withdraw_transfer::<T>(&collection, who, &TokenId::default())
-							.map(|()| sponsor)
-					}
-					UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
-						let from = T::CrossAccountId::from_eth(from);
-						withdraw_transfer::<T>(&collection, &from, &TokenId::default())
-							.map(|()| sponsor)
-					}
-					UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
-						withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
-							.map(|()| sponsor)
+				CollectionMode::ReFungible => {
+					let call = <UniqueRefungibleCall<T>>::parse(method_id, &mut reader).ok()??;
+					refungible::call_sponsor(call, collection, who).map(|()| sponsor)
+				}
+				CollectionMode::Fungible(_) => {
+					let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
+					match call {
+						UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
+							withdraw_transfer::<T>(&collection, who, &TokenId::default())
+								.map(|()| sponsor)
+						}
+						UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
+							let from = T::CrossAccountId::from_eth(from);
+							withdraw_transfer::<T>(&collection, &from, &TokenId::default())
+								.map(|()| sponsor)
+						}
+						UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
+							withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
+								.map(|()| sponsor)
+						}
+						_ => None,
 					}
-					_ => None,
 				}
-			}
-			_ => None,
-		}?))
+			}?))
+		} else {
+			let (collection_id, token_id) =
+				T::EvmTokenAddressMapping::address_to_token(&call_context.contract_address)?;
+			let collection = <CollectionHandle<T>>::new(collection_id)?;
+			let sponsor = collection.sponsorship.sponsor()?.clone();
+			let rft_collection = RefungibleHandle::cast(collection);
+			let token = RefungibleTokenHandle(rft_collection, token_id);
+			let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?;
+			let call = <UniqueRefungibleTokenCall<T>>::parse(method_id, &mut reader).ok()??;
+			Some(T::CrossAccountId::from_sub(
+				refungible::token_call_sponsor(call, token, who).map(|()| sponsor)?,
+			))
+		}
+	}
+}
+
+mod common {
+	use super::*;
+
+	use pallet_common::erc::{CollectionCall};
+
+	pub fn collection_call_sponsor<T>(
+		call: CollectionCall<T>,
+		_collection: CollectionHandle<T>,
+		_who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use CollectionCall::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _)
+			| CollectionProperty { .. }
+			| CollectionProperties { .. }
+			| HasCollectionPendingSponsor
+			| CollectionSponsor
+			| ContractAddress
+			| AllowlistedCross { .. }
+			| IsOwnerOrAdminEth { .. }
+			| IsOwnerOrAdminCross { .. }
+			| CollectionOwner
+			| CollectionAdmins
+			| UniqueCollectionType => None,
+
+			// Not sponsored
+			AddToCollectionAllowList { .. }
+			| AddToCollectionAllowListCross { .. }
+			| RemoveFromCollectionAllowList { .. }
+			| RemoveFromCollectionAllowListCross { .. }
+			| AddCollectionAdminCross { .. }
+			| RemoveCollectionAdminCross { .. }
+			| AddCollectionAdmin { .. }
+			| RemoveCollectionAdmin { .. }
+			| SetNestingBool { .. }
+			| SetNesting { .. }
+			| SetCollectionAccess { .. }
+			| SetCollectionMintMode { .. }
+			| SetOwner { .. }
+			| ChangeCollectionOwnerCross { .. }
+			| SetCollectionProperty { .. }
+			| SetCollectionProperties { .. }
+			| DeleteCollectionProperty { .. }
+			| DeleteCollectionProperties { .. }
+			| SetCollectionSponsor { .. }
+			| SetCollectionSponsorCross { .. }
+			| ConfirmCollectionSponsorship
+			| RemoveCollectionSponsor
+			| SetIntLimit { .. } => None,
+		}
 	}
 }
addedruntime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -0,0 +1,359 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! Implements EVM sponsoring logic via TransactionValidityHack
+
+use core::convert::TryInto;
+use pallet_common::CollectionHandle;
+use pallet_evm::account::CrossAccountId;
+use pallet_fungible::Config as FungibleConfig;
+use pallet_refungible::Config as RefungibleConfig;
+use pallet_nonfungible::Config as NonfungibleConfig;
+use pallet_unique::Config as UniqueConfig;
+use up_data_structs::{CreateItemData, CreateNftData, TokenId};
+
+use super::common;
+use crate::runtime_common::sponsoring::*;
+
+use pallet_refungible::{
+	erc::{
+		ERC721BurnableCall, ERC721Call, ERC721EnumerableCall, ERC721MetadataCall,
+		ERC721UniqueExtensionsCall, ERC721UniqueMintableCall, TokenPropertiesCall,
+		UniqueRefungibleCall,
+	},
+	erc_token::{
+		ERC1633Call, ERC20Call, ERC20UniqueExtensionsCall, RefungibleTokenHandle,
+		UniqueRefungibleTokenCall,
+	},
+};
+
+pub fn call_sponsor<T>(
+	call: UniqueRefungibleCall<T>,
+	collection: CollectionHandle<T>,
+	who: &T::CrossAccountId,
+) -> Option<()>
+where
+	T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+{
+	use UniqueRefungibleCall::*;
+
+	match call {
+		// Readonly
+		ERC165Call(_, _) => None,
+
+		ERC721Enumerable(call) => erc721::enumerable_call_sponsor(call, collection, who),
+		ERC721Burnable(call) => erc721::burnable_call_sponsor(call, collection, who),
+		ERC721Metadata(call) => erc721::metadata_call_sponsor(call, collection, who),
+		Collection(call) => common::collection_call_sponsor(call, collection, who),
+		ERC721(call) => erc721::call_sponsor(call, collection, who),
+		ERC721UniqueExtensions(call) => {
+			erc721::unique_extensions_call_sponsor(call, collection, who)
+		}
+		ERC721UniqueMintable(call) => erc721::unique_mintable_call_sponsor(call, collection, who),
+		TokenProperties(call) => token_properties_call_sponsor(call, collection, who),
+	}
+}
+
+pub fn token_properties_call_sponsor<T>(
+	call: TokenPropertiesCall<T>,
+	collection: CollectionHandle<T>,
+	who: &T::CrossAccountId,
+) -> Option<()>
+where
+	T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+{
+	use TokenPropertiesCall::*;
+
+	match call {
+		// Readonly
+		ERC165Call(_, _) | Property { .. } => None,
+
+		// Not sponsored
+		SetTokenPropertyPermission { .. }
+		| SetProperties { .. }
+		| DeleteProperty { .. }
+		| DeleteProperties { .. } => None,
+
+		SetProperty {
+			token_id,
+			key,
+			value,
+			..
+		} => {
+			let token_id: TokenId = token_id.try_into().ok()?;
+			withdraw_set_token_property::<T>(&collection, &who, &token_id, key.len() + value.len())
+		}
+	}
+}
+
+pub fn token_call_sponsor<T>(
+	call: UniqueRefungibleTokenCall<T>,
+	token: RefungibleTokenHandle<T>,
+	who: &T::CrossAccountId,
+) -> Option<()>
+where
+	T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+{
+	use UniqueRefungibleTokenCall::*;
+
+	match call {
+		// Readonly
+		ERC165Call(_, _) => None,
+
+		ERC20(call) => erc20::call_sponsor(call, token, who),
+		ERC20UniqueExtensions(call) => erc20::unique_extensions_call_sponsor(call, token, who),
+		ERC1633(call) => erc1633::call_sponsor(call, token, who),
+	}
+}
+
+mod erc721 {
+	use super::*;
+
+	pub fn call_sponsor<T>(
+		call: ERC721Call<T>,
+		collection: CollectionHandle<T>,
+		who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC721Call::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _)
+			| BalanceOf { .. }
+			| OwnerOf { .. }
+			| GetApproved { .. }
+			| IsApprovedForAll { .. }
+			| CollectionHelperAddress => None,
+
+			// Not sponsored
+			SafeTransferFromWithData { .. }
+			| SafeTransferFrom { .. }
+			| SetApprovalForAll { .. } => None,
+
+			TransferFrom { token_id, from, .. } => {
+				let token_id: TokenId = token_id.try_into().ok()?;
+				let from = T::CrossAccountId::from_eth(from);
+				withdraw_transfer::<T>(&collection, &from, &token_id)
+			}
+			Approve { _token_id, .. } => {
+				let token_id: TokenId = _token_id.try_into().ok()?;
+				withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
+			}
+		}
+	}
+
+	pub fn enumerable_call_sponsor<T>(
+		call: ERC721EnumerableCall<T>,
+		_collection: CollectionHandle<T>,
+		_who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC721EnumerableCall::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _) | TokenByIndex { .. } | TokenOfOwnerByIndex { .. } | TotalSupply => {
+				None
+			}
+		}
+	}
+
+	pub fn burnable_call_sponsor<T>(
+		call: ERC721BurnableCall<T>,
+		_collection: CollectionHandle<T>,
+		_who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC721BurnableCall::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _) => None,
+
+			// Not sponsored
+			Burn { .. } => None,
+		}
+	}
+
+	pub fn metadata_call_sponsor<T>(
+		call: ERC721MetadataCall<T>,
+		_collection: CollectionHandle<T>,
+		_who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC721MetadataCall::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _) | NameProxy | SymbolProxy | TokenUri { .. } => None,
+		}
+	}
+
+	pub fn unique_extensions_call_sponsor<T>(
+		call: ERC721UniqueExtensionsCall<T>,
+		collection: CollectionHandle<T>,
+		who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC721UniqueExtensionsCall::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _)
+			| Name
+			| Symbol
+			| Description
+			| CrossOwnerOf { .. }
+			| Properties { .. }
+			| NextTokenId
+			| TokenContractAddress { .. } => None,
+
+			// Not sponsored
+			TransferCross { .. }
+			| TransferFromCross { .. }
+			| BurnFrom { .. }
+			| BurnFromCross { .. }
+			| MintBulk { .. }
+			| MintBulkWithTokenUri { .. } => None,
+
+			Transfer { token_id, .. } => {
+				let token_id: TokenId = token_id.try_into().ok()?;
+				withdraw_transfer::<T>(&collection, &who, &token_id)
+			}
+		}
+	}
+
+	pub fn unique_mintable_call_sponsor<T>(
+		call: ERC721UniqueMintableCall<T>,
+		collection: CollectionHandle<T>,
+		who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC721UniqueMintableCall::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _) | MintingFinished => None,
+
+			// Not sponsored
+			FinishMinting => None,
+
+			Mint { .. }
+			| MintCheckId { .. }
+			| MintWithTokenUri { .. }
+			| MintWithTokenUriCheckId { .. } => withdraw_create_item::<T>(
+				&collection,
+				&who,
+				&CreateItemData::NFT(CreateNftData::default()),
+			),
+		}
+	}
+}
+
+mod erc20 {
+	use super::*;
+
+	pub fn call_sponsor<T>(
+		call: ERC20Call<T>,
+		token: RefungibleTokenHandle<T>,
+		who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC20Call::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _)
+			| Name
+			| Symbol
+			| TotalSupply
+			| Decimals
+			| BalanceOf { .. }
+			| Allowance { .. } => None,
+
+			Transfer { .. } => {
+				let RefungibleTokenHandle(handle, token_id) = token;
+				let token_id = token_id.try_into().ok()?;
+				withdraw_transfer::<T>(&handle, &who, &token_id)
+			}
+			TransferFrom { from, .. } => {
+				let RefungibleTokenHandle(handle, token_id) = token;
+				let token_id = token_id.try_into().ok()?;
+				let from = T::CrossAccountId::from_eth(from);
+				withdraw_transfer::<T>(&handle, &from, &token_id)
+			}
+			Approve { .. } => {
+				let RefungibleTokenHandle(handle, token_id) = token;
+				let token_id = token_id.try_into().ok()?;
+				withdraw_approve::<T>(&handle, who.as_sub(), &token_id)
+			}
+		}
+	}
+
+	pub fn unique_extensions_call_sponsor<T>(
+		call: ERC20UniqueExtensionsCall<T>,
+		_token: RefungibleTokenHandle<T>,
+		_who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC20UniqueExtensionsCall::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _) => None,
+
+			// Not sponsored
+			BurnFrom { .. } | Repartition { .. } => None,
+		}
+	}
+}
+
+mod erc1633 {
+	use super::*;
+
+	pub fn call_sponsor<T>(
+		call: ERC1633Call<T>,
+		_token: RefungibleTokenHandle<T>,
+		_who: &T::CrossAccountId,
+	) -> Option<()>
+	where
+		T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+	{
+		use ERC1633Call::*;
+
+		match call {
+			// Readonly
+			ERC165Call(_, _) | ParentToken | ParentTokenId => None,
+		}
+	}
+}
modifiedtests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
18import {usingPlaygrounds} from '../util/index';18import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index';
19import {itEth, expect} from './util';19import {itEth, expect} from './util';
2020
21describe('evm collection sponsoring', () => {21describe('evm nft collection sponsoring', () => {
22 let donor: IKeyringPair;22 let donor: IKeyringPair;
23 let alice: IKeyringPair;23 let alice: IKeyringPair;
24 let nominal: bigint;24 let nominal: bigint;
318 });318 });
319});319});
320
321describe('evm RFT collection sponsoring', () => {
322 let donor: IKeyringPair;
323 let alice: IKeyringPair;
324 let nominal: bigint;
325
326 before(async function() {
327 await usingPlaygrounds(async (helper, privateKey) => {
328 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
329 donor = await privateKey({filename: __filename});
330 [alice] = await helper.arrange.createAccounts([100n], donor);
331 nominal = helper.balance.getOneTokenNominal();
332 });
333 });
334
335 itEth('sponsors mint transactions', async ({helper}) => {
336 const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'spnr', permissions: {mintMode: true}});
337 await collection.setSponsor(alice, alice.address);
338 await collection.confirmSponsorship(alice);
339
340 const minter = helper.eth.createAccount();
341 expect(await helper.balance.getEthereum(minter)).to.equal(0n);
342
343 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
344 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', minter);
345
346 await collection.addToAllowList(alice, {Ethereum: minter});
347
348 const result = await contract.methods.mint(minter).send();
349
350 const events = helper.eth.normalizeEvents(result.events);
351 expect(events).to.deep.include({
352 address: collectionAddress,
353 event: 'Transfer',
354 args: {
355 from: '0x0000000000000000000000000000000000000000',
356 to: minter,
357 tokenId: '1',
358 },
359 });
360 });
361
362 // TODO: Temprorary off. Need refactor
363 // itWeb3('Set substrate sponsor', async ({api, web3, privateKeyWrapper}) => {
364 // const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
365 // const collectionHelpers = evmCollectionHelpers(web3, owner);
366 // let result = await collectionHelpers.methods.createRFTCollection('Sponsor collection', '1', '1').send();
367 // const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
368 // const sponsor = privateKeyWrapper('//Alice');
369 // const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
370
371 // expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;
372 // result = await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner});
373 // expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;
374
375 // const confirmTx = await api.tx.unique.confirmSponsorship(collectionId);
376 // await submitTransactionAsync(sponsor, confirmTx);
377 // expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;
378
379 // const sponsorTuple = await collectionEvm.methods.collectionSponsor().call({from: owner});
380 // expect(bigIntToSub(api, BigInt(sponsorTuple[1]))).to.be.eq(sponsor.address);
381 // });
382
383 // Soft-deprecated
384 itEth('[eth] Remove sponsor', async ({helper}) => {
385 const owner = await helper.eth.createAccountWithBalance(donor);
386 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
387
388 let result = await collectionHelpers.methods.createRFTCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
389 const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
390 const sponsor = await helper.eth.createAccountWithBalance(donor);
391 const collectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'rft', owner, true);
392
393 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;
394 result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
395 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;
396
397 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
398 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;
399
400 await collectionEvm.methods.removeCollectionSponsor().send({from: owner});
401
402 const sponsorTuple = await collectionEvm.methods.collectionSponsor().call({from: owner});
403 expect(sponsorTuple.field_0).to.be.eq('0x0000000000000000000000000000000000000000');
404 });
405
406 itEth('[cross] Remove sponsor', async ({helper}) => {
407 const owner = await helper.eth.createAccountWithBalance(donor);
408 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
409
410 let result = await collectionHelpers.methods.createRFTCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
411 const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
412 const sponsor = await helper.eth.createAccountWithBalance(donor);
413 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
414 const collectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'rft', owner);
415
416 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;
417 result = await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send({from: owner});
418 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;
419
420 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
421 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;
422
423 await collectionEvm.methods.removeCollectionSponsor().send({from: owner});
424
425 const sponsorTuple = await collectionEvm.methods.collectionSponsor().call({from: owner});
426 expect(sponsorTuple.field_0).to.be.eq('0x0000000000000000000000000000000000000000');
427 });
428
429 // Soft-deprecated
430 itEth('[eth] Sponsoring collection from evm address via access list', async ({helper}) => {
431 const owner = await helper.eth.createAccountWithBalance(donor);
432
433 const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Sponsor collection', '1', '1', '');
434
435 const collection = helper.rft.getCollectionObject(collectionId);
436 const sponsor = await helper.eth.createAccountWithBalance(donor);
437 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner, true);
438
439 await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
440 let collectionData = (await collection.getData())!;
441 expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
442 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
443
444 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
445 collectionData = (await collection.getData())!;
446 expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
447
448 const user = helper.eth.createAccount();
449 const nextTokenId = await collectionEvm.methods.nextTokenId().call();
450 expect(nextTokenId).to.be.equal('1');
451
452 const oldPermissions = (await collection.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
453 expect(oldPermissions.mintMode).to.be.false;
454 expect(oldPermissions.access).to.be.equal('Normal');
455
456 await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
457 await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
458 await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
459
460 const newPermissions = (await collection.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
461 expect(newPermissions.mintMode).to.be.true;
462 expect(newPermissions.access).to.be.equal('AllowList');
463
464 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
465 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
466
467 {
468 const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});
469 const events = helper.eth.normalizeEvents(result.events);
470
471 expect(events).to.deep.include({
472 address: collectionAddress,
473 event: 'Transfer',
474 args: {
475 from: '0x0000000000000000000000000000000000000000',
476 to: user,
477 tokenId: '1',
478 },
479 });
480
481 const ownerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(owner));
482 const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor));
483
484 expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
485 expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
486 expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
487 }
488 });
489
490 itEth('[cross] Sponsoring collection from evm address via access list', async ({helper}) => {
491 const owner = await helper.eth.createAccountWithBalance(donor);
492
493 const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Sponsor collection', '1', '1', '');
494
495 const collection = helper.rft.getCollectionObject(collectionId);
496 const sponsor = await helper.eth.createAccountWithBalance(donor);
497 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
498 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
499
500 await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send({from: owner});
501 let collectionData = (await collection.getData())!;
502 expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
503 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
504
505 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
506 collectionData = (await collection.getData())!;
507 expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
508
509 const user = helper.eth.createAccount();
510 const userCross = helper.ethCrossAccount.fromAddress(user);
511 const nextTokenId = await collectionEvm.methods.nextTokenId().call();
512 expect(nextTokenId).to.be.equal('1');
513
514 const oldPermissions = (await collection.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
515 expect(oldPermissions.mintMode).to.be.false;
516 expect(oldPermissions.access).to.be.equal('Normal');
517
518 await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
519 await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
520 await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
521
522 const newPermissions = (await collection.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
523 expect(newPermissions.mintMode).to.be.true;
524 expect(newPermissions.access).to.be.equal('AllowList');
525
526 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
527 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
528
529 {
530 const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});
531 const events = helper.eth.normalizeEvents(result.events);
532
533 expect(events).to.deep.include({
534 address: collectionAddress,
535 event: 'Transfer',
536 args: {
537 from: '0x0000000000000000000000000000000000000000',
538 to: user,
539 tokenId: '1',
540 },
541 });
542
543 const ownerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(owner));
544 const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor));
545
546 expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
547 expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
548 expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
549 }
550 });
551
552 // TODO: Temprorary off. Need refactor
553 // itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => {
554 // const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
555 // const collectionHelpers = evmCollectionHelpers(web3, owner);
556 // const result = await collectionHelpers.methods.createERC721MetadataCompatibleRFTCollection('Sponsor collection', '1', '1', '').send();
557 // const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
558 // const sponsor = privateKeyWrapper('//Alice');
559 // const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
560
561 // await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner});
562
563 // const confirmTx = await api.tx.unique.confirmSponsorship(collectionId);
564 // await submitTransactionAsync(sponsor, confirmTx);
565
566 // const user = createEthAccount(web3);
567 // const nextTokenId = await collectionEvm.methods.nextTokenId().call();
568 // expect(nextTokenId).to.be.equal('1');
569
570 // await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
571 // await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
572 // await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
573
574 // const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
575 // const sponsorBalanceBefore = (await getBalance(api, [sponsor.address]))[0];
576
577 // {
578 // const nextTokenId = await collectionEvm.methods.nextTokenId().call();
579 // expect(nextTokenId).to.be.equal('1');
580 // const result = await collectionEvm.methods.mintWithTokenURI(
581 // user,
582 // nextTokenId,
583 // 'Test URI',
584 // ).send({from: user});
585 // const events = normalizeEvents(result.events);
586
587 // expect(events).to.be.deep.equal([
588 // {
589 // address: collectionIdAddress,
590 // event: 'Transfer',
591 // args: {
592 // from: '0x0000000000000000000000000000000000000000',
593 // to: user,
594 // tokenId: nextTokenId,
595 // },
596 // },
597 // ]);
598
599 // const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
600 // const sponsorBalanceAfter = (await getBalance(api, [sponsor.address]))[0];
601
602 // expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
603 // expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
604 // expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
605 // }
606 // });
607
608 // Soft-deprecated
609 itEth('[eth] Check that transaction via EVM spend money from sponsor address', async ({helper}) => {
610 const owner = await helper.eth.createAccountWithBalance(donor);
611
612 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', '');
613 const collection = helper.rft.getCollectionObject(collectionId);
614 const sponsor = await helper.eth.createAccountWithBalance(donor);
615 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner, true);
616
617 await collectionEvm.methods.setCollectionSponsor(sponsor).send();
618 let collectionData = (await collection.getData())!;
619 expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
620 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
621
622 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true);
623 await sponsorCollection.methods.confirmCollectionSponsorship().send();
624 collectionData = (await collection.getData())!;
625 expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
626
627 const user = helper.eth.createAccount();
628 await collectionEvm.methods.addCollectionAdmin(user).send();
629
630 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
631 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
632
633 const userCollectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', user, true);
634
635 const result = await userCollectionEvm.methods.mintWithTokenURI(user, 'Test URI').send();
636 const tokenId = result.events.Transfer.returnValues.tokenId;
637
638 const events = helper.eth.normalizeEvents(result.events);
639 const address = helper.ethAddress.fromCollectionId(collectionId);
640
641 expect(events).to.deep.include({
642 address,
643 event: 'Transfer',
644 args: {
645 from: '0x0000000000000000000000000000000000000000',
646 to: user,
647 tokenId: '1',
648 },
649 });
650 expect(await userCollectionEvm.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
651
652 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
653 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
654 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
655 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
656 });
657
658 itEth('[cross] Check that transaction via EVM spend money from sponsor address', async ({helper}) => {
659 const owner = await helper.eth.createAccountWithBalance(donor);
660
661 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', '');
662 const collection = helper.rft.getCollectionObject(collectionId);
663 const sponsor = await helper.eth.createAccountWithBalance(donor);
664 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
665 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
666
667 await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send();
668 let collectionData = (await collection.getData())!;
669 expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
670 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
671
672 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
673 await sponsorCollection.methods.confirmCollectionSponsorship().send();
674 collectionData = (await collection.getData())!;
675 expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));
676
677 const user = helper.eth.createAccount();
678 const userCross = helper.ethCrossAccount.fromAddress(user);
679 await collectionEvm.methods.addCollectionAdminCross(userCross).send();
680
681 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
682 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
683
684 const userCollectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', user);
685
686 const result = await userCollectionEvm.methods.mintWithTokenURI(user, 'Test URI').send();
687 const tokenId = result.events.Transfer.returnValues.tokenId;
688
689 const events = helper.eth.normalizeEvents(result.events);
690 const address = helper.ethAddress.fromCollectionId(collectionId);
691
692 expect(events).to.deep.include({
693 address,
694 event: 'Transfer',
695 args: {
696 from: '0x0000000000000000000000000000000000000000',
697 to: user,
698 tokenId: '1',
699 },
700 });
701 expect(await userCollectionEvm.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
702
703 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
704 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
705 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
706 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
707 });
708});
709
710describe('evm RFT token sponsoring', () => {
711 let donor: IKeyringPair;
712
713 before(async function() {
714 await usingPlaygrounds(async (helper, privateKey) => {
715 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
716 donor = await privateKey({filename: __filename});
717 });
718 });
719
720 itEth('[cross] Check that transfer via EVM spend money from sponsor address', async ({helper}) => {
721 const owner = await helper.eth.createAccountWithBalance(donor);
722
723 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', '');
724 const sponsor = await helper.eth.createAccountWithBalance(donor);
725 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
726 const receiver = await helper.eth.createAccountWithBalance(donor);
727 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
728
729 await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send();
730
731 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
732 await sponsorCollection.methods.confirmCollectionSponsorship().send();
733
734 const user = await helper.eth.createAccountWithBalance(donor);
735 const userCross = helper.ethCrossAccount.fromAddress(user);
736 await collectionEvm.methods.addCollectionAdminCross(userCross).send();
737
738 const userCollectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', user);
739
740 const result = await userCollectionEvm.methods.mintWithTokenURI(user, 'Test URI').send();
741 const tokenId = result.events.Transfer.returnValues.tokenId;
742
743 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, user);
744 await tokenContract.methods.repartition(2).send();
745
746 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
747 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
748 const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
749
750 await tokenContract.methods.transfer(receiver, 1).send();
751
752 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
753 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
754 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
755 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
756 const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
757 expect(userBalanceAfter).to.be.eq(userBalanceBefore);
758 });
759
760 itEth('[cross] Check that approve via EVM spend money from sponsor address', async ({helper}) => {
761 const owner = await helper.eth.createAccountWithBalance(donor);
762
763 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', '');
764 const sponsor = await helper.eth.createAccountWithBalance(donor);
765 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
766 const receiver = await helper.eth.createAccountWithBalance(donor);
767 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
768
769 await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send();
770
771 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
772 await sponsorCollection.methods.confirmCollectionSponsorship().send();
773
774 const user = await helper.eth.createAccountWithBalance(donor);
775 const userCross = helper.ethCrossAccount.fromAddress(user);
776 await collectionEvm.methods.addCollectionAdminCross(userCross).send();
777
778 const userCollectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', user);
779
780 const result = await userCollectionEvm.methods.mintWithTokenURI(user, 'Test URI').send();
781 const tokenId = result.events.Transfer.returnValues.tokenId;
782
783 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, user);
784 await tokenContract.methods.repartition(2).send();
785
786 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
787 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
788 const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
789
790 await tokenContract.methods.approve(receiver, 1).send();
791
792 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
793 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
794 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
795 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
796 const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
797 expect(userBalanceAfter).to.be.eq(userBalanceBefore);
798 });
799
800
801 itEth('[cross] Check that transferFrom via EVM spend money from sponsor address', async ({helper}) => {
802 const owner = await helper.eth.createAccountWithBalance(donor);
803
804 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', '');
805 const sponsor = await helper.eth.createAccountWithBalance(donor);
806 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
807 const receiver = await helper.eth.createAccountWithBalance(donor);
808 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
809
810 await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send();
811
812 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
813 await sponsorCollection.methods.confirmCollectionSponsorship().send();
814
815 const user = await helper.eth.createAccountWithBalance(donor);
816 const userCross = helper.ethCrossAccount.fromAddress(user);
817 await collectionEvm.methods.addCollectionAdminCross(userCross).send();
818
819 const userCollectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', user);
820
821 const result = await userCollectionEvm.methods.mintWithTokenURI(user, 'Test URI').send();
822 const tokenId = result.events.Transfer.returnValues.tokenId;
823
824 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, user);
825 await tokenContract.methods.repartition(2).send();
826 await tokenContract.methods.approve(receiver, 1).send();
827
828 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
829 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
830 const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
831 const receiverBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(receiver));
832
833 const receiverTokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, receiver);
834 await receiverTokenContract.methods.transferFrom(user, receiver, 1).send();
835
836 const receiverBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(receiver));
837 expect(receiverBalanceAfter).to.be.eq(receiverBalanceBefore);
838 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
839 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
840 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));
841 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
842 const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
843 expect(userBalanceAfter).to.be.eq(userBalanceBefore);
844 });
845});
320846