difftreelog
Merge pull request #469 from UniqueNetwork/feature/refactor_contract_sponsoring
in: master
Feature/refactor contract sponsoring
19 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2345,7 +2345,7 @@
[[package]]
name = "fc-consensus"
version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"async-trait",
"fc-db",
@@ -2364,7 +2364,7 @@
[[package]]
name = "fc-db"
version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"fp-storage",
"kvdb-rocksdb",
@@ -2380,7 +2380,7 @@
[[package]]
name = "fc-mapping-sync"
version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"fc-db",
"fp-consensus",
@@ -2397,7 +2397,7 @@
[[package]]
name = "fc-rpc"
version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"ethereum",
"ethereum-types",
@@ -2437,7 +2437,7 @@
[[package]]
name = "fc-rpc-core"
version = "1.1.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"ethereum",
"ethereum-types",
@@ -2578,7 +2578,7 @@
[[package]]
name = "fp-consensus"
version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"ethereum",
"parity-scale-codec 3.1.5",
@@ -2590,7 +2590,7 @@
[[package]]
name = "fp-evm"
version = "3.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"evm",
"frame-support",
@@ -2604,7 +2604,7 @@
[[package]]
name = "fp-evm-mapping"
version = "0.1.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"frame-support",
"sp-core",
@@ -2613,7 +2613,7 @@
[[package]]
name = "fp-rpc"
version = "3.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"ethereum",
"ethereum-types",
@@ -2630,7 +2630,7 @@
[[package]]
name = "fp-self-contained"
version = "1.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"ethereum",
"frame-support",
@@ -2646,7 +2646,7 @@
[[package]]
name = "fp-storage"
version = "2.0.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"parity-scale-codec 3.1.5",
]
@@ -5445,7 +5445,7 @@
[[package]]
name = "pallet-base-fee"
version = "1.0.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"fp-evm",
"frame-support",
@@ -5552,7 +5552,7 @@
[[package]]
name = "pallet-common"
-version = "0.1.6"
+version = "0.1.7"
dependencies = [
"ethereum",
"evm-coder",
@@ -5660,7 +5660,7 @@
[[package]]
name = "pallet-ethereum"
version = "4.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"ethereum",
"ethereum-types",
@@ -5689,7 +5689,7 @@
[[package]]
name = "pallet-evm"
version = "6.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
dependencies = [
"evm",
"fp-evm",
@@ -5733,7 +5733,7 @@
[[package]]
name = "pallet-evm-contract-helpers"
-version = "0.1.2"
+version = "0.2.0"
dependencies = [
"evm-coder",
"fp-evm-mapping",
@@ -12130,7 +12130,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if 0.1.10",
"digest 0.10.3",
"rand 0.8.5",
"static_assertions",
crates/evm-coder/CHANGELOG.mddiffbeforeafterboth--- a/crates/evm-coder/CHANGELOG.md
+++ b/crates/evm-coder/CHANGELOG.md
@@ -1,4 +1,16 @@
-<!-- bureaucrate goes here -->
+# Change Log
+
+All notable changes to this project will be documented in this file.
+## [v0.1.2] 2022-08-19
+
+### Added
+
+ - Implementation `AbiWrite` for tuples.
+
+ ### Fixes
+
+ - Tuple generation for solidity.
+
## [v0.1.1] 2022-08-16
### Other changes
crates/evm-coder/src/abi.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -342,6 +342,16 @@
))
}
}
+ #[allow(non_snake_case)]
+ impl<$($ident),+> AbiWrite for &($($ident,)+)
+ where
+ $($ident: AbiWrite,)+
+ {
+ fn abi_write(&self, writer: &mut AbiWriter) {
+ let ($($ident,)+) = self;
+ $($ident.abi_write(writer);)+
+ }
+ }
};
}
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -180,9 +180,16 @@
fn is_simple() -> bool {
false
}
+ #[allow(unused_assignments)]
fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
write!(writer, "{}(", tc.collect_tuple::<Self>())?;
+ let mut first = true;
$(
+ if !first {
+ write!(writer, ",")?;
+ } else {
+ first = false;
+ }
<$ident>::solidity_default(writer, tc)?;
)*
write!(writer, ")")
pallets/common/CHANGELOG.mddiffbeforeafterboth--- a/pallets/common/CHANGELOG.md
+++ b/pallets/common/CHANGELOG.md
@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file.
+## [0.1.7] - 2022-08-19
+
+### Added
+
+ - Add convert funtion from `CrossAccountId` to eth `uint256`.
+
+
## [0.1.6] - 2022-08-16
### Added
pallets/common/Cargo.tomldiffbeforeafterboth--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-common"
-version = "0.1.6"
+version = "0.1.7"
license = "GPLv3"
edition = "2021"
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -16,13 +16,11 @@
//! The module contains a number of functions for converting and checking ethereum identifiers.
-use evm_coder::{types::*};
-pub use pallet_evm::account::CrossAccountId;
+use evm_coder::types::uint256;
+pub use pallet_evm::account::{Config, CrossAccountId};
use sp_core::H160;
use up_data_structs::CollectionId;
-use crate::Config;
-
// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
// TODO: Unhardcode prefix
const ETH_COLLECTION_PREFIX: [u8; 16] = [
@@ -52,6 +50,15 @@
address[0..16] == ETH_COLLECTION_PREFIX
}
+/// Convert `CrossAccountId` to `uint256`.
+pub fn convert_cross_account_to_uint256<T: Config>(from: &T::CrossAccountId) -> uint256
+where
+ T::AccountId: AsRef<[u8; 32]>,
+{
+ let slice = from.as_sub().as_ref();
+ uint256::from_big_endian(slice)
+}
+
/// Converts Substrate address to CrossAccountId
pub fn convert_substrate_address_to_cross_account_id<T: Config>(
address: uint256,
pallets/evm-contract-helpers/CHANGELOG.mddiffbeforeafterboth--- a/pallets/evm-contract-helpers/CHANGELOG.md
+++ b/pallets/evm-contract-helpers/CHANGELOG.md
@@ -1,4 +1,24 @@
-<!-- bureaucrate goes here -->
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [v0.2.0] - 2022-08-19
+
+### Added
+
+ - Set arbitrary evm address as contract sponsor.
+ - Ability to remove current sponsor.
+
+### Removed
+ - Remove methods
+ + sponsoring_enabled
+ + toggle_sponsoring
+
+ ### Changed
+
+ - Change `toggle_sponsoring` to `self_sponsored_enable`.
+
+
## [v0.1.2] 2022-08-16
### Other changes
pallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-evm-contract-helpers"
-version = "0.1.2"
+version = "0.2.0"
license = "GPLv3"
edition = "2021"
pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -16,14 +16,16 @@
use core::marker::PhantomData;
use evm_coder::{abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*};
-use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
+use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder, dispatch_to_evm};
use pallet_evm::{
ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle,
account::CrossAccountId,
};
use sp_core::H160;
+use up_data_structs::SponsorshipState;
use crate::{
AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringRateLimit, SponsoringModeT,
+ Sponsoring,
};
use frame_support::traits::Get;
use up_sponsorship::SponsorshipHandler;
@@ -41,36 +43,135 @@
}
#[solidity_interface(name = "ContractHelpers")]
-impl<T: Config> ContractHelpers<T> {
+impl<T: Config> ContractHelpers<T>
+where
+ T::AccountId: AsRef<[u8; 32]>,
+{
+ /// Get contract ovner
+ ///
+ /// @param Contract_address contract for which the owner is being determined.
+ /// @return Contract owner.
fn contract_owner(&self, contract_address: address) -> Result<address> {
Ok(<Owner<T>>::get(contract_address))
}
- fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
- Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)
- }
-
- /// Deprecated
- fn toggle_sponsoring(
+ /// Set sponsor.
+ ///
+ /// @param contract_address Contract for which a sponsor is being established.
+ /// @param sponsor User address who set as pending sponsor.
+ fn set_sponsor(
&mut self,
caller: caller,
contract_address: address,
- enabled: bool,
+ sponsor: address,
) -> Result<void> {
- <Pallet<T>>::ensure_owner(contract_address, caller)?;
- <Pallet<T>>::toggle_sponsoring(contract_address, enabled);
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ Pallet::<T>::set_sponsor(
+ &T::CrossAccountId::from_eth(caller),
+ contract_address,
+ &T::CrossAccountId::from_eth(sponsor),
+ )
+ .map_err(dispatch_to_evm::<T>)?;
+
+ Ok(())
+ }
+
+ /// Set contract as self sponsored.
+ ///
+ /// @param contract_address Contract for which a self sponsoring is being enabled.
+ fn self_sponsored_enable(&mut self, caller: caller, contract_address: address) -> Result<void> {
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ Pallet::<T>::self_sponsored_enable(&T::CrossAccountId::from_eth(caller), contract_address)
+ .map_err(dispatch_to_evm::<T>)?;
+
+ Ok(())
+ }
+
+ /// Remove sponsor.
+ ///
+ /// @param contract_address Contract for which a sponsorship is being removed.
+ fn remove_sponsor(&mut self, caller: caller, contract_address: address) -> Result<void> {
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ Pallet::<T>::remove_sponsor(&T::CrossAccountId::from_eth(caller), contract_address)
+ .map_err(dispatch_to_evm::<T>)?;
+
+ Ok(())
+ }
+
+ /// Confirm sponsorship.
+ ///
+ /// @dev Caller must be same that set via [`set_sponsor`].
+ ///
+ /// @param contract_address Сontract for which need to confirm sponsorship.
+ fn confirm_sponsorship(&mut self, caller: caller, contract_address: address) -> Result<void> {
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ Pallet::<T>::confirm_sponsorship(&T::CrossAccountId::from_eth(caller), contract_address)
+ .map_err(dispatch_to_evm::<T>)?;
+
Ok(())
}
+ /// Get current sponsor.
+ ///
+ /// @param contract_address The contract for which a sponsor is requested.
+ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+ fn get_sponsor(&self, contract_address: address) -> Result<(address, uint256)> {
+ let sponsor =
+ Pallet::<T>::get_sponsor(contract_address).ok_or("Contract has no sponsor")?;
+ let result: (address, uint256) = if sponsor.is_canonical_substrate() {
+ let sponsor = pallet_common::eth::convert_cross_account_to_uint256::<T>(&sponsor);
+ (Default::default(), sponsor)
+ } else {
+ let sponsor = *sponsor.as_eth();
+ (sponsor, Default::default())
+ };
+ Ok(result)
+ }
+
+ /// Check tat contract has confirmed sponsor.
+ ///
+ /// @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+ /// @return **true** if contract has confirmed sponsor.
+ fn has_sponsor(&self, contract_address: address) -> Result<bool> {
+ Ok(Pallet::<T>::get_sponsor(contract_address).is_some())
+ }
+
+ /// Check tat contract has pending sponsor.
+ ///
+ /// @param contract_address The contract for which the presence of a pending sponsor is checked.
+ /// @return **true** if contract has pending sponsor.
+ fn has_pending_sponsor(&self, contract_address: address) -> Result<bool> {
+ Ok(match Sponsoring::<T>::get(contract_address) {
+ SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => false,
+ SponsorshipState::Unconfirmed(_) => true,
+ })
+ }
+
+ fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
+ Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)
+ }
+
fn set_sponsoring_mode(
&mut self,
caller: caller,
contract_address: address,
mode: uint8,
) -> Result<void> {
- <Pallet<T>>::ensure_owner(contract_address, caller)?;
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ <Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
let mode = SponsoringModeT::from_eth(mode).ok_or("unknown mode")?;
<Pallet<T>>::set_sponsoring_mode(contract_address, mode);
+
Ok(())
}
@@ -84,8 +185,12 @@
contract_address: address,
rate_limit: uint32,
) -> Result<void> {
- <Pallet<T>>::ensure_owner(contract_address, caller)?;
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ <Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
<Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());
+
Ok(())
}
@@ -110,8 +215,12 @@
contract_address: address,
enabled: bool,
) -> Result<void> {
- <Pallet<T>>::ensure_owner(contract_address, caller)?;
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ <Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
<Pallet<T>>::toggle_allowlist(contract_address, enabled);
+
Ok(())
}
@@ -122,14 +231,21 @@
user: address,
allowed: bool,
) -> Result<void> {
- <Pallet<T>>::ensure_owner(contract_address, caller)?;
+ self.recorder().consume_sload()?;
+ self.recorder().consume_sstore()?;
+
+ <Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
<Pallet<T>>::toggle_allowed(contract_address, user, allowed);
+
Ok(())
}
}
pub struct HelpersOnMethodCall<T: Config>(PhantomData<*const T>);
-impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T> {
+impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T>
+where
+ T::AccountId: AsRef<[u8; 32]>,
+{
fn is_reserved(contract: &sp_core::H160) -> bool {
contract == &T::ContractAddress::get()
}
@@ -179,18 +295,26 @@
for HelpersContractSponsoring<T>
{
fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
- let mode = <Pallet<T>>::sponsoring_mode(call.0);
+ let (contract_address, _) = call;
+ let mode = <Pallet<T>>::sponsoring_mode(*contract_address);
if mode == SponsoringModeT::Disabled {
return None;
}
- if mode == SponsoringModeT::Allowlisted && !<Pallet<T>>::allowed(call.0, *who.as_eth()) {
+ let sponsor = match <Pallet<T>>::get_sponsor(*contract_address) {
+ Some(sponsor) => sponsor,
+ None => return None,
+ };
+
+ if mode == SponsoringModeT::Allowlisted
+ && !<Pallet<T>>::allowed(*contract_address, *who.as_eth())
+ {
return None;
}
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- if let Some(last_tx_block) = <SponsorBasket<T>>::get(&call.0, who.as_eth()) {
- let limit = <SponsoringRateLimit<T>>::get(&call.0);
+ if let Some(last_tx_block) = <SponsorBasket<T>>::get(contract_address, who.as_eth()) {
+ let limit = <SponsoringRateLimit<T>>::get(contract_address);
let timeout = last_tx_block + limit;
if block_number < timeout {
@@ -198,9 +322,8 @@
}
}
- <SponsorBasket<T>>::insert(&call.0, who.as_eth(), block_number);
+ <SponsorBasket<T>>::insert(contract_address, who.as_eth(), block_number);
- let sponsor = T::CrossAccountId::from_eth(call.0);
Some(sponsor)
}
}
pallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -25,9 +25,11 @@
#[frame_support::pallet]
pub mod pallet {
pub use super::*;
- use evm_coder::execution::Result;
use frame_support::pallet_prelude::*;
+ use pallet_evm_coder_substrate::DispatchResult;
use sp_core::H160;
+ use pallet_evm::account::CrossAccountId;
+ use up_data_structs::SponsorshipState;
#[pallet::config]
pub trait Config:
@@ -39,14 +41,21 @@
#[pallet::error]
pub enum Error<T> {
- /// This method is only executable by owner
+ /// This method is only executable by owner.
NoPermission,
+
+ /// No pending sponsor for contract.
+ NoPendingSponsor,
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
+ /// Store owner for contract.
+ ///
+ /// * **Key** - contract address.
+ /// * **Value** - owner for contract.
#[pallet::storage]
pub(super) type Owner<T: Config> =
StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;
@@ -56,10 +65,33 @@
pub(super) type SelfSponsoring<T: Config> =
StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;
+ /// Store for contract sponsorship state.
+ ///
+ /// * **Key** - contract address.
+ /// * **Value** - sponsorship state.
+ #[pallet::storage]
+ pub(super) type Sponsoring<T: Config> = StorageMap<
+ Hasher = Twox64Concat,
+ Key = H160,
+ Value = SponsorshipState<T::CrossAccountId>,
+ QueryKind = ValueQuery,
+ >;
+
+ /// Store for sponsoring mode.
+ ///
+ /// ### Usage
+ /// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).
+ ///
+ /// * **Key** - contract address.
+ /// * **Value** - [`sponsoring mode`](SponsoringModeT).
#[pallet::storage]
pub(super) type SponsoringMode<T: Config> =
StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;
+ /// Storage for sponsoring rate limit in blocks.
+ ///
+ /// * **Key** - contract address.
+ /// * **Value** - amount of sponsored blocks.
#[pallet::storage]
pub(super) type SponsoringRateLimit<T: Config> = StorageMap<
Hasher = Twox128,
@@ -69,6 +101,11 @@
OnEmpty = T::DefaultSponsoringRateLimit,
>;
+ /// Storage for last sponsored block.
+ ///
+ /// * **Key1** - contract address.
+ /// * **Key2** - sponsored user address.
+ /// * **Value** - last sponsored block number.
#[pallet::storage]
pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<
Hasher1 = Twox128,
@@ -79,10 +116,25 @@
QueryKind = OptionQuery,
>;
+ /// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.
+ ///
+ /// ### Usage
+ /// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.
+ ///
+ /// * **Key** - contract address.
+ /// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.
#[pallet::storage]
pub(super) type AllowlistEnabled<T: Config> =
StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;
+ /// Storage for users that allowed for sponsorship.
+ ///
+ /// ### Usage
+ /// Prefer to delete record from storage if user no more allowed for sponsorship.
+ ///
+ /// * **Key1** - contract address.
+ /// * **Key2** - user that allowed for sponsorship.
+ /// * **Value** - allowance for sponsorship.
#[pallet::storage]
pub(super) type Allowlist<T: Config> = StorageDoubleMap<
Hasher1 = Twox128,
@@ -94,6 +146,77 @@
>;
impl<T: Config> Pallet<T> {
+ /// Get contract owner.
+ pub fn contract_owner(contract: H160) -> H160 {
+ <Owner<T>>::get(contract)
+ }
+
+ /// Set `sponsor` for `contract`.
+ ///
+ /// `sender` must be owner of contract.
+ pub fn set_sponsor(
+ sender: &T::CrossAccountId,
+ contract: H160,
+ sponsor: &T::CrossAccountId,
+ ) -> DispatchResult {
+ Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+ Sponsoring::<T>::insert(
+ contract,
+ SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),
+ );
+ Ok(())
+ }
+
+ /// Set `contract` as self sponsored.
+ ///
+ /// `sender` must be owner of contract.
+ pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+ Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+ Sponsoring::<T>::insert(
+ contract,
+ SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(
+ contract,
+ )),
+ );
+ Ok(())
+ }
+
+ /// Remove sponsor for `contract`.
+ ///
+ /// `sender` must be owner of contract.
+ pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+ Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+ Sponsoring::<T>::remove(contract);
+ Ok(())
+ }
+
+ /// Confirm sponsorship.
+ ///
+ /// `sender` must be same that set via [`set_sponsor`].
+ pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+ match Sponsoring::<T>::get(contract) {
+ SponsorshipState::Unconfirmed(sponsor) => {
+ ensure!(sponsor == *sender, Error::<T>::NoPermission);
+ Sponsoring::<T>::insert(
+ contract,
+ SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),
+ );
+ Ok(())
+ }
+ SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {
+ Err(Error::<T>::NoPendingSponsor.into())
+ }
+ }
+ }
+
+ /// Get sponsor.
+ pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {
+ match Sponsoring::<T>::get(contract) {
+ SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,
+ SponsorshipState::Confirmed(sponsor) => Some(sponsor),
+ }
+ }
+
pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {
<SponsoringMode<T>>::get(contract)
.or_else(|| {
@@ -101,6 +224,7 @@
})
.unwrap_or_default()
}
+
pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {
if mode == SponsoringModeT::Disabled {
<SponsoringMode<T>>::remove(contract);
@@ -110,17 +234,6 @@
<SelfSponsoring<T>>::remove(contract)
}
- pub fn toggle_sponsoring(contract: H160, enabled: bool) {
- Self::set_sponsoring_mode(
- contract,
- if enabled {
- SponsoringModeT::Allowlisted
- } else {
- SponsoringModeT::Disabled
- },
- )
- }
-
pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {
<SponsoringRateLimit<T>>::insert(contract, rate_limit);
}
@@ -137,8 +250,8 @@
<Allowlist<T>>::insert(contract, user, allowed);
}
- pub fn ensure_owner(contract: H160, user: H160) -> Result<()> {
- ensure!(<Owner<T>>::get(&contract) == user, "no permission");
+ pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {
+ ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);
Ok(())
}
}
pallets/evm-contract-helpers/src/stubs/ContractHelpers.soldiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
+++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
@@ -3,6 +3,12 @@
pragma solidity >=0.8.0 <0.9.0;
+// Anonymous struct
+struct Tuple0 {
+ address field_0;
+ uint256 field_1;
+}
+
// Common stubs holder
contract Dummy {
uint8 dummy;
@@ -21,8 +27,13 @@
}
}
-// Selector: 7b4866f9
+// Selector: 6073d917
contract ContractHelpers is Dummy, ERC165 {
+ // Get contract ovner
+ //
+ // @param Contract_address contract for which the owner is being determined.
+ // @return Contract owner.
+ //
// Selector: contractOwner(address) 5152b14c
function contractOwner(address contractAddress)
public
@@ -35,26 +46,111 @@
return 0x0000000000000000000000000000000000000000;
}
- // Selector: sponsoringEnabled(address) 6027dc61
- function sponsoringEnabled(address contractAddress)
+ // Set sponsor.
+ //
+ // @param contract_address Contract for which a sponsor is being established.
+ // @param sponsor User address who set as pending sponsor.
+ //
+ // Selector: setSponsor(address,address) f01fba93
+ function setSponsor(address contractAddress, address sponsor) public {
+ require(false, stub_error);
+ contractAddress;
+ sponsor;
+ dummy = 0;
+ }
+
+ // Set contract as self sponsored.
+ //
+ // @param contract_address Contract for which a self sponsoring is being enabled.
+ //
+ // Selector: selfSponsoredEnable(address) 89f7d9ae
+ function selfSponsoredEnable(address contractAddress) public {
+ require(false, stub_error);
+ contractAddress;
+ dummy = 0;
+ }
+
+ // Remove sponsor.
+ //
+ // @param contract_address Contract for which a sponsorship is being removed.
+ //
+ // Selector: removeSponsor(address) ef784250
+ function removeSponsor(address contractAddress) public {
+ require(false, stub_error);
+ contractAddress;
+ dummy = 0;
+ }
+
+ // Confirm sponsorship.
+ //
+ // @dev Caller must be same that set via [`set_sponsor`].
+ //
+ // @param contract_address Сontract for which need to confirm sponsorship.
+ //
+ // Selector: confirmSponsorship(address) abc00001
+ function confirmSponsorship(address contractAddress) public {
+ require(false, stub_error);
+ contractAddress;
+ dummy = 0;
+ }
+
+ // Get current sponsor.
+ //
+ // @param contract_address The contract for which a sponsor is requested.
+ // @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+ //
+ // Selector: getSponsor(address) 743fc745
+ function getSponsor(address contractAddress)
public
view
- returns (bool)
+ returns (Tuple0 memory)
{
require(false, stub_error);
contractAddress;
dummy;
+ return Tuple0(0x0000000000000000000000000000000000000000, 0);
+ }
+
+ // Check tat contract has confirmed sponsor.
+ //
+ // @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+ // @return **true** if contract has confirmed sponsor.
+ //
+ // Selector: hasSponsor(address) 97418603
+ function hasSponsor(address contractAddress) public view returns (bool) {
+ require(false, stub_error);
+ contractAddress;
+ dummy;
return false;
}
- // Deprecated
+ // Check tat contract has pending sponsor.
+ //
+ // @param contract_address The contract for which the presence of a pending sponsor is checked.
+ // @return **true** if contract has pending sponsor.
//
- // Selector: toggleSponsoring(address,bool) fcac6d86
- function toggleSponsoring(address contractAddress, bool enabled) public {
+ // Selector: hasPendingSponsor(address) 39b9b242
+ function hasPendingSponsor(address contractAddress)
+ public
+ view
+ returns (bool)
+ {
+ require(false, stub_error);
+ contractAddress;
+ dummy;
+ return false;
+ }
+
+ // Selector: sponsoringEnabled(address) 6027dc61
+ function sponsoringEnabled(address contractAddress)
+ public
+ view
+ returns (bool)
+ {
require(false, stub_error);
contractAddress;
- enabled;
- dummy = 0;
+ dummy;
+ return false;
}
// Selector: setSponsoringMode(address,uint8) fde8a560
runtime/tests/src/lib.rsdiffbeforeafterboth--- a/runtime/tests/src/lib.rs
+++ b/runtime/tests/src/lib.rs
@@ -167,7 +167,7 @@
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, TypeInfo, MaxEncodedLen)]
-pub struct TestCrossAccountId(u64, sp_core::H160);
+pub struct TestCrossAccountId(u64, sp_core::H160, bool);
impl CrossAccountId<u64> for TestCrossAccountId {
fn as_sub(&self) -> &u64 {
&self.0
@@ -178,17 +178,20 @@
fn from_sub(sub: u64) -> Self {
let mut eth = [0; 20];
eth[12..20].copy_from_slice(&sub.to_be_bytes());
- Self(sub, sp_core::H160(eth))
+ Self(sub, sp_core::H160(eth), true)
}
fn from_eth(eth: sp_core::H160) -> Self {
let mut sub_raw = [0; 8];
sub_raw.copy_from_slice(ð.0[0..8]);
let sub = u64::from_be_bytes(sub_raw);
- Self(sub, eth)
+ Self(sub, eth, false)
}
fn conv_eq(&self, other: &Self) -> bool {
self.as_sub() == other.as_sub()
}
+ fn is_canonical_substrate(&self) -> bool {
+ self.2
+ }
}
impl Default for TestCrossAccountId {
tests/src/eth/api/ContractHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/ContractHelpers.sol
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -3,6 +3,12 @@
pragma solidity >=0.8.0 <0.9.0;
+// Anonymous struct
+struct Tuple0 {
+ address field_0;
+ uint256 field_1;
+}
+
// Common stubs holder
interface Dummy {
@@ -12,24 +18,85 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
-// Selector: 7b4866f9
+// Selector: 6073d917
interface ContractHelpers is Dummy, ERC165 {
+ // Get contract ovner
+ //
+ // @param Contract_address contract for which the owner is being determined.
+ // @return Contract owner.
+ //
// Selector: contractOwner(address) 5152b14c
function contractOwner(address contractAddress)
external
view
returns (address);
+ // Set sponsor.
+ //
+ // @param contract_address Contract for which a sponsor is being established.
+ // @param sponsor User address who set as pending sponsor.
+ //
+ // Selector: setSponsor(address,address) f01fba93
+ function setSponsor(address contractAddress, address sponsor) external;
+
+ // Set contract as self sponsored.
+ //
+ // @param contract_address Contract for which a self sponsoring is being enabled.
+ //
+ // Selector: selfSponsoredEnable(address) 89f7d9ae
+ function selfSponsoredEnable(address contractAddress) external;
+
+ // Remove sponsor.
+ //
+ // @param contract_address Contract for which a sponsorship is being removed.
+ //
+ // Selector: removeSponsor(address) ef784250
+ function removeSponsor(address contractAddress) external;
+
+ // Confirm sponsorship.
+ //
+ // @dev Caller must be same that set via [`set_sponsor`].
+ //
+ // @param contract_address Сontract for which need to confirm sponsorship.
+ //
+ // Selector: confirmSponsorship(address) abc00001
+ function confirmSponsorship(address contractAddress) external;
+
+ // Get current sponsor.
+ //
+ // @param contract_address The contract for which a sponsor is requested.
+ // @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+ //
+ // Selector: getSponsor(address) 743fc745
+ function getSponsor(address contractAddress)
+ external
+ view
+ returns (Tuple0 memory);
+
+ // Check tat contract has confirmed sponsor.
+ //
+ // @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+ // @return **true** if contract has confirmed sponsor.
+ //
+ // Selector: hasSponsor(address) 97418603
+ function hasSponsor(address contractAddress) external view returns (bool);
+
+ // Check tat contract has pending sponsor.
+ //
+ // @param contract_address The contract for which the presence of a pending sponsor is checked.
+ // @return **true** if contract has pending sponsor.
+ //
+ // Selector: hasPendingSponsor(address) 39b9b242
+ function hasPendingSponsor(address contractAddress)
+ external
+ view
+ returns (bool);
+
// Selector: sponsoringEnabled(address) 6027dc61
function sponsoringEnabled(address contractAddress)
external
view
returns (bool);
-
- // Deprecated
- //
- // Selector: toggleSponsoring(address,bool) fcac6d86
- function toggleSponsoring(address contractAddress, bool enabled) external;
// Selector: setSponsoringMode(address,uint8) fde8a560
function setSponsoringMode(address contractAddress, uint8 mode) external;
tests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -1,7 +1,8 @@
-import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, setCollectionSponsorExpectSuccess} from '../util/helpers';
-import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents} from './util/helpers';
+import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess} from '../util/helpers';
+import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub} from './util/helpers';
import nonFungibleAbi from './nonFungibleAbi.json';
import {expect} from 'chai';
+import {evmToAddress} from '@polkadot/util-crypto';
describe('evm collection sponsoring', () => {
itWeb3('sponsors mint transactions', async ({web3, privateKeyWrapper}) => {
@@ -36,4 +37,130 @@
},
]);
});
+
+ itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => {
+ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const collectionHelpers = evmCollectionHelpers(web3, owner);
+ let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+ const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+ const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+ result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
+ let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+ const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
+ expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+ expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+ await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
+
+ await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
+ collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+ expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+ expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+
+ const user = createEthAccount(web3);
+ const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+ expect(nextTokenId).to.be.equal('1');
+
+ const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
+ expect(oldPermissions.mintMode).to.be.false;
+ expect(oldPermissions.access).to.be.equal('Normal');
+
+ await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
+ await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
+ await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+
+ const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
+ expect(newPermissions.mintMode).to.be.true;
+ expect(newPermissions.access).to.be.equal('AllowList');
+
+ const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
+ const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+
+ {
+ const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+ expect(nextTokenId).to.be.equal('1');
+ const result = await collectionEvm.methods.mintWithTokenURI(
+ user,
+ nextTokenId,
+ 'Test URI',
+ ).send({from: user});
+ const events = normalizeEvents(result.events);
+
+ expect(events).to.be.deep.equal([
+ {
+ address: collectionIdAddress,
+ event: 'Transfer',
+ args: {
+ from: '0x0000000000000000000000000000000000000000',
+ to: user,
+ tokenId: nextTokenId,
+ },
+ },
+ ]);
+
+ const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
+ const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
+
+ expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+ expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
+ expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
+ }
+ });
+
+ itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => {
+ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const collectionHelpers = evmCollectionHelpers(web3, owner);
+ let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+ const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+ const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+ result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
+ let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+ const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
+ expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+ expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+ await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
+ const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+ await sponsorCollection.methods.confirmCollectionSponsorship().send();
+ collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+ expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+ expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+
+ const user = createEthAccount(web3);
+ await collectionEvm.methods.addCollectionAdmin(user).send();
+
+ const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
+ const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+
+
+ const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);
+ const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();
+ expect(nextTokenId).to.be.equal('1');
+ result = await userCollectionEvm.methods.mintWithTokenURI(
+ user,
+ nextTokenId,
+ 'Test URI',
+ ).send();
+
+ const events = normalizeEvents(result.events);
+ const address = collectionIdToAddress(collectionId);
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: '0x0000000000000000000000000000000000000000',
+ to: user,
+ tokenId: nextTokenId,
+ },
+ },
+ ]);
+ expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+
+ const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
+ expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
+ const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
+ expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
+ });
});
tests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth23 itWeb3,23 itWeb3,24 SponsoringMode,24 SponsoringMode,25 createEthAccount,25 createEthAccount,26 collectionIdToAddress,27 GAS_ARGS,28 normalizeEvents,29 subToEth,30 executeEthTxOnSub,31 evmCollectionHelpers,32 getCollectionAddressFromResult,33 evmCollection,34 ethBalanceViaSub,26 ethBalanceViaSub,35} from './util/helpers';27} from './util/helpers';36import {37 addCollectionAdminExpectSuccess,38 createCollectionExpectSuccess,39 getDetailedCollectionInfo,40 transferBalanceTo,41} from '../util/helpers';42import nonFungibleAbi from './nonFungibleAbi.json';43import getBalance from '../substrate/get-balance';44import {evmToAddress} from '@polkadot/util-crypto';452846describe('Sponsoring EVM contracts', () => {29describe('Sponsoring EVM contracts', () => {30 itWeb3('Self sponsored can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {31 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);32 const flipper = await deployFlipper(web3, owner);33 const helpers = contractHelpers(web3, owner);34 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;35 await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;36 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;37 });3839 itWeb3('Self sponsored can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {40 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);41 const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);42 const flipper = await deployFlipper(web3, owner);43 const helpers = contractHelpers(web3, owner);44 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;45 await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission');46 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;47 });4847 itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => {49 itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => {48 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);50 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);49 const flipper = await deployFlipper(web3, owner);51 const flipper = await deployFlipper(web3, owner);50 const helpers = contractHelpers(web3, owner);52 const helpers = contractHelpers(web3, owner);51 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;53 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;52 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});54 await expect(helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner})).to.be.not.rejected;53 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;55 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;54 });56 });555759 const flipper = await deployFlipper(web3, owner);61 const flipper = await deployFlipper(web3, owner);60 const helpers = contractHelpers(web3, owner);62 const helpers = contractHelpers(web3, owner);61 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;63 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;62 await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).send({from: notOwner})).to.rejected;64 await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).call({from: notOwner})).to.be.rejectedWith('NoPermission');63 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;65 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;64 });66 });67 68 itWeb3('Sponsor can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {69 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);70 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);71 const flipper = await deployFlipper(web3, owner);72 const helpers = contractHelpers(web3, owner);73 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;74 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;75 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true;76 });77 78 itWeb3('Sponsor can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {79 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);80 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);81 const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);82 const flipper = await deployFlipper(web3, owner);83 const helpers = contractHelpers(web3, owner);84 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;85 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).call({from: notOwner})).to.be.rejectedWith('NoPermission');86 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;87 });658889 itWeb3('Sponsorship can be confirmed by the address that pending as sponsor', async ({api, web3, privateKeyWrapper}) => {90 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);91 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);92 const flipper = await deployFlipper(web3, owner);93 const helpers = contractHelpers(web3, owner);94 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;95 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;96 await expect(helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor})).to.be.not.rejected;97 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;98 });99100 itWeb3('Sponsorship can not be confirmed by the address that not pending as sponsor', async ({api, web3, privateKeyWrapper}) => {101 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);102 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);103 const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);104 const flipper = await deployFlipper(web3, owner);105 const helpers = contractHelpers(web3, owner);106 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;107 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;108 await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPermission');109 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;110 });111112 itWeb3('Sponsorship can not be confirmed by the address that not set as sponsor', async ({api, web3, privateKeyWrapper}) => {113 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);114 const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);115 const flipper = await deployFlipper(web3, owner);116 const helpers = contractHelpers(web3, owner);117 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;118 await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPendingSponsor');119 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;120 });121122 itWeb3('Get self sponsored sponsor', async ({api, web3, privateKeyWrapper}) => {123 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);124 const flipper = await deployFlipper(web3, owner);125 const helpers = contractHelpers(web3, owner);126 await helpers.methods.selfSponsoredEnable(flipper.options.address).send();127 128 const result = await helpers.methods.getSponsor(flipper.options.address).call();129130 expect(result[0]).to.be.eq(flipper.options.address);131 expect(result[1]).to.be.eq('0');132 });133134 itWeb3('Get confirmed sponsor', async ({api, web3, privateKeyWrapper}) => {135 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);136 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);137 const flipper = await deployFlipper(web3, owner);138 const helpers = contractHelpers(web3, owner);139 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();140 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});141 142 const result = await helpers.methods.getSponsor(flipper.options.address).call();143144 expect(result[0]).to.be.eq(sponsor);145 expect(result[1]).to.be.eq('0');146 });147148 itWeb3('Sponsor can be removed by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {149 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);150 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);151 const flipper = await deployFlipper(web3, owner);152 const helpers = contractHelpers(web3, owner);153154 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;155 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();156 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});157 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;158 159 await helpers.methods.removeSponsor(flipper.options.address).send();160 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;161 });162163 itWeb3('Sponsor can not be removed by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {164 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);165 const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);166 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);167 const flipper = await deployFlipper(web3, owner);168 const helpers = contractHelpers(web3, owner);169170 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;171 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();172 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});173 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;174 175 await expect(helpers.methods.removeSponsor(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission');176 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;177 });17866 itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {179 itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {180 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);181 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);182 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);183184 const flipper = await deployFlipper(web3, owner);185186 const helpers = contractHelpers(web3, owner);187188 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();189 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});190191 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});192 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});193194 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);195 const callerBalanceBefore = await ethBalanceViaSub(api, caller);196197 await flipper.methods.flip().send({from: caller});198 expect(await flipper.methods.getValue().call()).to.be.true;199200 // Balance should be taken from sponsor instead of caller201 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);202 const callerBalanceAfter = await ethBalanceViaSub(api, caller);203 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;204 expect(callerBalanceAfter).to.be.eq(callerBalanceBefore);205 });206207 itWeb3('In generous mode, non-allowlisted user transaction will be self sponsored', async ({api, web3, privateKeyWrapper}) => {67 const alice = privateKeyWrapper('//Alice');208 const alice = privateKeyWrapper('//Alice');6820969 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);210 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);7321474 const helpers = contractHelpers(web3, owner);215 const helpers = contractHelpers(web3, owner);7521676 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;217 await helpers.methods.selfSponsoredEnable(flipper.options.address).send();21877 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});219 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});78 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});220 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});79 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;8022181 await transferBalanceToEth(api, alice, flipper.options.address);222 await transferBalanceToEth(api, alice, flipper.options.address);8222383 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);224 const contractBalanceBefore = await ethBalanceViaSub(api, flipper.options.address);84 expect(originalFlipperBalance).to.be.not.equal('0');225 const callerBalanceBefore = await ethBalanceViaSub(api, caller);8522686 await flipper.methods.flip().send({from: caller});227 await flipper.methods.flip().send({from: caller});87 expect(await flipper.methods.getValue().call()).to.be.true;228 expect(await flipper.methods.getValue().call()).to.be.true;8822989 // Balance should be taken from flipper instead of caller230 // Balance should be taken from sponsor instead of caller90 const balanceAfter = await web3.eth.getBalance(flipper.options.address);231 const contractBalanceAfter = await ethBalanceViaSub(api, flipper.options.address);232 const callerBalanceAfter = await ethBalanceViaSub(api, caller);233 expect(contractBalanceAfter < contractBalanceBefore).to.be.true;91 expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);234 expect(callerBalanceAfter).to.be.eq(callerBalanceBefore);92 });235 });9323694 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {237 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {95 const alice = privateKeyWrapper('//Alice');9697 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);238 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);239 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);98 const caller = createEthAccount(web3);240 const caller = createEthAccount(web3);99241100 const flipper = await deployFlipper(web3, owner);242 const flipper = await deployFlipper(web3, owner);103 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});245 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});104 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});246 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});105247106 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;107 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});248 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});108 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});249 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});109 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;110250111 await transferBalanceToEth(api, alice, flipper.options.address);251 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();252 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});112253113 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);254 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);114 expect(originalFlipperBalance).to.be.not.equal('0');255 expect(sponsorBalanceBefore).to.be.not.equal('0');115256116 await flipper.methods.flip().send({from: caller});257 await flipper.methods.flip().send({from: caller});117 expect(await flipper.methods.getValue().call()).to.be.true;258 expect(await flipper.methods.getValue().call()).to.be.true;118259119 // Balance should be taken from flipper instead of caller260 // Balance should be taken from flipper instead of caller120 const balanceAfter = await web3.eth.getBalance(flipper.options.address);261 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);121 expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);262 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;122 });263 });123264124 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {265 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {131272132 const helpers = contractHelpers(web3, owner);273 const helpers = contractHelpers(web3, owner);133274134 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;135 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});275 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});136 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});276 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});137 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;138277139 await transferBalanceToEth(api, alice, flipper.options.address);278 await transferBalanceToEth(api, alice, flipper.options.address);140279150 });289 });151290152 itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {291 itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {153 const alice = privateKeyWrapper('//Alice');154155 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);292 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);293 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);156 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);294 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);157 const originalCallerBalance = await web3.eth.getBalance(caller);158295159 const flipper = await deployFlipper(web3, owner);296 const flipper = await deployFlipper(web3, owner);160297161 const helpers = contractHelpers(web3, owner);298 const helpers = contractHelpers(web3, owner);162 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});299 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});163 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});300 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});164301165 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;166 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});302 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});167 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});303 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});168 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;169304170 await transferBalanceToEth(api, alice, flipper.options.address);305 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();306 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});171307172 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);308 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);173 expect(originalFlipperBalance).to.be.not.equal('0');309 const callerBalanceBefore = await ethBalanceViaSub(api, caller);174310175 await flipper.methods.flip().send({from: caller});311 await flipper.methods.flip().send({from: caller});176 expect(await flipper.methods.getValue().call()).to.be.true;312 expect(await flipper.methods.getValue().call()).to.be.true;177313178 expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);314 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);315 const callerBalanceAfter = await ethBalanceViaSub(api, caller);316 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;317 expect(callerBalanceAfter).to.be.equals(callerBalanceBefore);179 });318 });180319181 itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {320 itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {182 const alice = privateKeyWrapper('//Alice');183184 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);321 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);322 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);185 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);323 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);186 const originalCallerBalance = await web3.eth.getBalance(caller);324 const originalCallerBalance = await web3.eth.getBalance(caller);187325191 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});329 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});192 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});330 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});193331194 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;195 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});332 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});196 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});333 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});197 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;198334199 await transferBalanceToEth(api, alice, flipper.options.address);335 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();336 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});200337201 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);338 const originalFlipperBalance = await web3.eth.getBalance(sponsor);202 expect(originalFlipperBalance).to.be.not.equal('0');339 expect(originalFlipperBalance).to.be.not.equal('0');203340204 await flipper.methods.flip().send({from: caller});341 await flipper.methods.flip().send({from: caller});205 expect(await flipper.methods.getValue().call()).to.be.true;342 expect(await flipper.methods.getValue().call()).to.be.true;206 expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);343 expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);207344208 const newFlipperBalance = await web3.eth.getBalance(flipper.options.address);345 const newFlipperBalance = await web3.eth.getBalance(sponsor);209 expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);346 expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);210347211 await flipper.methods.flip().send({from: caller});348 await flipper.methods.flip().send({from: caller});212 expect(await web3.eth.getBalance(flipper.options.address)).to.be.equal(newFlipperBalance);349 expect(await web3.eth.getBalance(sponsor)).to.be.equal(newFlipperBalance);213 expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);350 expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);214 });351 });215352219 const flipper = await deployFlipper(web3, owner);356 const flipper = await deployFlipper(web3, owner);220 const helpers = contractHelpers(web3, owner);357 const helpers = contractHelpers(web3, owner);221 expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');358 expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');222 });223224 itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => {225 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);226 const collectionHelpers = evmCollectionHelpers(web3, owner);227 let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();228 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);229 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);230 const collectionEvm = evmCollection(web3, owner, collectionIdAddress);231 result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});232 let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;233 const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;234 expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;235 expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));236 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');237238 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});239 collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;240 expect(collectionSub.sponsorship.isConfirmed).to.be.true;241 expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));242243 const user = createEthAccount(web3);244 const nextTokenId = await collectionEvm.methods.nextTokenId().call();245 expect(nextTokenId).to.be.equal('1');246247 const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();248 expect(oldPermissions.mintMode).to.be.false;249 expect(oldPermissions.access).to.be.equal('Normal');250251 await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});252 await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});253 await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});254255 const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();256 expect(newPermissions.mintMode).to.be.true;257 expect(newPermissions.access).to.be.equal('AllowList');258259 const ownerBalanceBefore = await ethBalanceViaSub(api, owner);260 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);261262 {263 const nextTokenId = await collectionEvm.methods.nextTokenId().call();264 expect(nextTokenId).to.be.equal('1');265 const result = await collectionEvm.methods.mintWithTokenURI(266 user,267 nextTokenId,268 'Test URI',269 ).send({from: user});270 const events = normalizeEvents(result.events);271272 expect(events).to.be.deep.equal([273 {274 address: collectionIdAddress,275 event: 'Transfer',276 args: {277 from: '0x0000000000000000000000000000000000000000',278 to: user,279 tokenId: nextTokenId,280 },281 },282 ]);283284 const ownerBalanceAfter = await ethBalanceViaSub(api, owner);285 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);286287 expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');288 expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);289 expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;290 }291 });292293 itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => {294 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);295 const collectionHelpers = evmCollectionHelpers(web3, owner);296 let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();297 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);298 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);299 const collectionEvm = evmCollection(web3, owner, collectionIdAddress);300 result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();301 let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;302 const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;303 expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;304 expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));305 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');306 const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);307 await sponsorCollection.methods.confirmCollectionSponsorship().send();308 collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;309 expect(collectionSub.sponsorship.isConfirmed).to.be.true;310 expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));311312 const user = createEthAccount(web3);313 await collectionEvm.methods.addCollectionAdmin(user).send();314 315 const ownerBalanceBefore = await ethBalanceViaSub(api, owner);316 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);317 318 319 const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);320 const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();321 expect(nextTokenId).to.be.equal('1');322 result = await userCollectionEvm.methods.mintWithTokenURI(323 user,324 nextTokenId,325 'Test URI',326 ).send();327328 const events = normalizeEvents(result.events);329 const address = collectionIdToAddress(collectionId);330331 expect(events).to.be.deep.equal([332 {333 address,334 event: 'Transfer',335 args: {336 from: '0x0000000000000000000000000000000000000000',337 to: user,338 tokenId: nextTokenId,339 },340 },341 ]);342 expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');343 344 const ownerBalanceAfter = await ethBalanceViaSub(api, owner);345 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);346 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);347 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;348 });359 });349});360});350361tests/src/eth/marketplace/marketplace.test.tsdiffbeforeafterboth--- a/tests/src/eth/marketplace/marketplace.test.ts
+++ b/tests/src/eth/marketplace/marketplace.test.ts
@@ -39,6 +39,7 @@
describe('Matcher contract usage', () => {
itWeb3('With UNQ', async ({api, web3, privateKeyWrapper}) => {
const alice = privateKeyWrapper('//Alice');
+ const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
from: matcherOwner,
@@ -48,7 +49,9 @@
const helpers = contractHelpers(web3, matcherOwner);
await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner});
await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
- await transferBalanceToEth(api, alice, matcher.options.address);
+
+ await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner});
+ await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor});
const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1});
@@ -100,6 +103,7 @@
itWeb3('With escrow', async ({api, web3, privateKeyWrapper}) => {
const alice = privateKeyWrapper('//Alice');
+ const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const escrow = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
@@ -111,7 +115,9 @@
const helpers = contractHelpers(web3, matcherOwner);
await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner});
await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
- await transferBalanceToEth(api, alice, matcher.options.address);
+
+ await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner});
+ await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor});
const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1});
tests/src/eth/sponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/sponsoring.test.ts
+++ b/tests/src/eth/sponsoring.test.ts
@@ -15,13 +15,12 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {expect} from 'chai';
-import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode, transferBalanceToEth} from './util/helpers';
+import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode} from './util/helpers';
describe('EVM sponsoring', () => {
itWeb3('Fee is deducted from contract if sponsoring is enabled', async ({api, web3, privateKeyWrapper}) => {
- const alice = privateKeyWrapper('//Alice');
-
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const caller = createEthAccount(web3);
const originalCallerBalance = await web3.eth.getBalance(caller);
expect(originalCallerBalance).to.be.equal('0');
@@ -31,28 +30,29 @@
const helpers = contractHelpers(web3, owner);
await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});
await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
+
+ await helpers.methods.setSponsor(flipper.options.address, sponsor).send({from: owner});
+ await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
- await transferBalanceToEth(api, alice, flipper.options.address);
-
- const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);
- expect(originalFlipperBalance).to.be.not.equal('0');
+ const originalSponsorBalance = await web3.eth.getBalance(sponsor);
+ expect(originalSponsorBalance).to.be.not.equal('0');
await flipper.methods.flip().send({from: caller});
expect(await flipper.methods.getValue().call()).to.be.true;
// Balance should be taken from flipper instead of caller
expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);
- expect(await web3.eth.getBalance(flipper.options.address)).to.be.not.equals(originalFlipperBalance);
+ expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance);
});
+
itWeb3('...but this doesn\'t applies to payable value', async ({api, web3, privateKeyWrapper}) => {
- const alice = privateKeyWrapper('//Alice');
-
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const originalCallerBalance = await web3.eth.getBalance(caller);
expect(originalCallerBalance).to.be.not.equal('0');
@@ -68,16 +68,17 @@
await helpers.methods.setSponsoringRateLimit(collector.options.address, 0).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(collector.options.address).call()).to.be.true;
- await transferBalanceToEth(api, alice, collector.options.address);
+ await helpers.methods.setSponsor(collector.options.address, sponsor).send({from: owner});
+ await helpers.methods.confirmSponsorship(collector.options.address).send({from: sponsor});
- const originalCollectorBalance = await web3.eth.getBalance(collector.options.address);
- expect(originalCollectorBalance).to.be.not.equal('0');
+ const originalSponsorBalance = await web3.eth.getBalance(sponsor);
+ expect(originalSponsorBalance).to.be.not.equal('0');
await collector.methods.giveMoney().send({from: caller, value: '10000'});
// Balance will be taken from both caller (value) and from collector (fee)
expect(await web3.eth.getBalance(caller)).to.be.equals((BigInt(originalCallerBalance) - 10000n).toString());
- expect(await web3.eth.getBalance(collector.options.address)).to.be.not.equals(originalCollectorBalance);
+ expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance);
expect(await collector.methods.getCollected().call()).to.be.equal('10000');
});
});
tests/src/eth/util/contractHelpersAbi.jsondiffbeforeafterboth--- a/tests/src/eth/util/contractHelpersAbi.json
+++ b/tests/src/eth/util/contractHelpersAbi.json
@@ -34,6 +34,19 @@
"type": "address"
}
],
+ "name": "confirmSponsorship",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "contractAddress",
+ "type": "address"
+ }
+ ],
"name": "contractOwner",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
@@ -47,6 +60,29 @@
"type": "address"
}
],
+ "name": "getSponsor",
+ "outputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple0",
+ "name": "",
+ "type": "tuple"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "contractAddress",
+ "type": "address"
+ }
+ ],
"name": "getSponsoringRateLimit",
"outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
"stateMutability": "view",
@@ -58,6 +94,72 @@
"internalType": "address",
"name": "contractAddress",
"type": "address"
+ }
+ ],
+ "name": "hasPendingSponsor",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "contractAddress",
+ "type": "address"
+ }
+ ],
+ "name": "hasSponsor",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "contractAddress",
+ "type": "address"
+ }
+ ],
+ "name": "removeSponsor",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "contractAddress",
+ "type": "address"
+ }
+ ],
+ "name": "selfSponsoredEnable",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "contractAddress",
+ "type": "address"
+ },
+ { "internalType": "address", "name": "sponsor", "type": "address" }
+ ],
+ "name": "setSponsor",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "contractAddress",
+ "type": "address"
},
{ "internalType": "uint8", "name": "mode", "type": "uint8" }
],
@@ -140,20 +242,6 @@
{ "internalType": "bool", "name": "enabled", "type": "bool" }
],
"name": "toggleAllowlist",
- "outputs": [],
- "stateMutability": "nonpayable",
- "type": "function"
- },
- {
- "inputs": [
- {
- "internalType": "address",
- "name": "contractAddress",
- "type": "address"
- },
- { "internalType": "bool", "name": "enabled", "type": "bool" }
- ],
- "name": "toggleSponsoring",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"