--- a/.maintain/scripts/generate_sol.sh +++ b/.maintain/scripts/generate_sol.sh @@ -1,11 +1,14 @@ #!/bin/sh set -eu +PRETTIER_CONFIG="$(pwd)""/.prettierrc" + tmp=$(mktemp) cargo test --package $PACKAGE -- $NAME --exact --nocapture --ignored | tee $tmp raw=$(mktemp --suffix .sol) sed -n '/=== SNIP START ===/, /=== SNIP END ===/{ /=== SNIP START ===/! { /=== SNIP END ===/! p } }' $tmp > $raw + formatted=$(mktemp) -prettier --use-tabs $raw > $formatted +prettier --config $PRETTIER_CONFIG $raw > $formatted mv $formatted $OUTPUT --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +!**/*.sol --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "all", + "overrides": [ + { + "files": "*.sol", + "options": { + "singleQuote": false, + "printWidth": 120, + "explicitTypes": "always" + } + } + ] +} \ No newline at end of file --- a/Cargo.lock +++ b/Cargo.lock @@ -2324,7 +2324,7 @@ [[package]] name = "fc-consensus" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "async-trait", "fc-db", @@ -2343,7 +2343,7 @@ [[package]] name = "fc-db" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "fp-storage", "kvdb-rocksdb", @@ -2359,7 +2359,7 @@ [[package]] name = "fc-mapping-sync" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "fc-db", "fp-consensus", @@ -2376,7 +2376,7 @@ [[package]] name = "fc-rpc" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "ethereum", "ethereum-types", @@ -2416,7 +2416,7 @@ [[package]] name = "fc-rpc-core" version = "1.1.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "ethereum", "ethereum-types", @@ -2557,7 +2557,7 @@ [[package]] name = "fp-consensus" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "ethereum", "parity-scale-codec 3.1.5", @@ -2569,7 +2569,7 @@ [[package]] name = "fp-evm" version = "3.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "evm", "frame-support", @@ -2583,7 +2583,7 @@ [[package]] name = "fp-evm-mapping" version = "0.1.0" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "frame-support", "sp-core", @@ -2592,7 +2592,7 @@ [[package]] name = "fp-rpc" version = "3.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "ethereum", "ethereum-types", @@ -2609,7 +2609,7 @@ [[package]] name = "fp-self-contained" version = "1.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "ethereum", "frame-support", @@ -2625,7 +2625,7 @@ [[package]] name = "fp-storage" version = "2.0.0" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "parity-scale-codec 3.1.5", ] @@ -5543,7 +5543,7 @@ [[package]] name = "pallet-base-fee" version = "1.0.0" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "fp-evm", "frame-support", @@ -5758,7 +5758,7 @@ [[package]] name = "pallet-ethereum" version = "4.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "ethereum", "ethereum-types", @@ -5787,7 +5787,7 @@ [[package]] name = "pallet-evm" version = "6.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit#0ae36821312f4a26e7f96225f1b2cb16216cddf5" dependencies = [ "evm", "fp-evm", @@ -5842,6 +5842,7 @@ "pallet-common", "pallet-evm", "pallet-evm-coder-substrate", + "pallet-evm-transaction-payment", "parity-scale-codec 3.1.5", "scale-info", "sp-core", --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -19,4 +19,4 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-rpc = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -577,8 +577,8 @@ BlockNumber: Decode + Member + AtLeast32BitUnsigned, AccountId: Decode, C: 'static + ProvideRuntimeApi + HeaderBackend, + CrossAccountId: pallet_evm::account::CrossAccountId, C::Api: AppPromotionRuntimeApi, - CrossAccountId: pallet_evm::account::CrossAccountId, { pass_method!(total_staked(staker: Option) -> String => |v| v.to_string(), app_promotion_api); pass_method!(total_staked_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, String)> => --- /dev/null +++ b/doc/sponsoring-flow.drawio.svg @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + +
+
+
+ EVM calls +
+
+
+
+ + EVM calls + +
+
+ + + + +
+
+
+
+ Extrinsics +
+
+
+
+
+ + Extrinsics + +
+
+ + + + + + + + +
+
+
+
+ User +
+
+
+
+
+ + User + +
+
+ + + + + + +
+
+
+
+ Substrate +
+
+
+
+
+ + Substrate + +
+
+ + + + + + +
+
+
+
+ Ethereum +
+
+
+
+
+ + Ethereum + +
+
+ + + + + + +
+
+
+ pallet_evm:: +
+ TransactionValidityHack +
+
+
+
+ + pallet_evm::... + +
+
+ + + + + + +
+
+
+

+ + pallet_charge_evm_transaction:: +
+ Config::EvmSponsorshipHandler +
+
+

