difftreelog
Merge pull request #281 from UniqueNetwork/feature/contract-sponsoring-modes
in: master
Generous contract sponsoring mode
12 files changed
crates/evm-coder/src/abi.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -95,6 +95,10 @@
string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))
}
+ pub fn uint8(&mut self) -> Result<u8> {
+ Ok(self.read_padleft::<1>()?[0])
+ }
+
pub fn uint32(&mut self) -> Result<u32> {
Ok(u32::from_be_bytes(self.read_padleft()?))
}
@@ -243,6 +247,7 @@
};
}
+impl_abi_readable!(u8, uint8);
impl_abi_readable!(u32, uint32);
impl_abi_readable!(u64, uint64);
impl_abi_readable!(u128, uint128);
pallets/evm-contract-helpers/exp.rsdiffbeforeafterbothno changes
pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -4,7 +4,7 @@
use pallet_evm::{ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure};
use sp_core::H160;
use crate::{
- AllowlistEnabled, Config, Owner, Pallet, SelfSponsoring, SponsorBasket, SponsoringRateLimit,
+ AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringRateLimit, SponsoringModeT,
};
use frame_support::traits::Get;
use up_sponsorship::SponsorshipHandler;
@@ -28,9 +28,10 @@
}
fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
- Ok(<SelfSponsoring<T>>::get(contract_address))
+ Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)
}
+ /// Deprecated
fn toggle_sponsoring(
&mut self,
caller: caller,
@@ -42,6 +43,22 @@
Ok(())
}
+ fn set_sponsoring_mode(
+ &mut self,
+ caller: caller,
+ contract_address: address,
+ mode: uint8,
+ ) -> Result<void> {
+ <Pallet<T>>::ensure_owner(contract_address, caller)?;
+ let mode = SponsoringModeT::from_eth(mode).ok_or("unknown mode")?;
+ <Pallet<T>>::set_sponsoring_mode(contract_address, mode);
+ Ok(())
+ }
+
+ fn sponsoring_mode(&self, contract_address: address) -> Result<uint8> {
+ Ok(<Pallet<T>>::sponsoring_mode(contract_address).to_eth())
+ }
+
fn set_sponsoring_rate_limit(
&mut self,
caller: caller,
@@ -146,10 +163,11 @@
pub struct HelpersContractSponsoring<T: Config>(PhantomData<*const T>);
impl<T: Config> SponsorshipHandler<H160, (H160, Vec<u8>)> for HelpersContractSponsoring<T> {
fn get_sponsor(who: &H160, call: &(H160, Vec<u8>)) -> Option<H160> {
- if !<SelfSponsoring<T>>::get(&call.0) {
+ let mode = <Pallet<T>>::sponsoring_mode(call.0);
+ if mode == SponsoringModeT::Disabled {
return None;
}
- if !<Pallet<T>>::allowed(call.0, *who) {
+ if mode == SponsoringModeT::Allowlisted && !<Pallet<T>>::allowed(call.0, *who) {
return None;
}
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
pallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -1,11 +1,14 @@
#![cfg_attr(not(feature = "std"), no_std)]
+use codec::{Decode, Encode, MaxEncodedLen};
pub use pallet::*;
pub use eth::*;
+use scale_info::TypeInfo;
pub mod eth;
#[frame_support::pallet]
pub mod pallet {
+ pub use super::*;
use evm_coder::execution::Result;
use frame_support::pallet_prelude::*;
use sp_core::H160;
@@ -31,10 +34,15 @@
StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;
#[pallet::storage]
+ #[deprecated]
pub(super) type SelfSponsoring<T: Config> =
StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;
#[pallet::storage]
+ pub(super) type SponsoringMode<T: Config> =
+ StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;
+
+ #[pallet::storage]
pub(super) type SponsoringRateLimit<T: Config> = StorageMap<
Hasher = Twox128,
Key = H160,
@@ -68,8 +76,31 @@
>;
impl<T: Config> Pallet<T> {
+ pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {
+ <SponsoringMode<T>>::get(contract)
+ .or_else(|| {
+ <SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)
+ })
+ .unwrap_or_default()
+ }
+ pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {
+ if mode == SponsoringModeT::Disabled {
+ <SponsoringMode<T>>::remove(contract);
+ } else {
+ <SponsoringMode<T>>::insert(contract, mode);
+ }
+ <SelfSponsoring<T>>::remove(contract)
+ }
+
pub fn toggle_sponsoring(contract: H160, enabled: bool) {
- <SelfSponsoring<T>>::insert(contract, enabled);
+ Self::set_sponsoring_mode(
+ contract,
+ if enabled {
+ SponsoringModeT::Allowlisted
+ } else {
+ SponsoringModeT::Disabled
+ },
+ )
}
pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {
@@ -94,3 +125,34 @@
}
}
}
+
+#[derive(Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen)]
+pub enum SponsoringModeT {
+ Disabled,
+ Allowlisted,
+ Generous,
+}
+
+impl SponsoringModeT {
+ fn from_eth(v: u8) -> Option<Self> {
+ Some(match v {
+ 0 => Self::Disabled,
+ 1 => Self::Allowlisted,
+ 2 => Self::Generous,
+ _ => return None,
+ })
+ }
+ fn to_eth(self) -> u8 {
+ match self {
+ SponsoringModeT::Disabled => 0,
+ SponsoringModeT::Allowlisted => 1,
+ SponsoringModeT::Generous => 2,
+ }
+ }
+}
+
+impl Default for SponsoringModeT {
+ fn default() -> Self {
+ Self::Disabled
+ }
+}
pallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterbothbinary blob — no preview
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
@@ -21,7 +21,7 @@
}
}
-// Selector: 31acb1fe
+// Selector: 7b4866f9
contract ContractHelpers is Dummy, ERC165 {
// Selector: contractOwner(address) 5152b14c
function contractOwner(address contractAddress)
@@ -47,6 +47,8 @@
return false;
}
+ // Deprecated
+ //
// Selector: toggleSponsoring(address,bool) fcac6d86
function toggleSponsoring(address contractAddress, bool enabled) public {
require(false, stub_error);
@@ -55,6 +57,26 @@
dummy = 0;
}
+ // Selector: setSponsoringMode(address,uint8) fde8a560
+ function setSponsoringMode(address contractAddress, uint8 mode) public {
+ require(false, stub_error);
+ contractAddress;
+ mode;
+ dummy = 0;
+ }
+
+ // Selector: sponsoringMode(address) b70c7267
+ function sponsoringMode(address contractAddress)
+ public
+ view
+ returns (uint8)
+ {
+ require(false, stub_error);
+ contractAddress;
+ dummy;
+ return 0;
+ }
+
// Selector: setSponsoringRateLimit(address,uint32) 77b6c908
function setSponsoringRateLimit(address contractAddress, uint32 rateLimit)
public
tests/src/eth/api/ContractHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/ContractHelpers.sol
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -12,7 +12,7 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
-// Selector: 31acb1fe
+// Selector: 7b4866f9
interface ContractHelpers is Dummy, ERC165 {
// Selector: contractOwner(address) 5152b14c
function contractOwner(address contractAddress)
@@ -26,9 +26,20 @@
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;
+
+ // Selector: sponsoringMode(address) b70c7267
+ function sponsoringMode(address contractAddress)
+ external
+ view
+ returns (uint8);
+
// Selector: setSponsoringRateLimit(address,uint32) 77b6c908
function setSponsoringRateLimit(address contractAddress, uint32 rateLimit)
external;
tests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/contractSponsoring.test.ts
+++ b/tests/src/eth/contractSponsoring.test.ts
@@ -10,7 +10,10 @@
createEthAccountWithBalance,
transferBalanceToEth,
deployFlipper,
- itWeb3} from './util/helpers';
+ itWeb3,
+ SponsoringMode,
+ createEthAccount,
+} from './util/helpers';
describe('Sponsoring EVM contracts', () => {
itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3}) => {
@@ -18,7 +21,7 @@
const flipper = await deployFlipper(web3, owner);
const helpers = contractHelpers(web3, owner);
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(flipper.options.address, true).send({from: owner});
+ await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
});
@@ -28,11 +31,11 @@
const flipper = await deployFlipper(web3, owner);
const helpers = contractHelpers(web3, owner);
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await expect(helpers.methods.toggleSponsoring(notOwner, true).send({from: notOwner})).to.rejected;
+ await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).send({from: notOwner})).to.rejected;
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
});
- 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}) => {
+ itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3}) => {
const alice = privateKey('//Alice');
const owner = await createEthAccountWithBalance(api, web3);
@@ -41,11 +44,39 @@
const flipper = await deployFlipper(web3, owner);
const helpers = contractHelpers(web3, owner);
+
+ expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
+ await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).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');
+
+ 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
+ const balanceAfter = await web3.eth.getBalance(flipper.options.address);
+ expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);
+ });
+
+ 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}) => {
+ const alice = privateKey('//Alice');
+
+ const owner = await createEthAccountWithBalance(api, web3);
+ const caller = createEthAccount(web3);
+
+ const flipper = await deployFlipper(web3, owner);
+
+ 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});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(flipper.options.address, true).send({from: owner});
+ 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;
@@ -66,14 +97,14 @@
const alice = privateKey('//Alice');
const owner = await createEthAccountWithBalance(api, web3);
- const caller = await createEthAccountWithBalance(api, web3);
+ const caller = createEthAccount(web3);
const flipper = await deployFlipper(web3, owner);
const helpers = contractHelpers(web3, owner);
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(flipper.options.address, true).send({from: owner});
+ 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;
@@ -104,7 +135,7 @@
await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(flipper.options.address, true).send({from: owner});
+ 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;
@@ -133,7 +164,7 @@
await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(flipper.options.address, true).send({from: owner});
+ await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
@@ -157,33 +188,4 @@
const helpers = contractHelpers(web3, owner);
expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');
});
-
- itWeb3('If allowlist mode is off and sponsorship is on, sponsorship does not work', async ({api, web3}) => {
- const alice = privateKey('//Alice');
-
- const owner = await createEthAccountWithBalance(api, web3);
- const caller = await createEthAccountWithBalance(api, web3);
-
- const flipper = await deployFlipper(web3, owner);
-
- const helpers = contractHelpers(web3, owner);
-
- expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(flipper.options.address, true).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');
-
- 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
- const balanceAfter = await web3.eth.getBalance(flipper.options.address);
- expect(+balanceAfter).to.be.equals(+originalFlipperBalance);
- });
-
});
tests/src/eth/marketplace/marketplace.test.tsdiffbeforeafterboth--- a/tests/src/eth/marketplace/marketplace.test.ts
+++ b/tests/src/eth/marketplace/marketplace.test.ts
@@ -2,7 +2,7 @@
import {getBalanceSingle, transferBalanceExpectSuccess} from '../../substrate/get-balance';
import privateKey from '../../substrate/privateKey';
import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, getTokenOwner, setCollectionLimitsExpectSuccess, setCollectionSponsorExpectSuccess, transferExpectSuccess, transferFromExpectSuccess} from '../../util/helpers';
-import {collectionIdToAddress, contractHelpers, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, subToEth, subToEthLowercase, transferBalanceToEth} from '../util/helpers';
+import {collectionIdToAddress, contractHelpers, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, SponsoringMode, subToEth, subToEthLowercase, transferBalanceToEth} from '../util/helpers';
import {evmToAddress} from '@polkadot/util-crypto';
import nonFungibleAbi from '../nonFungibleAbi.json';
import fungibleAbi from '../fungibleAbi.json';
@@ -20,7 +20,7 @@
});
const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments:[matcherOwner]}).send({from: matcherOwner});
const helpers = contractHelpers(web3, matcherOwner);
- await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: 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);
@@ -147,7 +147,7 @@
const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments: [matcherOwner]}).send({from: matcherOwner, gas: 10000000});
await matcher.methods.setEscrow(escrow).send({from: matcherOwner});
const helpers = contractHelpers(web3, matcherOwner);
- await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: 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);
tests/src/eth/sponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/sponsoring.test.ts
+++ b/tests/src/eth/sponsoring.test.ts
@@ -1,6 +1,6 @@
import {expect} from 'chai';
import privateKey from '../substrate/privateKey';
-import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, transferBalanceToEth} from './util/helpers';
+import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode, transferBalanceToEth} from './util/helpers';
describe('EVM sponsoring', () => {
itWeb3('Fee is deducted from contract if sponsoring is enabled', async ({api, web3}) => {
@@ -18,7 +18,7 @@
await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(flipper.options.address, true).send({from: owner});
+ 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;
@@ -49,7 +49,7 @@
await helpers.methods.toggleAllowed(collector.options.address, caller, true).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(collector.options.address).call()).to.be.false;
- await helpers.methods.toggleSponsoring(collector.options.address, true).send({from: owner});
+ await helpers.methods.setSponsoringMode(collector.options.address, SponsoringMode.Allowlisted).send({from: owner});
await helpers.methods.setSponsoringRateLimit(collector.options.address, 0).send({from: owner});
expect(await helpers.methods.sponsoringEnabled(collector.options.address).call()).to.be.true;
tests/src/eth/util/contractHelpersAbi.jsondiffbeforeafterboth--- a/tests/src/eth/util/contractHelpersAbi.json
+++ b/tests/src/eth/util/contractHelpersAbi.json
@@ -147,6 +147,43 @@
"type": "address"
},
{
+ "internalType": "uint8",
+ "name": "mode",
+ "type": "uint8"
+ }
+ ],
+ "name": "setSponsoringMode",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ }
+ ],
+ "name": "sponsoringMode",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
"internalType": "uint32",
"name": "limit",
"type": "uint32"
@@ -176,4 +213,4 @@
"stateMutability": "view",
"type": "function"
}
-]
\ No newline at end of file
+]
tests/src/eth/util/helpers.tsdiffbeforeafterboth--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -21,6 +21,12 @@
export const GAS_ARGS = {gas: 2500000};
+export enum SponsoringMode {
+ Disabled = 0,
+ Allowlisted = 1,
+ Generous = 2,
+}
+
let web3Connected = false;
export async function usingWeb3<T>(cb: (web3: Web3) => Promise<T> | T): Promise<T> {
if (web3Connected) throw new Error('do not nest usingWeb3 calls');