+
+
+
+
+ + pallet_charge_evm_tr... + +
+
+ + + + + + +
+
+
+ pallet_charge_evm_transaction:: +
+ BridgeSponsorshipHandler +
+
+
+
+ + pallet_charge_evm_transaction::... + +
+
+ + + + + + +
+
+
+ pallet_sponsoring:: +
+ ChargeTransactionPayment +
+
+
+
+
+ + pallet_sponsoring::... + +
+
+ + + + + +
+
+
+ tx.others +
+
+
+
+ + tx.others + +
+
+ + + + + +
+
+
+
+ tx.evm.call +
+
+
+
+
+ + tx.evm.call + +
+
+ + + + +
+
+
+
+ pallet_sponsoring:: +
+ Config::SponsorshipHandler +
+
+
+
+
+ + pallet_sponsoring::... + +
+
+ + + + +
+
+
+ SponsorshipHandler<CrossAccountId, (H160, Vec<u8>), CallContext> +
+
+
+
+ + SponsorshipHandler<CrossAccountId, (H160, Vec<u8>), CallContext> + +
+
+ + + + +
+
+
+ SponsorshipHandler<AccountId, Call, ()> +
+
+
+
+ + SponsorshipHandler<AccountId, Call, ()> + +
+
+
+ + + + + Viewer does not support full SVG 1.1 + + + +
--- /dev/null +++ b/doc/sponsoring.md @@ -0,0 +1,13 @@ +# Sponsoring + +![Sponsoring flow](./sponsoring-flow.drawio.svg) + +## Implementation + +If you need to add sponsoring for pallet call, you should implement `SponsorshipHandler`, see `UniqueSponsorshipHandler` for example. + +If you need to add sponsoring for EVM contract call, you should implement `SponsorshipHandler), CallContext>`, see `UniqueEthSponsorshipHandler` for example. + +## EVM bridging + +In case if Ethereum call is being called using substrate `evm.call` extrinsic, `BridgeSponsorshipHandler` is used to convert between two different `SponsorshipHandler` types --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -309,13 +309,13 @@ jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } tokio = { version = "1.19.2", features = ["time"] } -fc-rpc-core = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-consensus = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-mapping-sync = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-rpc = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-db = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-rpc = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-ethereum = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +fc-rpc-core = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-consensus = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-mapping-sync = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-rpc = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-db = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-rpc = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-ethereum = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } unique-rpc = { default-features = false, path = "../rpc" } app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false} --- a/node/rpc/Cargo.toml +++ b/node/rpc/Cargo.toml @@ -40,13 +40,13 @@ substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } tokio = { version = "1.19.2", features = ["macros", "sync"] } -pallet-ethereum = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-storage = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-rpc-core = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-db = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fc-mapping-sync = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-ethereum = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-storage = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-rpc-core = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-db = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fc-mapping-sync = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } pallet-common = { default-features = false, path = "../../pallets/common" } up-common = { path = "../../primitives/common" } --- a/pallets/app-promotion/Cargo.toml +++ b/pallets/app-promotion/Cargo.toml @@ -52,7 +52,7 @@ pallet-balances ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } pallet-timestamp ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } pallet-randomness-collective-flip ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm ={ default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm ={ default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } sp-std ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } --- a/pallets/common/Cargo.toml +++ b/pallets/common/Cargo.toml @@ -17,12 +17,12 @@ sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } evm-coder = { default-features = false, path = '../../crates/evm-coder' } ethereum = { version = "0.12.0", default-features = false } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } serde = { version = "1.0.130", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = [ --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -20,6 +20,7 @@ solidity_interface, solidity, ToLog, types::*, execution::{Result, Error}, + weight, }; pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId}; use pallet_evm_coder_substrate::dispatch_to_evm; @@ -31,8 +32,12 @@ use alloc::format; use crate::{ - Pallet, CollectionHandle, Config, CollectionProperties, - eth::{convert_cross_account_to_uint256, convert_uint256_to_cross_account}, + Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf, + eth::{ + convert_cross_account_to_uint256, convert_uint256_to_cross_account, + convert_cross_account_to_tuple, + }, + weights::WeightInfo, }; /// Events for ethereum collection helper. @@ -69,6 +74,7 @@ /// /// @param key Property key. /// @param value Propery value. + #[weight(>::set_collection_properties(1))] fn set_collection_property( &mut self, caller: caller, @@ -88,7 +94,10 @@ /// Delete collection property. /// /// @param key Property key. + #[weight(>::delete_collection_properties(1))] fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> { + self.consume_store_reads_and_writes(1, 1)?; + let caller = T::CrossAccountId::from_eth(caller); let key = >::from(key) .try_into() @@ -120,6 +129,8 @@ /// /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract. fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + check_is_owner_or_admin(caller, self)?; let sponsor = T::CrossAccountId::from_eth(sponsor); @@ -138,6 +149,8 @@ caller: caller, sponsor: uint256, ) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + check_is_owner_or_admin(caller, self)?; let sponsor = convert_uint256_to_cross_account::(sponsor); @@ -146,7 +159,7 @@ save(self) } - // /// Whether there is a pending sponsor. + /// Whether there is a pending sponsor. fn has_collection_pending_sponsor(&self) -> Result { Ok(matches!( self.collection.sponsorship, @@ -158,6 +171,8 @@ /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result { + self.consume_store_writes(1)?; + let caller = T::CrossAccountId::from_eth(caller); if !self .confirm_sponsorship(caller.as_sub()) @@ -170,6 +185,7 @@ /// Remove collection sponsor. fn remove_collection_sponsor(&mut self, caller: caller) -> Result { + self.consume_store_reads_and_writes(1, 1)?; check_is_owner_or_admin(caller, self)?; self.remove_sponsor().map_err(dispatch_to_evm::)?; save(self) @@ -206,6 +222,8 @@ /// @param value Value of the limit. #[solidity(rename_selector = "setCollectionLimit")] fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + check_is_owner_or_admin(caller, self)?; let mut limits = self.limits.clone(); @@ -249,6 +267,8 @@ /// @param value Value of the limit. #[solidity(rename_selector = "setCollectionLimit")] fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + check_is_owner_or_admin(caller, self)?; let mut limits = self.limits.clone(); @@ -275,7 +295,7 @@ } /// Get contract address. - fn contract_address(&self, _caller: caller) -> Result
{ + fn contract_address(&self) -> Result
{ Ok(crate::eth::collection_id_to_address(self.id)) } @@ -286,6 +306,8 @@ caller: caller, new_admin: uint256, ) -> Result { + self.consume_store_writes(2)?; + let caller = T::CrossAccountId::from_eth(caller); let new_admin = convert_uint256_to_cross_account::(new_admin); >::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::)?; @@ -299,6 +321,8 @@ caller: caller, admin: uint256, ) -> Result { + self.consume_store_writes(2)?; + let caller = T::CrossAccountId::from_eth(caller); let admin = convert_uint256_to_cross_account::(admin); >::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::)?; @@ -308,6 +332,8 @@ /// Add collection admin. /// @param newAdmin Address of the added administrator. fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result { + self.consume_store_writes(2)?; + let caller = T::CrossAccountId::from_eth(caller); let new_admin = T::CrossAccountId::from_eth(new_admin); >::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::)?; @@ -318,6 +344,8 @@ /// /// @param admin Address of the removed administrator. fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result { + self.consume_store_writes(2)?; + let caller = T::CrossAccountId::from_eth(caller); let admin = T::CrossAccountId::from_eth(admin); >::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::)?; @@ -329,6 +357,8 @@ /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' #[solidity(rename_selector = "setCollectionNesting")] fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + check_is_owner_or_admin(caller, self)?; let mut permissions = self.collection.permissions.clone(); @@ -358,6 +388,8 @@ enable: bool, collections: Vec
, ) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + if collections.is_empty() { return Err("no addresses provided".into()); } @@ -401,6 +433,8 @@ /// 0 for Normal /// 1 for AllowList fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + check_is_owner_or_admin(caller, self)?; let permissions = CollectionPermissions { access: Some(match mode { @@ -420,30 +454,78 @@ save(self) } + /// Checks that user allowed to operate with collection. + /// + /// @param user User address to check. + fn allowed(&self, user: address) -> Result { + Ok(Pallet::::allowed( + self.id, + T::CrossAccountId::from_eth(user), + )) + } + /// Add the user to the allowed list. /// /// @param user Address of a trusted user. fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result { + self.consume_store_writes(1)?; + let caller = T::CrossAccountId::from_eth(caller); let user = T::CrossAccountId::from_eth(user); >::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::)?; Ok(()) } + /// Add substrate user to allowed list. + /// + /// @param user User substrate address. + fn add_to_collection_allow_list_substrate( + &mut self, + caller: caller, + user: uint256, + ) -> Result { + self.consume_store_writes(1)?; + + let caller = T::CrossAccountId::from_eth(caller); + let user = convert_uint256_to_cross_account::(user); + Pallet::::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::)?; + Ok(()) + } + /// Remove the user from the allowed list. /// /// @param user Address of a removed user. fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result { + self.consume_store_writes(1)?; + let caller = T::CrossAccountId::from_eth(caller); let user = T::CrossAccountId::from_eth(user); >::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::)?; Ok(()) } + /// Remove substrate user from allowed list. + /// + /// @param user User substrate address. + fn remove_from_collection_allow_list_substrate( + &mut self, + caller: caller, + user: uint256, + ) -> Result { + self.consume_store_writes(1)?; + + let caller = T::CrossAccountId::from_eth(caller); + let user = convert_uint256_to_cross_account::(user); + Pallet::::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::)?; + Ok(()) + } + /// Switch permission for minting. /// /// @param mode Enable if "true". fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result { + self.consume_store_reads_and_writes(1, 1)?; + check_is_owner_or_admin(caller, self)?; let permissions = CollectionPermissions { mint_mode: Some(mode), @@ -481,7 +563,7 @@ /// Returns collection type /// /// @return `Fungible` or `NFT` or `ReFungible` - fn unique_collection_type(&mut self) -> Result { + fn unique_collection_type(&self) -> Result { let mode = match self.collection.mode { CollectionMode::Fungible(_) => "Fungible", CollectionMode::NFT => "NFT", @@ -490,11 +572,23 @@ Ok(mode.into()) } + /// Get collection owner. + /// + /// @return Tuble with sponsor address and his substrate mirror. + /// If address is canonical then substrate mirror is zero and vice versa. + fn collection_owner(&self) -> Result<(address, uint256)> { + Ok(convert_cross_account_to_tuple::( + &T::CrossAccountId::from_sub(self.owner.clone()), + )) + } + /// Changes collection owner to another account /// /// @dev Owner can be changed only by current owner /// @param newOwner new owner account fn set_owner(&mut self, caller: caller, new_owner: address) -> Result { + self.consume_store_writes(1)?; + let caller = T::CrossAccountId::from_eth(caller); let new_owner = T::CrossAccountId::from_eth(new_owner); self.set_owner_internal(caller, new_owner) @@ -506,13 +600,25 @@ /// @dev Owner can be changed only by current owner /// @param newOwner new owner substrate account fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result { + self.consume_store_writes(1)?; + let caller = T::CrossAccountId::from_eth(caller); let new_owner = convert_uint256_to_cross_account::(new_owner); self.set_owner_internal(caller, new_owner) .map_err(dispatch_to_evm::) } + + // TODO: need implement AbiWriter for &Vec + // fn collection_admins(&self) -> Result> { + // let result = pallet_common::IsAdmin::::iter_prefix((self.id,)) + // .map(|(admin, _)| pallet_common::eth::convert_cross_account_to_tuple::(&admin)) + // .collect(); + // Ok(result) + // } } +/// ### Note +/// Do not forget to add: `self.consume_store_reads(1)?;` fn check_is_owner_or_admin( caller: caller, collection: &CollectionHandle, @@ -524,9 +630,9 @@ Ok(caller) } +/// ### Note +/// Do not forget to add: `self.consume_store_writes(1)?;` fn save(collection: &CollectionHandle) -> Result { - // TODO possibly delete for the lack of transaction - collection.consume_store_writes(1)?; collection .check_is_internal() .map_err(dispatch_to_evm::)?; --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -16,7 +16,7 @@ //! The module contains a number of functions for converting and checking ethereum identifiers. -use evm_coder::types::uint256; +use evm_coder::types::{uint256, address}; pub use pallet_evm::account::{Config, CrossAccountId}; use sp_core::H160; use up_data_structs::CollectionId; @@ -69,3 +69,19 @@ let account_id = T::AccountId::from(new_admin_arr); T::CrossAccountId::from_sub(account_id) } + +/// Convert `CrossAccountId` to `(address, uint256)`. +pub fn convert_cross_account_to_tuple( + cross_account_id: &T::CrossAccountId, +) -> (address, uint256) +where + T::AccountId: AsRef<[u8; 32]>, +{ + if cross_account_id.is_canonical_substrate() { + let sub = convert_cross_account_to_uint256::(cross_account_id); + (Default::default(), sub) + } else { + let eth = *cross_account_id.as_eth(); + (eth, Default::default()) + } +} --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -112,7 +112,6 @@ RmrkBoundedTheme, RmrkNftChild, CollectionPermissions, - SchemaVersion, }; pub use pallet::*; @@ -202,6 +201,21 @@ )) } + /// Consume gas for reading and writing. + pub fn consume_store_reads_and_writes( + &self, + reads: u64, + writes: u64, + ) -> evm_coder::execution::Result<()> { + let weight = ::DbWeight::get(); + let reads = weight.read.saturating_mul(reads); + let writes = weight.read.saturating_mul(writes); + self.recorder + .consume_gas(T::GasWeightMapping::weight_to_gas( + reads.saturating_add(writes), + )) + } + /// Save collection to storage. pub fn save(&self) -> DispatchResult { >::insert(self.id, &self.collection); @@ -310,6 +324,8 @@ } /// Changes collection owner to another account + /// #### Store read/writes + /// 1 writes fn set_owner_internal( &mut self, caller: T::CrossAccountId, @@ -1292,6 +1308,8 @@ } /// Toggle `user` participation in the `collection`'s allow list. + /// #### Store read/writes + /// 1 writes pub fn toggle_allowlist( collection: &CollectionHandle, sender: &T::CrossAccountId, @@ -1312,6 +1330,8 @@ } /// Toggle `user` participation in the `collection`'s admin list. + /// #### Store read/writes + /// 2 writes pub fn toggle_admin( collection: &CollectionHandle, sender: &T::CrossAccountId, --- a/pallets/configuration/Cargo.toml +++ b/pallets/configuration/Cargo.toml @@ -16,7 +16,7 @@ sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-arithmetic = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } smallvec = "1.6.1" [features] --- a/pallets/evm-coder-substrate/Cargo.toml +++ b/pallets/evm-coder-substrate/Cargo.toml @@ -12,8 +12,8 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } ethereum = { version = "0.12.0", default-features = false } evm-coder = { default-features = false, path = "../../crates/evm-coder" } -pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } --- a/pallets/evm-contract-helpers/Cargo.toml +++ b/pallets/evm-contract-helpers/Cargo.toml @@ -19,14 +19,15 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } # Unique -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.27" } # Locals evm-coder = { default-features = false, path = '../../crates/evm-coder' } pallet-common = { default-features = false, path = '../../pallets/common' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } +pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' } up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = [ 'serde1', ] } --- a/pallets/evm-contract-helpers/src/eth.rs +++ b/pallets/evm-contract-helpers/src/eth.rs @@ -20,16 +20,17 @@ use evm_coder::{ abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog, }; -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 pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder, dispatch_to_evm}; +use pallet_evm_transaction_payment::CallContext; +use sp_core::{H160, U256}; use up_data_structs::SponsorshipState; use crate::{ - AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringRateLimit, SponsoringModeT, - Sponsoring, + AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringFeeLimit, + SponsoringRateLimit, SponsoringModeT, Sponsoring, }; use frame_support::traits::Get; use up_sponsorship::SponsorshipHandler; @@ -171,14 +172,9 @@ fn get_sponsor(&self, contract_address: address) -> Result<(address, uint256)> { let sponsor = Pallet::::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::(&sponsor); - (Default::default(), sponsor) - } else { - let sponsor = *sponsor.as_eth(); - (sponsor, Default::default()) - }; - Ok(result) + Ok(pallet_common::eth::convert_cross_account_to_tuple::( + &sponsor, + )) } /// Check tat contract has confirmed sponsor. @@ -222,9 +218,11 @@ } /// Get current contract sponsoring rate limit - /// @param contractAddress Contract to get sponsoring mode of + /// @param contractAddress Contract to get sponsoring rate limit of /// @return uint32 Amount of blocks between two sponsored transactions fn get_sponsoring_rate_limit(&self, contract_address: address) -> Result { + self.recorder().consume_sload()?; + Ok(>::get(contract_address) .try_into() .map_err(|_| "rate limit > u32::MAX")?) @@ -250,6 +248,37 @@ Ok(()) } + /// Set contract sponsoring fee limit + /// @dev Sponsoring fee limit - is maximum fee that could be spent by + /// single transaction + /// @param contractAddress Contract to change sponsoring fee limit of + /// @param feeLimit Fee limit + /// @dev Only contract owner can change this setting + fn set_sponsoring_fee_limit( + &mut self, + caller: caller, + contract_address: address, + fee_limit: uint256, + ) -> Result { + self.recorder().consume_sload()?; + self.recorder().consume_sstore()?; + + >::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::)?; + >::set_sponsoring_fee_limit(contract_address, fee_limit.into()) + .map_err(dispatch_to_evm::)?; + Ok(()) + } + + /// Get current contract sponsoring fee limit + /// @param contractAddress Contract to get sponsoring fee limit of + /// @return uint256 Maximum amount of fee that could be spent by single + /// transaction + fn get_sponsoring_fee_limit(&self, contract_address: address) -> Result { + self.recorder().consume_sload()?; + + Ok(get_sponsoring_fee_limit::(contract_address)) + } + /// Is specified user present in contract allow list /// @dev Contract owner always implicitly included /// @param contractAddress Contract to check allowlist of @@ -363,23 +392,26 @@ /// Bridge to pallet-sponsoring pub struct HelpersContractSponsoring(PhantomData<*const T>); -impl SponsorshipHandler)> +impl SponsorshipHandler for HelpersContractSponsoring { - fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec)) -> Option { - let (contract_address, _) = call; - let mode = >::sponsoring_mode(*contract_address); + fn get_sponsor( + who: &T::CrossAccountId, + call_context: &CallContext, + ) -> Option { + let contract_address = call_context.contract_address; + let mode = >::sponsoring_mode(contract_address); if mode == SponsoringModeT::Disabled { return None; } - let sponsor = match >::get_sponsor(*contract_address) { + let sponsor = match >::get_sponsor(contract_address) { Some(sponsor) => sponsor, None => return None, }; if mode == SponsoringModeT::Allowlisted - && !>::allowed(*contract_address, *who.as_eth()) + && !>::allowed(contract_address, *who.as_eth()) { return None; } @@ -394,11 +426,24 @@ } } + let sponsored_fee_limit = get_sponsoring_fee_limit::(contract_address); + + if call_context.max_fee > sponsored_fee_limit { + return None; + } + >::insert(contract_address, who.as_eth(), block_number); Some(sponsor) } } +fn get_sponsoring_fee_limit(contract_address: address) -> uint256 { + >::get(contract_address) + .get(&0xffffffff) + .cloned() + .unwrap_or(U256::MAX) +} + generate_stubgen!(contract_helpers_impl, ContractHelpersCall<()>, true); generate_stubgen!(contract_helpers_iface, ContractHelpersCall<()>, false); --- a/pallets/evm-contract-helpers/src/lib.rs +++ b/pallets/evm-contract-helpers/src/lib.rs @@ -22,15 +22,19 @@ pub use pallet::*; pub use eth::*; use scale_info::TypeInfo; +use frame_support::storage::bounded_btree_map::BoundedBTreeMap; pub mod eth; +/// Maximum number of methods per contract that could have fee limit +pub const MAX_FEE_LIMITED_METHODS: u32 = 5; + #[frame_support::pallet] pub mod pallet { pub use super::*; use crate::eth::ContractHelpersEvents; use frame_support::pallet_prelude::*; use pallet_evm_coder_substrate::DispatchResult; - use sp_core::H160; + use sp_core::{H160, U256}; use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use up_data_structs::SponsorshipState; use evm_coder::ToLog; @@ -57,6 +61,9 @@ /// No pending sponsor for contract. NoPendingSponsor, + + /// Number of methods that sponsored limit is defined for exceeds maximum. + TooManyMethodsHaveSponsoredLimit, } #[pallet::pallet] @@ -118,6 +125,14 @@ /// * **Key2** - sponsored user address. /// * **Value** - last sponsored block number. #[pallet::storage] + pub(super) type SponsoringFeeLimit = StorageMap< + Hasher = Twox128, + Key = H160, + Value = BoundedBTreeMap>, + QueryKind = ValueQuery, + >; + + #[pallet::storage] pub(super) type SponsorBasket = StorageDoubleMap< Hasher1 = Twox128, Key1 = H160, @@ -355,6 +370,16 @@ >::insert(contract, rate_limit); } + /// Set maximum for gas limit of transaction + pub fn set_sponsoring_fee_limit(contract: H160, fee_limit: U256) -> DispatchResult { + >::try_mutate(contract, |limits_map| { + limits_map + .try_insert(0xffffffff, fee_limit) + .map_err(|_| >::TooManyMethodsHaveSponsoredLimit) + })?; + Ok(()) + } + /// Is user added to allowlist, or he is owner of specified contract pub fn allowed(contract: H160, user: H160) -> bool { >::get(&contract, &user) || >::get(&contract) == user --- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol +++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol @@ -32,7 +32,7 @@ } /// @title Magic contract, which allows users to reconfigure other contracts -/// @dev the ERC-165 identifier for this interface is 0xd77fab70 +/// @dev the ERC-165 identifier for this interface is 0x172cb4fb contract ContractHelpers is Dummy, ERC165, ContractHelpersEvents { /// Get user, which deployed specified contract /// @dev May return zero address in case if contract is deployed @@ -171,7 +171,7 @@ } /// Get current contract sponsoring rate limit - /// @param contractAddress Contract to get sponsoring mode of + /// @param contractAddress Contract to get sponsoring rate limit of /// @return uint32 Amount of blocks between two sponsored transactions /// @dev EVM selector for this function is: 0x610cfabd, /// or in textual repr: getSponsoringRateLimit(address) @@ -203,6 +203,40 @@ dummy = 0; } + /// Set contract sponsoring fee limit + /// @dev Sponsoring fee limit - is maximum fee that could be spent by + /// single transaction + /// @param contractAddress Contract to change sponsoring fee limit of + /// @param feeLimit Fee limit + /// @dev Only contract owner can change this setting + /// @dev EVM selector for this function is: 0x03aed665, + /// or in textual repr: setSponsoringFeeLimit(address,uint256) + function setSponsoringFeeLimit(address contractAddress, uint256 feeLimit) + public + { + require(false, stub_error); + contractAddress; + feeLimit; + dummy = 0; + } + + /// Get current contract sponsoring fee limit + /// @param contractAddress Contract to get sponsoring fee limit of + /// @return uint256 Maximum amount of fee that could be spent by single + /// transaction + /// @dev EVM selector for this function is: 0xc3fdc9ee, + /// or in textual repr: getSponsoringFeeLimit(address) + function getSponsoringFeeLimit(address contractAddress) + public + view + returns (uint256) + { + require(false, stub_error); + contractAddress; + dummy; + return 0; + } + /// Is specified user present in contract allow list /// @dev Contract owner always implicitly included /// @param contractAddress Contract to check allowlist of --- a/pallets/evm-migration/Cargo.toml +++ b/pallets/evm-migration/Cargo.toml @@ -15,8 +15,8 @@ sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-io = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } [dependencies.codec] default-features = false --- a/pallets/evm-transaction-payment/Cargo.toml +++ b/pallets/evm-transaction-payment/Cargo.toml @@ -14,11 +14,11 @@ sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-io = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.27" } -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } [dependencies.codec] default-features = false --- a/pallets/evm-transaction-payment/src/lib.rs +++ b/pallets/evm-transaction-payment/src/lib.rs @@ -22,8 +22,8 @@ use fp_evm::WithdrawReason; use frame_support::traits::IsSubType; pub use pallet::*; -use pallet_evm::{EnsureAddressOrigin, account::CrossAccountId}; -use sp_core::H160; +use pallet_evm::{account::CrossAccountId, EnsureAddressOrigin}; +use sp_core::{H160, U256}; use sp_runtime::{TransactionOutcome, DispatchError}; use up_sponsorship::SponsorshipHandler; @@ -33,10 +33,20 @@ use sp_std::vec::Vec; + /// Contains call data + pub struct CallContext { + /// Contract address + pub contract_address: H160, + /// Transaction data + pub input: Vec, + /// Max fee for transaction - gasLimit * gasPrice + pub max_fee: U256, + } + #[pallet::config] pub trait Config: frame_system::Config + pallet_evm::account::Config { /// Loosly-coupled handlers for evm call sponsoring - type EvmSponsorshipHandler: SponsorshipHandler)>; + type EvmSponsorshipHandler: SponsorshipHandler; } #[pallet::pallet] @@ -47,11 +57,20 @@ /// Implements [`fp_evm::TransactionValidityHack`], which provides sponsor address to pallet-evm pub struct TransactionValidityHack(PhantomData<*const T>); impl fp_evm::TransactionValidityHack for TransactionValidityHack { - fn who_pays_fee(origin: H160, reason: &WithdrawReason) -> Option { + fn who_pays_fee( + origin: H160, + max_fee: U256, + reason: &WithdrawReason, + ) -> Option { match reason { WithdrawReason::Call { target, input } => { let origin_sub = T::CrossAccountId::from_eth(origin); - T::EvmSponsorshipHandler::get_sponsor(&origin_sub, &(*target, input.clone())) + let call_context = CallContext { + contract_address: *target, + input: input.clone(), + max_fee, + }; + T::EvmSponsorshipHandler::get_sponsor(&origin_sub, &call_context) } _ => None, } @@ -71,6 +90,8 @@ source, target, input, + gas_limit, + max_fee_per_gas, .. } => { let _ = T::CallOrigin::ensure_address_origin( @@ -79,11 +100,17 @@ ) .ok()?; let who = T::CrossAccountId::from_sub(who.clone()); + let max_fee = max_fee_per_gas.saturating_mul((*gas_limit).into()); + let call_context = CallContext { + contract_address: *target, + input: input.clone(), + max_fee, + }; // Effects from EvmSponsorshipHandler are applied by pallet_evm::runner // TODO: Should we implement simulation mode (test, but do not apply effects) in `up-sponsorship`? let sponsor = frame_support::storage::with_transaction(|| { TransactionOutcome::Rollback(Ok::<_, DispatchError>( - T::EvmSponsorshipHandler::get_sponsor(&who, &(*target, input.clone())), + T::EvmSponsorshipHandler::get_sponsor(&who, &call_context), )) }) // FIXME: it may fail with DispatchError in case of depth limit --- a/pallets/fungible/Cargo.toml +++ b/pallets/fungible/Cargo.toml @@ -23,7 +23,7 @@ pallet-evm-coder-substrate = { default-features = false, path = '../evm-coder-substrate' } ethereum = { version = "0.12.0", default-features = false } frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -22,7 +22,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xe54be640 +/// @dev the ERC-165 identifier for this interface is 0x9f70d4e0 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -255,6 +255,18 @@ dummy = 0; } + /// Checks that user allowed to operate with collection. + /// + /// @param user User address to check. + /// @dev EVM selector for this function is: 0xd63a8e11, + /// or in textual repr: allowed(address) + function allowed(address user) public view returns (bool) { + require(false, stub_error); + user; + dummy; + return false; + } + /// Add the user to the allowed list. /// /// @param user Address of a trusted user. @@ -266,6 +278,17 @@ dummy = 0; } + /// Add substrate user to allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xd06ad267, + /// or in textual repr: addToCollectionAllowListSubstrate(uint256) + function addToCollectionAllowListSubstrate(uint256 user) public { + require(false, stub_error); + user; + dummy = 0; + } + /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -277,6 +300,17 @@ dummy = 0; } + /// Remove substrate user from allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xa31913ed, + /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) + function removeFromCollectionAllowListSubstrate(uint256 user) public { + require(false, stub_error); + user; + dummy = 0; + } + /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -325,6 +359,18 @@ return ""; } + /// Get collection owner. + /// + /// @return Tuble with sponsor address and his substrate mirror. + /// If address is canonical then substrate mirror is zero and vice versa. + /// @dev EVM selector for this function is: 0xdf727d3b, + /// or in textual repr: collectionOwner() + function collectionOwner() public view returns (Tuple6 memory) { + require(false, stub_error); + dummy; + return Tuple6(0x0000000000000000000000000000000000000000, 0); + } + /// Changes collection owner to another account /// /// @dev Owner can be changed only by current owner --- a/pallets/nonfungible/Cargo.toml +++ b/pallets/nonfungible/Cargo.toml @@ -16,7 +16,7 @@ sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } pallet-common = { default-features = false, path = '../common' } pallet-structure = { default-features = false, path = '../structure' } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -99,7 +99,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xe54be640 +/// @dev the ERC-165 identifier for this interface is 0x9f70d4e0 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -332,6 +332,18 @@ dummy = 0; } + /// Checks that user allowed to operate with collection. + /// + /// @param user User address to check. + /// @dev EVM selector for this function is: 0xd63a8e11, + /// or in textual repr: allowed(address) + function allowed(address user) public view returns (bool) { + require(false, stub_error); + user; + dummy; + return false; + } + /// Add the user to the allowed list. /// /// @param user Address of a trusted user. @@ -343,6 +355,17 @@ dummy = 0; } + /// Add substrate user to allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xd06ad267, + /// or in textual repr: addToCollectionAllowListSubstrate(uint256) + function addToCollectionAllowListSubstrate(uint256 user) public { + require(false, stub_error); + user; + dummy = 0; + } + /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -354,6 +377,17 @@ dummy = 0; } + /// Remove substrate user from allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xa31913ed, + /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) + function removeFromCollectionAllowListSubstrate(uint256 user) public { + require(false, stub_error); + user; + dummy = 0; + } + /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -402,6 +436,18 @@ return ""; } + /// Get collection owner. + /// + /// @return Tuble with sponsor address and his substrate mirror. + /// If address is canonical then substrate mirror is zero and vice versa. + /// @dev EVM selector for this function is: 0xdf727d3b, + /// or in textual repr: collectionOwner() + function collectionOwner() public view returns (Tuple17 memory) { + require(false, stub_error); + dummy; + return Tuple17(0x0000000000000000000000000000000000000000, 0); + } + /// Changes collection owner to another account /// /// @dev Owner can be changed only by current owner --- a/pallets/proxy-rmrk-core/Cargo.toml +++ b/pallets/proxy-rmrk-core/Cargo.toml @@ -20,7 +20,7 @@ pallet-nonfungible = { default-features = false, path = "../../pallets/nonfungible" } pallet-structure = { default-features = false, path = "../../pallets/structure" } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } rmrk-traits = { default-features = false, path = "../../primitives/rmrk-traits" } scale-info = { version = "2.0.1", default-features = false, features = [ --- a/pallets/proxy-rmrk-equip/Cargo.toml +++ b/pallets/proxy-rmrk-equip/Cargo.toml @@ -19,7 +19,7 @@ pallet-common = { default-features = false, path = '../common' } pallet-nonfungible = { default-features = false, path = "../../pallets/nonfungible" } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } rmrk-traits = { default-features = false, path = "../../primitives/rmrk-traits" } scale-info = { version = "2.0.1", default-features = false, features = [ --- a/pallets/refungible/Cargo.toml +++ b/pallets/refungible/Cargo.toml @@ -16,7 +16,7 @@ sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } evm-coder = { default-features = false, path = '../../crates/evm-coder' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } pallet-common = { default-features = false, path = '../common' } --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -99,7 +99,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xe54be640 +/// @dev the ERC-165 identifier for this interface is 0x9f70d4e0 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -332,6 +332,18 @@ dummy = 0; } + /// Checks that user allowed to operate with collection. + /// + /// @param user User address to check. + /// @dev EVM selector for this function is: 0xd63a8e11, + /// or in textual repr: allowed(address) + function allowed(address user) public view returns (bool) { + require(false, stub_error); + user; + dummy; + return false; + } + /// Add the user to the allowed list. /// /// @param user Address of a trusted user. @@ -343,6 +355,17 @@ dummy = 0; } + /// Add substrate user to allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xd06ad267, + /// or in textual repr: addToCollectionAllowListSubstrate(uint256) + function addToCollectionAllowListSubstrate(uint256 user) public { + require(false, stub_error); + user; + dummy = 0; + } + /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -354,6 +377,17 @@ dummy = 0; } + /// Remove substrate user from allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xa31913ed, + /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) + function removeFromCollectionAllowListSubstrate(uint256 user) public { + require(false, stub_error); + user; + dummy = 0; + } + /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -402,6 +436,18 @@ return ""; } + /// Get collection owner. + /// + /// @return Tuble with sponsor address and his substrate mirror. + /// If address is canonical then substrate mirror is zero and vice versa. + /// @dev EVM selector for this function is: 0xdf727d3b, + /// or in textual repr: collectionOwner() + function collectionOwner() public view returns (Tuple17 memory) { + require(false, stub_error); + dummy; + return Tuple17(0x0000000000000000000000000000000000000000, 0); + } + /// Changes collection owner to another account /// /// @dev Owner can be changed only by current owner --- a/pallets/structure/Cargo.toml +++ b/pallets/structure/Cargo.toml @@ -16,7 +16,7 @@ "derive", ] } up-data-structs = { path = "../../primitives/data-structs", default-features = false } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } [features] default = ["std"] --- a/pallets/unique/CHANGELOG.md +++ b/pallets/unique/CHANGELOG.md @@ -4,6 +4,11 @@ +## [v0.2.0] 2022-09-13 + +### Changes +- Change **collectionHelper** method `createRefungibleCollection` to `createRFTCollection`, + ## [v0.1.4] 2022-09-05 ### Added --- a/pallets/unique/Cargo.toml +++ b/pallets/unique/Cargo.toml @@ -9,7 +9,7 @@ license = 'GPLv3' name = 'pallet-unique' repository = 'https://github.com/UniqueNetwork/unique-chain' -version = "0.1.4" +version = "0.2.0" [package.metadata.docs.rs] targets = ['x86_64-unknown-linux-gnu'] @@ -98,7 +98,7 @@ scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } pallet-common = { default-features = false, path = "../common" } evm-coder = { default-features = false, path = '../../crates/evm-coder' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -21,25 +21,23 @@ use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*}; use frame_support::traits::Get; use pallet_common::{ - CollectionById, CollectionHandle, + CollectionById, dispatch::CollectionDispatch, erc::{ CollectionHelpersEvents, static_property::{key, value as property_value}, }, - Pallet as PalletCommon, }; use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder}; use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult}; -use pallet_evm_coder_substrate::dispatch_to_evm; use up_data_structs::{ CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData, - CollectionMode, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, + CollectionMode, PropertyValue, }; use crate::{Config, SelfWeightOf, weights::WeightInfo}; -use sp_std::{vec, vec::Vec}; +use sp_std::vec::Vec; use alloc::format; /// See [`CollectionHelpersCall`] @@ -245,6 +243,7 @@ } #[weight(>::create_collection())] + #[solidity(rename_selector = "createRFTCollection")] fn create_refungible_collection( &mut self, caller: caller, --- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol +++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol @@ -10,11 +10,7 @@ } contract ERC165 is Dummy { - function supportsInterface(bytes4 interfaceID) - external - view - returns (bool) - { + function supportsInterface(bytes4 interfaceID) external view returns (bool) { require(false, stub_error); interfaceID; return true; @@ -23,14 +19,11 @@ /// @dev inlined interface contract CollectionHelpersEvents { - event CollectionCreated( - address indexed owner, - address indexed collectionId - ); + event CollectionCreated(address indexed owner, address indexed collectionId); } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0x675f3074 +/// @dev the ERC-165 identifier for this interface is 0x88ee8ef1 contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { /// Create an NFT collection /// @param name Name of the collection @@ -69,9 +62,9 @@ return 0x0000000000000000000000000000000000000000; } - /// @dev EVM selector for this function is: 0x44a68ad5, - /// or in textual repr: createRefungibleCollection(string,string,string) - function createRefungibleCollection( + /// @dev EVM selector for this function is: 0xab173450, + /// or in textual repr: createRFTCollection(string,string,string) + function createRFTCollection( string memory name, string memory description, string memory tokenPrefix @@ -106,11 +99,7 @@ /// @return bool Does the collection exist? /// @dev EVM selector for this function is: 0xc3de1494, /// or in textual repr: isCollectionExist(address) - function isCollectionExist(address collectionAddress) - public - view - returns (bool) - { + function isCollectionExist(address collectionAddress) public view returns (bool) { require(false, stub_error); collectionAddress; dummy; --- a/primitives/app_promotion_rpc/Cargo.toml +++ b/primitives/app_promotion_rpc/Cargo.toml @@ -14,7 +14,7 @@ sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-api = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } [features] default = ["std"] --- a/primitives/common/Cargo.toml +++ b/primitives/common/Cargo.toml @@ -48,9 +48,9 @@ [dependencies.fp-rpc] default-features = false git = "https://github.com/uniquenetwork/frontier" -branch = "unique-polkadot-v0.9.27" +branch = "unique-polkadot-v0.9.27-fee-limit" [dependencies.pallet-evm] default-features = false git = "https://github.com/uniquenetwork/frontier" -branch = "unique-polkadot-v0.9.27" +branch = "unique-polkadot-v0.9.27-fee-limit" --- a/primitives/data-structs/Cargo.toml +++ b/primitives/data-structs/Cargo.toml @@ -25,7 +25,7 @@ sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } derivative = { version = "2.2.0", features = ["use_core"] } struct-versioning = { path = "../../crates/struct-versioning" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } rmrk-traits = { default-features = false, path = "../rmrk-traits" } [features] --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -14,7 +14,7 @@ sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-api = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } [features] default = ["std"] --- a/runtime/common/config/sponsoring.rs +++ b/runtime/common/config/sponsoring.rs @@ -14,15 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -use frame_support::parameter_types; use crate::{ runtime_common::{sponsoring::UniqueSponsorshipHandler}, Runtime, }; -use up_common::{types::BlockNumber, constants::*}; +use frame_support::parameter_types; +use sp_core::U256; +use up_common::{constants::*, types::BlockNumber}; parameter_types! { pub const DefaultSponsoringRateLimit: BlockNumber = 1 * DAYS; + pub const DefaultSponsoringFeeLimit: U256 = U256::MAX; } type SponsorshipHandler = ( --- a/runtime/common/ethereum/sponsoring.rs +++ b/runtime/common/ethereum/sponsoring.rs @@ -16,27 +16,30 @@ //! Implements EVM sponsoring logic via TransactionValidityHack +use core::{convert::TryInto, marker::PhantomData}; use evm_coder::{Call, abi::AbiReader}; use pallet_common::{CollectionHandle, eth::map_eth_to_id}; -use sp_core::H160; +use pallet_evm::account::CrossAccountId; +use pallet_evm_transaction_payment::CallContext; +use pallet_nonfungible::{ + Config as NonfungibleConfig, + erc::{ + UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, + TokenPropertiesCall, + }, +}; +use pallet_fungible::{ + Config as FungibleConfig, + erc::{UniqueFungibleCall, ERC20Call}, +}; +use pallet_refungible::Config as RefungibleConfig; +use pallet_unique::Config as UniqueConfig; use sp_std::prelude::*; +use up_data_structs::{CollectionMode, CreateItemData, CreateNftData, TokenId}; use up_sponsorship::SponsorshipHandler; -use core::marker::PhantomData; -use core::convert::TryInto; -use pallet_evm::account::CrossAccountId; -use up_data_structs::{TokenId, CreateItemData, CreateNftData, CollectionMode}; -use pallet_unique::Config as UniqueConfig; use crate::{Runtime, runtime_common::sponsoring::*}; -use pallet_nonfungible::erc::{ - UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, TokenPropertiesCall, -}; -use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call}; -use pallet_fungible::Config as FungibleConfig; -use pallet_nonfungible::Config as NonfungibleConfig; -use pallet_refungible::Config as RefungibleConfig; - pub type EvmSponsorshipHandler = ( UniqueEthSponsorshipHandler, pallet_evm_contract_helpers::HelpersContractSponsoring, @@ -44,13 +47,16 @@ pub struct UniqueEthSponsorshipHandler(PhantomData<*const T>); impl - SponsorshipHandler)> for UniqueEthSponsorshipHandler + SponsorshipHandler for UniqueEthSponsorshipHandler { - fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec)) -> Option { - let collection_id = map_eth_to_id(&call.0)?; + fn get_sponsor( + who: &T::CrossAccountId, + call_context: &CallContext, + ) -> Option { + let collection_id = map_eth_to_id(&call_context.contract_address)?; let collection = >::new(collection_id)?; let sponsor = collection.sponsorship.sponsor()?.clone(); - let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?; + let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?; Some(T::CrossAccountId::from_sub(match &collection.mode { CollectionMode::NFT => { let call = >::parse(method_id, &mut reader).ok()??; --- a/runtime/opal/Cargo.toml +++ b/runtime/opal/Cargo.toml @@ -441,7 +441,7 @@ up-rpc = { path = "../../primitives/rpc", default-features = false } app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false} rmrk-rpc = { path = "../../primitives/rmrk-rpc", default-features = false } -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } pallet-inflation = { path = '../../pallets/inflation', default-features = false } pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false } up-data-structs = { path = '../../primitives/data-structs', default-features = false } @@ -460,11 +460,11 @@ pallet-evm-contract-helpers = { path = '../../pallets/evm-contract-helpers', default-features = false } pallet-evm-transaction-payment = { path = '../../pallets/evm-transaction-payment', default-features = false } pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } evm-coder = { default-features = false, path = '../../crates/evm-coder' } up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.27' } pallet-foreign-assets = { default-features = false, path = "../../pallets/foreign-assets" } --- a/runtime/quartz/Cargo.toml +++ b/runtime/quartz/Cargo.toml @@ -446,7 +446,7 @@ pallet-unique = { path = '../../pallets/unique', default-features = false } up-rpc = { path = "../../primitives/rpc", default-features = false } app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false} -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } pallet-inflation = { path = '../../pallets/inflation', default-features = false } pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false } up-data-structs = { path = '../../primitives/data-structs', default-features = false } @@ -465,11 +465,11 @@ pallet-evm-contract-helpers = { path = '../../pallets/evm-contract-helpers', default-features = false } pallet-evm-transaction-payment = { path = '../../pallets/evm-transaction-payment', default-features = false } pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } evm-coder = { default-features = false, path = '../../crates/evm-coder' } up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.27' } pallet-foreign-assets = { default-features = false, path = "../../pallets/foreign-assets" } --- a/runtime/tests/Cargo.toml +++ b/runtime/tests/Cargo.toml @@ -16,7 +16,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } @@ -25,8 +25,8 @@ pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } pallet-common = { path = '../../pallets/common' } pallet-structure = { path = '../../pallets/structure' } @@ -43,4 +43,4 @@ scale-info = "*" evm-coder = { default-features = false, path = '../../crates/evm-coder' } -up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.27' } +up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.27" } --- a/runtime/unique/Cargo.toml +++ b/runtime/unique/Cargo.toml @@ -456,12 +456,12 @@ pallet-evm-contract-helpers = { path = '../../pallets/evm-contract-helpers', default-features = false } pallet-evm-transaction-payment = { path = '../../pallets/evm-transaction-payment', default-features = false } pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" } -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" } evm-coder = { default-features = false, path = '../../crates/evm-coder' } up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.27' } pallet-foreign-assets = { default-features = false, path = "../../pallets/foreign-assets" } --- a/tests/src/eth/allowlist.test.ts +++ b/tests/src/eth/allowlist.test.ts @@ -14,10 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +import {IKeyringPair} from '@polkadot/types/types'; import {expect} from 'chai'; -import {contractHelpers, createEthAccountWithBalance, deployFlipper, itWeb3} from './util/helpers'; +import {isAllowlisted, normalizeAccountId} from '../util/helpers'; +import { + contractHelpers, + createEthAccount, + createEthAccountWithBalance, + deployFlipper, + evmCollection, + evmCollectionHelpers, + getCollectionAddressFromResult, + itWeb3, +} from './util/helpers'; +import {itEth, usingEthPlaygrounds} from './util/playgrounds'; -describe('EVM allowlist', () => { +describe('EVM contract allowlist', () => { itWeb3('Contract allowlist can be toggled', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const flipper = await deployFlipper(web3, owner); @@ -58,3 +70,79 @@ expect(await flipper.methods.getValue().call()).to.be.false; }); }); + +describe('EVM collection allowlist', () => { + let donor: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (_helper, privateKey) => { + donor = privateKey('//Alice'); + }); + }); + + itEth('Collection allowlist can be added and removed by [eth] address', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const user = helper.eth.createAccount(); + + const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false; + await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner}); + expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true; + + await collectionEvm.methods.removeFromCollectionAllowList(user).send({from: owner}); + expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false; + }); + + itEth('Collection allowlist can be added and removed by [sub] address', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const user = donor; + + const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + + await collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + }); + + itEth('Collection allowlist can not be add and remove [eth] address by not owner', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const notOwner = await helper.eth.createAccountWithBalance(donor); + const user = helper.eth.createAccount(); + + const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false; + await expect(collectionEvm.methods.addToCollectionAllowList(user).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false; + await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner}); + + expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true; + await expect(collectionEvm.methods.removeFromCollectionAllowList(user).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true; + }); + + itEth('Collection allowlist can not be add and remove [sub] address by not owner', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const notOwner = await helper.eth.createAccountWithBalance(donor); + const user = donor; + + const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + await expect(collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); + + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + await expect(collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + }); +}); --- a/tests/src/eth/api/CollectionHelpers.sol +++ b/tests/src/eth/api/CollectionHelpers.sol @@ -14,14 +14,11 @@ /// @dev inlined interface interface CollectionHelpersEvents { - event CollectionCreated( - address indexed owner, - address indexed collectionId - ); + event CollectionCreated(address indexed owner, address indexed collectionId); } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0x675f3074 +/// @dev the ERC-165 identifier for this interface is 0x88ee8ef1 interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { /// Create an NFT collection /// @param name Name of the collection @@ -45,9 +42,9 @@ string memory baseUri ) external returns (address); - /// @dev EVM selector for this function is: 0x44a68ad5, - /// or in textual repr: createRefungibleCollection(string,string,string) - function createRefungibleCollection( + /// @dev EVM selector for this function is: 0xab173450, + /// or in textual repr: createRFTCollection(string,string,string) + function createRFTCollection( string memory name, string memory description, string memory tokenPrefix @@ -67,8 +64,5 @@ /// @return bool Does the collection exist? /// @dev EVM selector for this function is: 0xc3de1494, /// or in textual repr: isCollectionExist(address) - function isCollectionExist(address collectionAddress) - external - view - returns (bool); + function isCollectionExist(address collectionAddress) external view returns (bool); } --- a/tests/src/eth/api/ContractHelpers.sol +++ b/tests/src/eth/api/ContractHelpers.sol @@ -23,7 +23,7 @@ } /// @title Magic contract, which allows users to reconfigure other contracts -/// @dev the ERC-165 identifier for this interface is 0xd77fab70 +/// @dev the ERC-165 identifier for this interface is 0x172cb4fb interface ContractHelpers is Dummy, ERC165, ContractHelpersEvents { /// Get user, which deployed specified contract /// @dev May return zero address in case if contract is deployed @@ -111,7 +111,7 @@ function setSponsoringMode(address contractAddress, uint8 mode) external; /// Get current contract sponsoring rate limit - /// @param contractAddress Contract to get sponsoring mode of + /// @param contractAddress Contract to get sponsoring rate limit of /// @return uint32 Amount of blocks between two sponsored transactions /// @dev EVM selector for this function is: 0x610cfabd, /// or in textual repr: getSponsoringRateLimit(address) @@ -131,6 +131,28 @@ function setSponsoringRateLimit(address contractAddress, uint32 rateLimit) external; + /// Set contract sponsoring fee limit + /// @dev Sponsoring fee limit - is maximum fee that could be spent by + /// single transaction + /// @param contractAddress Contract to change sponsoring fee limit of + /// @param feeLimit Fee limit + /// @dev Only contract owner can change this setting + /// @dev EVM selector for this function is: 0x03aed665, + /// or in textual repr: setSponsoringFeeLimit(address,uint256) + function setSponsoringFeeLimit(address contractAddress, uint256 feeLimit) + external; + + /// Get current contract sponsoring fee limit + /// @param contractAddress Contract to get sponsoring fee limit of + /// @return uint256 Maximum amount of fee that could be spent by single + /// transaction + /// @dev EVM selector for this function is: 0xc3fdc9ee, + /// or in textual repr: getSponsoringFeeLimit(address) + function getSponsoringFeeLimit(address contractAddress) + external + view + returns (uint256); + /// Is specified user present in contract allow list /// @dev Contract owner always implicitly included /// @param contractAddress Contract to check allowlist of --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -13,7 +13,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xe54be640 +/// @dev the ERC-165 identifier for this interface is 0x9f70d4e0 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -164,6 +164,13 @@ /// or in textual repr: setCollectionAccess(uint8) function setCollectionAccess(uint8 mode) external; + /// Checks that user allowed to operate with collection. + /// + /// @param user User address to check. + /// @dev EVM selector for this function is: 0xd63a8e11, + /// or in textual repr: allowed(address) + function allowed(address user) external view returns (bool); + /// Add the user to the allowed list. /// /// @param user Address of a trusted user. @@ -171,6 +178,13 @@ /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) external; + /// Add substrate user to allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xd06ad267, + /// or in textual repr: addToCollectionAllowListSubstrate(uint256) + function addToCollectionAllowListSubstrate(uint256 user) external; + /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -178,6 +192,13 @@ /// or in textual repr: removeFromCollectionAllowList(address) function removeFromCollectionAllowList(address user) external; + /// Remove substrate user from allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xa31913ed, + /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) + function removeFromCollectionAllowListSubstrate(uint256 user) external; + /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -208,6 +229,14 @@ /// or in textual repr: uniqueCollectionType() function uniqueCollectionType() external returns (string memory); + /// Get collection owner. + /// + /// @return Tuble with sponsor address and his substrate mirror. + /// If address is canonical then substrate mirror is zero and vice versa. + /// @dev EVM selector for this function is: 0xdf727d3b, + /// or in textual repr: collectionOwner() + function collectionOwner() external view returns (Tuple6 memory); + /// Changes collection owner to another account /// /// @dev Owner can be changed only by current owner --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -65,7 +65,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xe54be640 +/// @dev the ERC-165 identifier for this interface is 0x9f70d4e0 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -216,6 +216,13 @@ /// or in textual repr: setCollectionAccess(uint8) function setCollectionAccess(uint8 mode) external; + /// Checks that user allowed to operate with collection. + /// + /// @param user User address to check. + /// @dev EVM selector for this function is: 0xd63a8e11, + /// or in textual repr: allowed(address) + function allowed(address user) external view returns (bool); + /// Add the user to the allowed list. /// /// @param user Address of a trusted user. @@ -223,6 +230,13 @@ /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) external; + /// Add substrate user to allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xd06ad267, + /// or in textual repr: addToCollectionAllowListSubstrate(uint256) + function addToCollectionAllowListSubstrate(uint256 user) external; + /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -230,6 +244,13 @@ /// or in textual repr: removeFromCollectionAllowList(address) function removeFromCollectionAllowList(address user) external; + /// Remove substrate user from allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xa31913ed, + /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) + function removeFromCollectionAllowListSubstrate(uint256 user) external; + /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -260,6 +281,14 @@ /// or in textual repr: uniqueCollectionType() function uniqueCollectionType() external returns (string memory); + /// Get collection owner. + /// + /// @return Tuble with sponsor address and his substrate mirror. + /// If address is canonical then substrate mirror is zero and vice versa. + /// @dev EVM selector for this function is: 0xdf727d3b, + /// or in textual repr: collectionOwner() + function collectionOwner() external view returns (Tuple17 memory); + /// Changes collection owner to another account /// /// @dev Owner can be changed only by current owner --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -65,7 +65,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xe54be640 +/// @dev the ERC-165 identifier for this interface is 0x9f70d4e0 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -216,6 +216,13 @@ /// or in textual repr: setCollectionAccess(uint8) function setCollectionAccess(uint8 mode) external; + /// Checks that user allowed to operate with collection. + /// + /// @param user User address to check. + /// @dev EVM selector for this function is: 0xd63a8e11, + /// or in textual repr: allowed(address) + function allowed(address user) external view returns (bool); + /// Add the user to the allowed list. /// /// @param user Address of a trusted user. @@ -223,6 +230,13 @@ /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) external; + /// Add substrate user to allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xd06ad267, + /// or in textual repr: addToCollectionAllowListSubstrate(uint256) + function addToCollectionAllowListSubstrate(uint256 user) external; + /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -230,6 +244,13 @@ /// or in textual repr: removeFromCollectionAllowList(address) function removeFromCollectionAllowList(address user) external; + /// Remove substrate user from allowed list. + /// + /// @param user User substrate address. + /// @dev EVM selector for this function is: 0xa31913ed, + /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) + function removeFromCollectionAllowListSubstrate(uint256 user) external; + /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -260,6 +281,14 @@ /// or in textual repr: uniqueCollectionType() function uniqueCollectionType() external returns (string memory); + /// Get collection owner. + /// + /// @return Tuble with sponsor address and his substrate mirror. + /// If address is canonical then substrate mirror is zero and vice versa. + /// @dev EVM selector for this function is: 0xdf727d3b, + /// or in textual repr: collectionOwner() + function collectionOwner() external view returns (Tuple17 memory); + /// Changes collection owner to another account /// /// @dev Owner can be changed only by current owner --- a/tests/src/eth/collectionHelpersAbi.json +++ b/tests/src/eth/collectionHelpersAbi.json @@ -59,7 +59,7 @@ { "internalType": "string", "name": "description", "type": "string" }, { "internalType": "string", "name": "tokenPrefix", "type": "string" } ], - "name": "createRefungibleCollection", + "name": "createRFTCollection", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "nonpayable", "type": "function" --- a/tests/src/eth/contractSponsoring.test.ts +++ b/tests/src/eth/contractSponsoring.test.ts @@ -14,8 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +import * as solc from 'solc'; import {expect} from 'chai'; import {expectSubstrateEventsAtBlock} from '../util/helpers'; +import Web3 from 'web3'; + import { contractHelpers, createEthAccountWithBalance, @@ -26,7 +29,11 @@ createEthAccount, ethBalanceViaSub, normalizeEvents, + CompiledContract, + GAS_ARGS, + subToEth, } from './util/helpers'; +import {submitTransactionAsync} from '../substrate/substrate-api'; describe('Sponsoring EVM contracts', () => { itWeb3('Self sponsored can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => { @@ -478,3 +485,167 @@ expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200'); }); }); + +describe('Sponsoring Fee Limit', () => { + + let testContract: CompiledContract; + + function compileTestContract() { + if (!testContract) { + const input = { + language: 'Solidity', + sources: { + ['TestContract.sol']: { + content: + ` + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + contract TestContract { + event Result(bool); + + function test(uint32 cycles) public { + uint256 counter = 0; + while(true) { + counter ++; + if (counter > cycles){ + break; + } + } + emit Result(true); + } + } + `, + }, + }, + settings: { + outputSelection: { + '*': { + '*': ['*'], + }, + }, + }, + }; + const json = JSON.parse(solc.compile(JSON.stringify(input))); + const out = json.contracts['TestContract.sol']['TestContract']; + + testContract = { + abi: out.abi, + object: '0x' + out.evm.bytecode.object, + }; + } + return testContract; + } + + async function deployTestContract(web3: Web3, owner: string) { + const compiled = compileTestContract(); + const testContract = new web3.eth.Contract(compiled.abi, undefined, { + data: compiled.object, + from: owner, + ...GAS_ARGS, + }); + return await testContract.deploy({data: compiled.object}).send({from: owner}); + } + + itWeb3('Default fee limit', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, owner); + const helpers = contractHelpers(web3, owner); + expect(await helpers.methods.getSponsoringFeeLimit(flipper.options.address).call()).to.be.equals('115792089237316195423570985008687907853269984665640564039457584007913129639935'); + }); + + itWeb3('Set fee limit', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, owner); + const helpers = contractHelpers(web3, owner); + await helpers.methods.setSponsoringFeeLimit(flipper.options.address, 100).send(); + expect(await helpers.methods.getSponsoringFeeLimit(flipper.options.address).call()).to.be.equals('100'); + }); + + itWeb3('Negative test - set fee limit by non-owner', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const stranger = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, owner); + const helpers = contractHelpers(web3, owner); + await expect(helpers.methods.setSponsoringFeeLimit(flipper.options.address, 100).send({from: stranger})).to.be.rejected; + }); + + itWeb3('Negative test - check that eth transactions exceeding fee limit are not executed', async ({api, web3, privateKeyWrapper}) => { + const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const user = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const testContract = await deployTestContract(web3, owner); + const helpers = contractHelpers(web3, owner); + + await helpers.methods.setSponsoringMode(testContract.options.address, SponsoringMode.Generous).send({from: owner}); + await helpers.methods.setSponsoringRateLimit(testContract.options.address, 0).send({from: owner}); + + await helpers.methods.setSponsor(testContract.options.address, sponsor).send(); + await helpers.methods.confirmSponsorship(testContract.options.address).send({from: sponsor}); + + const gasPrice = BigInt(await web3.eth.getGasPrice()); + + await helpers.methods.setSponsoringFeeLimit(testContract.options.address, 2_000_000n * gasPrice).send(); + + const originalUserBalance = await web3.eth.getBalance(user); + await testContract.methods.test(100).send({from: user, gas: 2_000_000}); + expect(await web3.eth.getBalance(user)).to.be.equal(originalUserBalance); + + await testContract.methods.test(100).send({from: user, gas: 2_100_000}); + expect(await web3.eth.getBalance(user)).to.not.be.equal(originalUserBalance); + }); + + itWeb3('Negative test - check that evm.call transactions exceeding fee limit are not executed', async ({api, web3, privateKeyWrapper}) => { + const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const testContract = await deployTestContract(web3, owner); + const helpers = contractHelpers(web3, owner); + + await helpers.methods.setSponsoringMode(testContract.options.address, SponsoringMode.Generous).send({from: owner}); + await helpers.methods.setSponsoringRateLimit(testContract.options.address, 0).send({from: owner}); + + await helpers.methods.setSponsor(testContract.options.address, sponsor).send(); + await helpers.methods.confirmSponsorship(testContract.options.address).send({from: sponsor}); + + const gasPrice = BigInt(await web3.eth.getGasPrice()); + + await helpers.methods.setSponsoringFeeLimit(testContract.options.address, 2_000_000n * gasPrice).send(); + + const alice = privateKeyWrapper('//Alice'); + const originalAliceBalance = (await api.query.system.account(alice.address)).data.free.toBigInt(); + + await submitTransactionAsync( + alice, + api.tx.evm.call( + subToEth(alice.address), + testContract.options.address, + testContract.methods.test(100).encodeABI(), + Uint8Array.from([]), + 2_000_000n, + gasPrice, + null, + null, + [], + ), + ); + expect((await api.query.system.account(alice.address)).data.free.toBigInt()).to.be.equal(originalAliceBalance); + + await submitTransactionAsync( + alice, + api.tx.evm.call( + subToEth(alice.address), + testContract.options.address, + testContract.methods.test(100).encodeABI(), + Uint8Array.from([]), + 2_100_000n, + gasPrice, + null, + null, + [], + ), + ); + expect((await api.query.system.account(alice.address)).data.free.toBigInt()).to.not.be.equal(originalAliceBalance); + }); +}); --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -41,7 +41,7 @@ const collectionCountBefore = await getCreatedCollectionCount(api); const result = await collectionHelper.methods - .createRefungibleCollection(collectionName, description, tokenPrefix) + .createRFTCollection(collectionName, description, tokenPrefix) .send(); const collectionCountAfter = await getCreatedCollectionCount(api); @@ -65,7 +65,7 @@ .call()).to.be.false; await collectionHelpers.methods - .createRefungibleCollection('A', 'A', 'A') + .createRFTCollection('A', 'A', 'A') .send(); expect(await collectionHelpers.methods @@ -76,7 +76,7 @@ itWeb3('Set sponsorship', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const collectionHelpers = evmCollectionHelpers(web3, owner); - let result = await collectionHelpers.methods.createRefungibleCollection('Sponsor collection', '1', '1').send(); + let result = await collectionHelpers.methods.createRFTCollection('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, {type: 'ReFungible'}); @@ -96,7 +96,7 @@ itWeb3('Set limits', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const collectionHelpers = evmCollectionHelpers(web3, owner); - const result = await collectionHelpers.methods.createRefungibleCollection('Const collection', '5', '5').send(); + const result = await collectionHelpers.methods.createRFTCollection('Const collection', '5', '5').send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const limits = { accountTokenOwnershipLimit: 1000, @@ -141,7 +141,7 @@ .isCollectionExist(collectionAddressForNonexistentCollection).call()) .to.be.false; - const result = await collectionHelpers.methods.createRefungibleCollection('Collection address exist', '7', '7').send(); + const result = await collectionHelpers.methods.createRFTCollection('Collection address exist', '7', '7').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); expect(await collectionHelpers.methods .isCollectionExist(collectionIdAddress).call()) @@ -164,7 +164,7 @@ const tokenPrefix = 'A'; await expect(helper.methods - .createRefungibleCollection(collectionName, description, tokenPrefix) + .createRFTCollection(collectionName, description, tokenPrefix) .call()).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGHT); } @@ -174,7 +174,7 @@ const description = 'A'.repeat(MAX_DESCRIPTION_LENGHT + 1); const tokenPrefix = 'A'; await expect(helper.methods - .createRefungibleCollection(collectionName, description, tokenPrefix) + .createRFTCollection(collectionName, description, tokenPrefix) .call()).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGHT); } { @@ -183,7 +183,7 @@ const description = 'A'; const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGHT + 1); await expect(helper.methods - .createRefungibleCollection(collectionName, description, tokenPrefix) + .createRFTCollection(collectionName, description, tokenPrefix) .call()).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGHT); } }); @@ -196,7 +196,7 @@ const tokenPrefix = 'A'; await expect(helper.methods - .createRefungibleCollection(collectionName, description, tokenPrefix) + .createRFTCollection(collectionName, description, tokenPrefix) .call()).to.be.rejectedWith('NotSufficientFounds'); }); @@ -204,7 +204,7 @@ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const notOwner = createEthAccount(web3); const collectionHelpers = evmCollectionHelpers(web3, owner); - const result = await collectionHelpers.methods.createRefungibleCollection('A', 'A', 'A').send(); + const result = await collectionHelpers.methods.createRFTCollection('A', 'A', 'A').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contractEvmFromNotOwner = evmCollection(web3, notOwner, collectionIdAddress, {type: 'ReFungible'}); const EXPECTED_ERROR = 'NoPermission'; @@ -229,7 +229,7 @@ itWeb3('(!negative test!) Set limits', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const collectionHelpers = evmCollectionHelpers(web3, owner); - const result = await collectionHelpers.methods.createRefungibleCollection('Schema collection', 'A', 'A').send(); + const result = await collectionHelpers.methods.createRFTCollection('Schema collection', 'A', 'A').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const collectionEvm = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); await expect(collectionEvm.methods --- a/tests/src/eth/fractionalizer/Fractionalizer.sol +++ b/tests/src/eth/fractionalizer/Fractionalizer.sol @@ -10,179 +10,158 @@ /// stores allowlist of NFT tokens available for fractionalization, has methods /// for fractionalization and defractionalization of NFT tokens. contract Fractionalizer { - struct Token { - address _collection; - uint256 _tokenId; - } - address rftCollection; - mapping(address => bool) nftCollectionAllowList; - mapping(address => mapping(uint256 => uint256)) public nft2rftMapping; - mapping(address => Token) public rft2nftMapping; - bytes32 refungibleCollectionType = keccak256(bytes("ReFungible")); + struct Token { + address _collection; + uint256 _tokenId; + } + address rftCollection; + mapping(address => bool) nftCollectionAllowList; + mapping(address => mapping(uint256 => uint256)) public nft2rftMapping; + mapping(address => Token) public rft2nftMapping; + bytes32 refungibleCollectionType = keccak256(bytes("ReFungible")); - receive() external payable onlyOwner {} + receive() external payable onlyOwner {} - /// @dev Method modifier to only allow contract owner to call it. - modifier onlyOwner() { - address contracthelpersAddress = 0x842899ECF380553E8a4de75bF534cdf6fBF64049; - ContractHelpers contractHelpers = ContractHelpers(contracthelpersAddress); - address contractOwner = contractHelpers.contractOwner(address(this)); - require(msg.sender == contractOwner, "Only owner can"); - _; - } + /// @dev Method modifier to only allow contract owner to call it. + modifier onlyOwner() { + address contracthelpersAddress = 0x842899ECF380553E8a4de75bF534cdf6fBF64049; + ContractHelpers contractHelpers = ContractHelpers(contracthelpersAddress); + address contractOwner = contractHelpers.contractOwner(address(this)); + require(msg.sender == contractOwner, "Only owner can"); + _; + } - /// @dev This emits when RFT collection setting is changed. - event RFTCollectionSet(address _collection); + /// @dev This emits when RFT collection setting is changed. + event RFTCollectionSet(address _collection); - /// @dev This emits when NFT collection is allowed or disallowed. - event AllowListSet(address _collection, bool _status); + /// @dev This emits when NFT collection is allowed or disallowed. + event AllowListSet(address _collection, bool _status); - /// @dev This emits when NFT token is fractionalized by contract. - event Fractionalized(address _collection, uint256 _tokenId, address _rftToken, uint128 _amount); + /// @dev This emits when NFT token is fractionalized by contract. + event Fractionalized(address _collection, uint256 _tokenId, address _rftToken, uint128 _amount); - /// @dev This emits when NFT token is defractionalized by contract. - event Defractionalized(address _rftToken, address _nftCollection, uint256 _nftTokenId); + /// @dev This emits when NFT token is defractionalized by contract. + event Defractionalized(address _rftToken, address _nftCollection, uint256 _nftTokenId); - /// Set RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens - /// would be created in this collection. - /// @dev Throws if RFT collection is already configured for this contract. - /// Throws if collection of wrong type (NFT, Fungible) is provided instead - /// of RFT collection. - /// Throws if `msg.sender` is not owner or admin of provided RFT collection. - /// Can only be called by contract owner. - /// @param _collection address of RFT collection. - function setRFTCollection(address _collection) public onlyOwner { - require( - rftCollection == address(0), - "RFT collection is already set" - ); - UniqueRefungible refungibleContract = UniqueRefungible(_collection); - string memory collectionType = refungibleContract.uniqueCollectionType(); - - require( - keccak256(bytes(collectionType)) == refungibleCollectionType, - "Wrong collection type. Collection is not refungible." - ); - require( - refungibleContract.isOwnerOrAdmin(address(this)), - "Fractionalizer contract should be an admin of the collection" - ); - rftCollection = _collection; - emit RFTCollectionSet(rftCollection); - } + /// Set RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens + /// would be created in this collection. + /// @dev Throws if RFT collection is already configured for this contract. + /// Throws if collection of wrong type (NFT, Fungible) is provided instead + /// of RFT collection. + /// Throws if `msg.sender` is not owner or admin of provided RFT collection. + /// Can only be called by contract owner. + /// @param _collection address of RFT collection. + function setRFTCollection(address _collection) public onlyOwner { + require(rftCollection == address(0), "RFT collection is already set"); + UniqueRefungible refungibleContract = UniqueRefungible(_collection); + string memory collectionType = refungibleContract.uniqueCollectionType(); - /// Creates and sets RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens - /// would be created in this collection. - /// @dev Throws if RFT collection is already configured for this contract. - /// Can only be called by contract owner. - /// @param _name name for created RFT collection. - /// @param _description description for created RFT collection. - /// @param _tokenPrefix token prefix for created RFT collection. - function createAndSetRFTCollection(string calldata _name, string calldata _description, string calldata _tokenPrefix) public onlyOwner { - require( - rftCollection == address(0), - "RFT collection is already set" - ); - address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F; - rftCollection = CollectionHelpers(collectionHelpers).createRefungibleCollection(_name, _description, _tokenPrefix); - emit RFTCollectionSet(rftCollection); - } + require( + keccak256(bytes(collectionType)) == refungibleCollectionType, + "Wrong collection type. Collection is not refungible." + ); + require( + refungibleContract.isOwnerOrAdmin(address(this)), + "Fractionalizer contract should be an admin of the collection" + ); + rftCollection = _collection; + emit RFTCollectionSet(rftCollection); + } - /// Allow or disallow NFT collection tokens from being fractionalized by this contract. - /// @dev Can only be called by contract owner. - /// @param collection NFT token address. - /// @param status `true` to allow and `false` to disallow NFT token. - function setNftCollectionIsAllowed(address collection, bool status) public onlyOwner { - nftCollectionAllowList[collection] = status; - emit AllowListSet(collection, status); - } + /// Creates and sets RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens + /// would be created in this collection. + /// @dev Throws if RFT collection is already configured for this contract. + /// Can only be called by contract owner. + /// @param _name name for created RFT collection. + /// @param _description description for created RFT collection. + /// @param _tokenPrefix token prefix for created RFT collection. + function createAndSetRFTCollection( + string calldata _name, + string calldata _description, + string calldata _tokenPrefix + ) public onlyOwner { + require(rftCollection == address(0), "RFT collection is already set"); + address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F; + rftCollection = CollectionHelpers(collectionHelpers).createRFTCollection(_name, _description, _tokenPrefix); + emit RFTCollectionSet(rftCollection); + } - /// Fractionilize NFT token. - /// @dev Takes NFT token from `msg.sender` and transfers RFT token to `msg.sender` - /// instead. Creates new RFT token if provided NFT token never was fractionalized - /// by this contract or existing RFT token if it was. - /// Throws if RFT collection isn't configured for this contract. - /// Throws if fractionalization of provided NFT token is not allowed - /// Throws if `msg.sender` is not owner of provided NFT token - /// @param _collection NFT collection address - /// @param _token id of NFT token to be fractionalized - /// @param _pieces number of pieces new RFT token would have - function nft2rft(address _collection, uint256 _token, uint128 _pieces) public { - require( - rftCollection != address(0), - "RFT collection is not set" - ); - UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection); - require( - nftCollectionAllowList[_collection] == true, - "Fractionalization of this collection is not allowed by admin" - ); - require( - UniqueNFT(_collection).ownerOf(_token) == msg.sender, - "Only token owner could fractionalize it" - ); - UniqueNFT(_collection).transferFrom( - msg.sender, - address(this), - _token - ); - uint256 rftTokenId; - address rftTokenAddress; - UniqueRefungibleToken rftTokenContract; - if (nft2rftMapping[_collection][_token] == 0) { - rftTokenId = rftCollectionContract.nextTokenId(); - rftCollectionContract.mint(address(this), rftTokenId); - rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId); - nft2rftMapping[_collection][_token] = rftTokenId; - rft2nftMapping[rftTokenAddress] = Token(_collection, _token); + /// Allow or disallow NFT collection tokens from being fractionalized by this contract. + /// @dev Can only be called by contract owner. + /// @param collection NFT token address. + /// @param status `true` to allow and `false` to disallow NFT token. + function setNftCollectionIsAllowed(address collection, bool status) public onlyOwner { + nftCollectionAllowList[collection] = status; + emit AllowListSet(collection, status); + } - rftTokenContract = UniqueRefungibleToken(rftTokenAddress); - } else { - rftTokenId = nft2rftMapping[_collection][_token]; - rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId); - rftTokenContract = UniqueRefungibleToken(rftTokenAddress); - } - rftTokenContract.repartition(_pieces); - rftTokenContract.transfer(msg.sender, _pieces); - emit Fractionalized(_collection, _token, rftTokenAddress, _pieces); - } + /// Fractionilize NFT token. + /// @dev Takes NFT token from `msg.sender` and transfers RFT token to `msg.sender` + /// instead. Creates new RFT token if provided NFT token never was fractionalized + /// by this contract or existing RFT token if it was. + /// Throws if RFT collection isn't configured for this contract. + /// Throws if fractionalization of provided NFT token is not allowed + /// Throws if `msg.sender` is not owner of provided NFT token + /// @param _collection NFT collection address + /// @param _token id of NFT token to be fractionalized + /// @param _pieces number of pieces new RFT token would have + function nft2rft( + address _collection, + uint256 _token, + uint128 _pieces + ) public { + require(rftCollection != address(0), "RFT collection is not set"); + UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection); + require( + nftCollectionAllowList[_collection] == true, + "Fractionalization of this collection is not allowed by admin" + ); + require(UniqueNFT(_collection).ownerOf(_token) == msg.sender, "Only token owner could fractionalize it"); + UniqueNFT(_collection).transferFrom(msg.sender, address(this), _token); + uint256 rftTokenId; + address rftTokenAddress; + UniqueRefungibleToken rftTokenContract; + if (nft2rftMapping[_collection][_token] == 0) { + rftTokenId = rftCollectionContract.nextTokenId(); + rftCollectionContract.mint(address(this), rftTokenId); + rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId); + nft2rftMapping[_collection][_token] = rftTokenId; + rft2nftMapping[rftTokenAddress] = Token(_collection, _token); - /// Defrationalize NFT token. - /// @dev Takes RFT token from `msg.sender` and transfers corresponding NFT token - /// to `msg.sender` instead. - /// Throws if RFT collection isn't configured for this contract. - /// Throws if provided RFT token is no from configured RFT collection. - /// Throws if RFT token was not created by this contract. - /// Throws if `msg.sender` isn't owner of all RFT token pieces. - /// @param _collection RFT collection address - /// @param _token id of RFT token - function rft2nft(address _collection, uint256 _token) public { - require( - rftCollection != address(0), - "RFT collection is not set" - ); - require( - rftCollection == _collection, - "Wrong RFT collection" - ); - UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection); - address rftTokenAddress = rftCollectionContract.tokenContractAddress(_token); - Token memory nftToken = rft2nftMapping[rftTokenAddress]; - require( - nftToken._collection != address(0), - "No corresponding NFT token found" - ); - UniqueRefungibleToken rftTokenContract = UniqueRefungibleToken(rftTokenAddress); - require( - rftTokenContract.balanceOf(msg.sender) == rftTokenContract.totalSupply(), - "Not all pieces are owned by the caller" - ); - rftCollectionContract.transferFrom(msg.sender, address(this), _token); - UniqueNFT(nftToken._collection).transferFrom( - address(this), - msg.sender, - nftToken._tokenId - ); - emit Defractionalized(rftTokenAddress, nftToken._collection, nftToken._tokenId); - } -} \ No newline at end of file + rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + } else { + rftTokenId = nft2rftMapping[_collection][_token]; + rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId); + rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + } + rftTokenContract.repartition(_pieces); + rftTokenContract.transfer(msg.sender, _pieces); + emit Fractionalized(_collection, _token, rftTokenAddress, _pieces); + } + + /// Defrationalize NFT token. + /// @dev Takes RFT token from `msg.sender` and transfers corresponding NFT token + /// to `msg.sender` instead. + /// Throws if RFT collection isn't configured for this contract. + /// Throws if provided RFT token is no from configured RFT collection. + /// Throws if RFT token was not created by this contract. + /// Throws if `msg.sender` isn't owner of all RFT token pieces. + /// @param _collection RFT collection address + /// @param _token id of RFT token + function rft2nft(address _collection, uint256 _token) public { + require(rftCollection != address(0), "RFT collection is not set"); + require(rftCollection == _collection, "Wrong RFT collection"); + UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection); + address rftTokenAddress = rftCollectionContract.tokenContractAddress(_token); + Token memory nftToken = rft2nftMapping[rftTokenAddress]; + require(nftToken._collection != address(0), "No corresponding NFT token found"); + UniqueRefungibleToken rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + require( + rftTokenContract.balanceOf(msg.sender) == rftTokenContract.totalSupply(), + "Not all pieces are owned by the caller" + ); + rftCollectionContract.transferFrom(msg.sender, address(this), _token); + UniqueNFT(nftToken._collection).transferFrom(address(this), msg.sender, nftToken._tokenId); + emit Defractionalized(rftTokenAddress, nftToken._collection, nftToken._tokenId); + } +} --- a/tests/src/eth/fractionalizer/fractionalizer.test.ts +++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts @@ -21,7 +21,7 @@ import {readFile} from 'fs/promises'; import {executeTransaction, submitTransactionAsync} from '../../substrate/substrate-api'; import {getCreateCollectionResult, getCreateItemResult, UNIQUE, requirePallets, Pallets} from '../../util/helpers'; -import {collectionIdToAddress, CompiledContract, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers'; +import {collectionIdToAddress, CompiledContract, createEthAccountWithBalance, createNonfungibleCollection, createRFTCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers'; import {Contract} from 'web3-eth-contract'; import * as solc from 'solc'; @@ -123,7 +123,7 @@ itWeb3('Set RFT collection', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const fractionalizer = await deployFractionalizer(web3, owner); - const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner); + const {collectionIdAddress} = await createRFTCollection(api, web3, owner); const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send(); const result = await fractionalizer.methods.setRFTCollection(collectionIdAddress).send(); @@ -256,7 +256,7 @@ itWeb3('call setRFTCollection twice', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner); + const {collectionIdAddress} = await createRFTCollection(api, web3, owner); const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); const fractionalizer = await deployFractionalizer(web3, owner); @@ -282,7 +282,7 @@ itWeb3('call setRFTCollection while not collection admin', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const fractionalizer = await deployFractionalizer(web3, owner); - const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner); + const {collectionIdAddress} = await createRFTCollection(api, web3, owner); await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call()) .to.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g); @@ -368,7 +368,7 @@ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const fractionalizer = await deployFractionalizer(web3, owner); - const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner); + const {collectionIdAddress: rftCollectionAddress} = await createRFTCollection(api, web3, owner); const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner); const rftTokenId = await refungibleContract.methods.nextTokenId().call(); await refungibleContract.methods.mint(owner, rftTokenId).send(); @@ -381,7 +381,7 @@ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); - const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner); + const {collectionIdAddress: rftCollectionAddress} = await createRFTCollection(api, web3, owner); const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner); const rftTokenId = await refungibleContract.methods.nextTokenId().call(); await refungibleContract.methods.mint(owner, rftTokenId).send(); @@ -392,7 +392,7 @@ itWeb3('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner); + const {collectionIdAddress: rftCollectionAddress} = await createRFTCollection(api, web3, owner); const fractionalizer = await deployFractionalizer(web3, owner); const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner); --- a/tests/src/eth/fungibleAbi.json +++ b/tests/src/eth/fungibleAbi.json @@ -78,6 +78,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "user", "type": "uint256" } + ], + "name": "addToCollectionAllowListSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], @@ -88,6 +97,15 @@ }, { "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "allowed", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], @@ -116,6 +134,23 @@ "type": "function" }, { + "inputs": [], + "name": "collectionOwner", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "field_0", "type": "address" }, + { "internalType": "uint256", "name": "field_1", "type": "uint256" } + ], + "internalType": "struct Tuple6", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "collectionProperty", "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], @@ -261,6 +296,15 @@ "type": "function" }, { + "inputs": [ + { "internalType": "uint256", "name": "user", "type": "uint256" } + ], + "name": "removeFromCollectionAllowListSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }], "name": "setCollectionAccess", "outputs": [], --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -109,6 +109,24 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "user", "type": "uint256" } + ], + "name": "addToCollectionAllowListSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "allowed", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "approved", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" } ], @@ -146,6 +164,23 @@ "type": "function" }, { + "inputs": [], + "name": "collectionOwner", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "field_0", "type": "address" }, + { "internalType": "uint256", "name": "field_1", "type": "uint256" } + ], + "internalType": "struct Tuple17", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "collectionProperty", "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], @@ -376,6 +411,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "user", "type": "uint256" } + ], + "name": "removeFromCollectionAllowListSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" } --- a/tests/src/eth/reFungible.test.ts +++ b/tests/src/eth/reFungible.test.ts @@ -26,7 +26,7 @@ itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); const nextTokenId = await contract.methods.nextTokenId().call(); @@ -38,7 +38,7 @@ itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -63,7 +63,7 @@ itWeb3('ownerOf', async ({api, web3, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -79,7 +79,7 @@ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const receiver = createEthAccount(web3); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -103,7 +103,7 @@ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const receiver = createEthAccount(web3); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -130,7 +130,7 @@ itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + let result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const receiver = createEthAccount(web3); const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); @@ -163,7 +163,7 @@ itWeb3('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -221,7 +221,7 @@ itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -247,7 +247,7 @@ itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -304,7 +304,7 @@ itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -344,7 +344,7 @@ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const receiver = createEthAccount(web3); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -376,7 +376,7 @@ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const receiver = createEthAccount(web3); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -413,7 +413,7 @@ itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -430,7 +430,7 @@ itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -109,6 +109,24 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "user", "type": "uint256" } + ], + "name": "addToCollectionAllowListSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "allowed", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "approved", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" } ], @@ -146,6 +164,23 @@ "type": "function" }, { + "inputs": [], + "name": "collectionOwner", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "field_0", "type": "address" }, + { "internalType": "uint256", "name": "field_1", "type": "uint256" } + ], + "internalType": "struct Tuple17", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "collectionProperty", "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], @@ -376,6 +411,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "user", "type": "uint256" } + ], + "name": "removeFromCollectionAllowListSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" } --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE, requirePallets, Pallets} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueRefungible, uniqueRefungibleToken} from './util/helpers'; +import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createRFTCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueRefungible, uniqueRefungibleToken} from './util/helpers'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; @@ -458,7 +458,7 @@ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send(); + const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); @@ -658,7 +658,7 @@ itWeb3('Default parent token address and id', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const {collectionIdAddress, collectionId} = await createRefungibleCollection(api, web3, owner); + const {collectionIdAddress, collectionId} = await createRFTCollection(api, web3, owner); const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); const refungibleTokenId = await refungibleContract.methods.nextTokenId().call(); await refungibleContract.methods.mint(owner, refungibleTokenId).send(); --- a/tests/src/eth/util/contractHelpersAbi.json +++ b/tests/src/eth/util/contractHelpersAbi.json @@ -134,6 +134,19 @@ "type": "address" } ], + "name": "getSponsoringFeeLimit", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + } + ], "name": "getSponsoringRateLimit", "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }], "stateMutability": "view", @@ -212,6 +225,20 @@ "name": "contractAddress", "type": "address" }, + { "internalType": "uint256", "name": "feeLimit", "type": "uint256" } + ], + "name": "setSponsoringFeeLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, { "internalType": "uint8", "name": "mode", "type": "uint8" } ], "name": "setSponsoringMode", --- a/tests/src/eth/util/helpers.ts +++ b/tests/src/eth/util/helpers.ts @@ -141,10 +141,10 @@ expect(result.success).to.be.true; } -export async function createRefungibleCollection(api: ApiPromise, web3: Web3, owner: string) { +export async function createRFTCollection(api: ApiPromise, web3: Web3, owner: string) { const collectionHelper = evmCollectionHelpers(web3, owner); const result = await collectionHelper.methods - .createRefungibleCollection('A', 'B', 'C') + .createRFTCollection('A', 'B', 'C') .send(); return await getCollectionAddressFromResult(api, result); } --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -1677,7 +1677,7 @@ }); } -export async function isAllowlisted(api: ApiPromise, collectionId: number, address: string | CrossAccountId) { +export async function isAllowlisted(api: ApiPromise, collectionId: number, address: string | CrossAccountId | IKeyringPair) { return (await api.rpc.unique.allowed(collectionId, normalizeAccountId(address))).toJSON(); } --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -730,6 +730,18 @@ } /** + * Check if user is in allow list. + * + * @param collectionId ID of collection + * @param user Account to check + * @example await getAdmins(1) + * @returns is user in allow list + */ + async allowed(collectionId: number, user: ICrossAccountId): Promise { + return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON(); + } + + /** * Adds an address to allow list * @param signer keyring of signer * @param collectionId ID of collection