--- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -37,6 +37,7 @@ Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf, eth::{ EthCrossAccount, convert_cross_account_to_uint256, CollectionPermissions as EvmPermissions, + CollectionLimits as EvmCollectionLimits, }, weights::WeightInfo, }; @@ -304,6 +305,101 @@ Ok(result) } + /// Get current collection limits. + /// + /// @return Array of tuples (byte, bool, uint256) with limits and their values. Order of limits: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// Return `false` if a limit not set. + fn collection_limits(&self) -> Result> { + let convert_value_limit = |limit: EvmCollectionLimits, + value: Option| + -> (EvmCollectionLimits, bool, uint256) { + value + .map(|v| (limit, true, v.into())) + .unwrap_or((limit, false, Default::default())) + }; + + let convert_bool_limit = |limit: EvmCollectionLimits, + value: Option| + -> (EvmCollectionLimits, bool, uint256) { + value + .map(|v| { + ( + limit, + true, + if v { + uint256::from(1) + } else { + Default::default() + }, + ) + }) + .unwrap_or((limit, false, Default::default())) + }; + + let limits = &self.collection.limits; + + Ok(vec![ + convert_value_limit( + EvmCollectionLimits::AccountTokenOwnership, + limits.account_token_ownership_limit, + ), + convert_value_limit( + EvmCollectionLimits::SponsoredDataSize, + limits.sponsored_data_size, + ), + limits + .sponsored_data_rate_limit + .map(|limit| { + ( + EvmCollectionLimits::SponsoredDataRateLimit, + match limit { + SponsoringRateLimit::Blocks(_) => true, + _ => false, + }, + match limit { + SponsoringRateLimit::Blocks(blocks) => blocks.into(), + _ => Default::default(), + }, + ) + }) + .unwrap_or(( + EvmCollectionLimits::SponsoredDataRateLimit, + false, + Default::default(), + )), + convert_value_limit(EvmCollectionLimits::TokenLimit, limits.token_limit), + convert_value_limit( + EvmCollectionLimits::SponsorTransferTimeout, + limits.sponsor_transfer_timeout, + ), + convert_value_limit( + EvmCollectionLimits::SponsorApproveTimeout, + limits.sponsor_approve_timeout, + ), + convert_bool_limit( + EvmCollectionLimits::OwnerCanTransfer, + limits.owner_can_transfer, + ), + convert_bool_limit( + EvmCollectionLimits::OwnerCanDestroy, + limits.owner_can_destroy, + ), + convert_bool_limit( + EvmCollectionLimits::TransferEnabled, + limits.transfers_enabled, + ), + ]) + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -318,9 +414,19 @@ /// "transfersEnabled" /// @param value Value of the limit. #[solidity(rename_selector = "setCollectionLimit")] - fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result { + fn set_collection_limit( + &mut self, + caller: caller, + limit: EvmCollectionLimits, + status: bool, + value: uint256, + ) -> Result { self.consume_store_reads_and_writes(1, 1)?; + if !status { + return Err(Error::Revert("user can't disable limits".into())); + } + let value = value .try_into() .map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?; @@ -338,35 +444,35 @@ let mut limits = self.limits.clone(); - match limit.as_str() { - "accountTokenOwnershipLimit" => { + match limit { + EvmCollectionLimits::AccountTokenOwnership => { limits.account_token_ownership_limit = Some(value); } - "sponsoredDataSize" => { + EvmCollectionLimits::SponsoredDataSize => { limits.sponsored_data_size = Some(value); } - "sponsoredDataRateLimit" => { + EvmCollectionLimits::SponsoredDataRateLimit => { limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value)); } - "tokenLimit" => { + EvmCollectionLimits::TokenLimit => { limits.token_limit = Some(value); } - "sponsorTransferTimeout" => { + EvmCollectionLimits::SponsorTransferTimeout => { limits.sponsor_transfer_timeout = Some(value); } - "sponsorApproveTimeout" => { + EvmCollectionLimits::SponsorApproveTimeout => { limits.sponsor_approve_timeout = Some(value); } - "ownerCanTransfer" => { + EvmCollectionLimits::OwnerCanTransfer => { limits.owner_can_transfer = Some(convert_value_to_bool()?); } - "ownerCanDestroy" => { + EvmCollectionLimits::OwnerCanDestroy => { limits.owner_can_destroy = Some(convert_value_to_bool()?); } - "transfersEnabled" => { + EvmCollectionLimits::TransferEnabled => { limits.transfers_enabled = Some(convert_value_to_bool()?); } - _ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))), + _ => return Err(Error::Revert(format!("unknown limit \"{:?}\"", limit))), } let caller = T::CrossAccountId::from_eth(caller); @@ -778,3 +884,31 @@ }) } } + +fn convert_value_limit + Copy>( + limit: EvmCollectionLimits, + value: &Option, +) -> (EvmCollectionLimits, bool, uint256) { + value + .map(|v| (limit, true, v.into())) + .unwrap_or((limit, false, Default::default())) +} + +fn convert_bool_limit( + limit: EvmCollectionLimits, + value: &Option, +) -> (EvmCollectionLimits, bool, uint256) { + value + .map(|v| { + ( + limit, + true, + if v { + uint256::from(1) + } else { + Default::default() + }, + ) + }) + .unwrap_or((limit, false, Default::default())) +} --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -155,6 +155,20 @@ } } } +#[derive(Debug, Default, Clone, Copy, AbiCoder)] +#[repr(u8)] +pub enum CollectionLimits { + #[default] + AccountTokenOwnership, + SponsoredDataSize, + SponsoredDataRateLimit, + TokenLimit, + SponsorTransferTimeout, + SponsorApproveTimeout, + OwnerCanTransfer, + OwnerCanDestroy, + TransferEnabled, +} #[derive(Default, Debug, Clone, Copy, AbiCoder)] #[repr(u8)] pub enum CollectionPermissions { --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -158,6 +158,27 @@ return Tuple8(0x0000000000000000000000000000000000000000, 0); } + /// Get current collection limits. + /// + /// @return Array of tuples (byte, bool, uint256) with limits and their values. Order of limits: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// Return `false` if a limit not set. + /// @dev EVM selector for this function is: 0xf63bc572, + /// or in textual repr: collectionLimits() + function collectionLimits() public view returns (Tuple20[] memory) { + require(false, stub_error); + dummy; + return new Tuple20[](0); + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -171,11 +192,16 @@ /// "ownerCanDestroy", /// "transfersEnabled" /// @param value Value of the limit. - /// @dev EVM selector for this function is: 0x4ad890a8, - /// or in textual repr: setCollectionLimit(string,uint256) - function setCollectionLimit(string memory limit, uint256 value) public { + /// @dev EVM selector for this function is: 0x88150bd0, + /// or in textual repr: setCollectionLimit(uint8,bool,uint256) + function setCollectionLimit( + CollectionLimits limit, + bool status, + uint256 value + ) public { require(false, stub_error); limit; + status; value; dummy = 0; } --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -291,6 +291,27 @@ return Tuple30(0x0000000000000000000000000000000000000000, 0); } + /// Get current collection limits. + /// + /// @return Array of tuples (byte, bool, uint256) with limits and their values. Order of limits: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// Return `false` if a limit not set. + /// @dev EVM selector for this function is: 0xf63bc572, + /// or in textual repr: collectionLimits() + function collectionLimits() public view returns (Tuple33[] memory) { + require(false, stub_error); + dummy; + return new Tuple33[](0); + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -304,11 +325,16 @@ /// "ownerCanDestroy", /// "transfersEnabled" /// @param value Value of the limit. - /// @dev EVM selector for this function is: 0x4ad890a8, - /// or in textual repr: setCollectionLimit(string,uint256) - function setCollectionLimit(string memory limit, uint256 value) public { + /// @dev EVM selector for this function is: 0x88150bd0, + /// or in textual repr: setCollectionLimit(uint8,bool,uint256) + function setCollectionLimit( + CollectionLimits limit, + bool status, + uint256 value + ) public { require(false, stub_error); limit; + status; value; dummy = 0; } --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -292,6 +292,27 @@ return Tuple29(0x0000000000000000000000000000000000000000, 0); } + /// Get current collection limits. + /// + /// @return Array of tuples (byte, bool, uint256) with limits and their values. Order of limits: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// Return `false` if a limit not set. + /// @dev EVM selector for this function is: 0xf63bc572, + /// or in textual repr: collectionLimits() + function collectionLimits() public view returns (Tuple32[] memory) { + require(false, stub_error); + dummy; + return new Tuple32[](0); + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -305,11 +326,16 @@ /// "ownerCanDestroy", /// "transfersEnabled" /// @param value Value of the limit. - /// @dev EVM selector for this function is: 0x4ad890a8, - /// or in textual repr: setCollectionLimit(string,uint256) - function setCollectionLimit(string memory limit, uint256 value) public { + /// @dev EVM selector for this function is: 0x88150bd0, + /// or in textual repr: setCollectionLimit(uint8,bool,uint256) + function setCollectionLimit( + CollectionLimits limit, + bool status, + uint256 value + ) public { require(false, stub_error); limit; + status; value; dummy = 0; } --- a/tests/src/eth/abi/fungible.json +++ b/tests/src/eth/abi/fungible.json @@ -208,11 +208,16 @@ }, { "inputs": [], +<<<<<<< HEAD "name": "collectionNestingPermissions", +======= + "name": "collectionLimits", +>>>>>>> 32e011ce... added `collectionLimits` function in `Collection` interface, changed signture for `setCollectionLimit` "outputs": [ { "components": [ { +<<<<<<< HEAD "internalType": "enum CollectionPermissions", "name": "field_0", "type": "uint8" @@ -220,6 +225,16 @@ { "internalType": "bool", "name": "field_1", "type": "bool" } ], "internalType": "struct Tuple24[]", +======= + "internalType": "enum CollectionLimits", + "name": "field_0", + "type": "uint8" + }, + { "internalType": "bool", "name": "field_1", "type": "bool" }, + { "internalType": "uint256", "name": "field_2", "type": "uint256" } + ], + "internalType": "struct Tuple20[]", +>>>>>>> 32e011ce... added `collectionLimits` function in `Collection` interface, changed signture for `setCollectionLimit` "name": "", "type": "tuple[]" } @@ -229,6 +244,7 @@ }, { "inputs": [], +<<<<<<< HEAD "name": "collectionNestingRestrictedCollectionIds", "outputs": [ { @@ -250,6 +266,8 @@ }, { "inputs": [], +======= +>>>>>>> 32e011ce... added `collectionLimits` function in `Collection` interface, changed signture for `setCollectionLimit` "name": "collectionOwner", "outputs": [ { @@ -453,7 +471,12 @@ }, { "inputs": [ - { "internalType": "string", "name": "limit", "type": "string" }, + { + "internalType": "enum CollectionLimits", + "name": "limit", + "type": "uint8" + }, + { "internalType": "bool", "name": "status", "type": "bool" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "setCollectionLimit", --- a/tests/src/eth/abi/nonFungible.json +++ b/tests/src/eth/abi/nonFungible.json @@ -607,7 +607,12 @@ }, { "inputs": [ - { "internalType": "string", "name": "limit", "type": "string" }, + { + "internalType": "enum CollectionLimits", + "name": "limit", + "type": "uint8" + }, + { "internalType": "bool", "name": "status", "type": "bool" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "setCollectionLimit", --- a/tests/src/eth/abi/reFungible.json +++ b/tests/src/eth/abi/reFungible.json @@ -589,7 +589,12 @@ }, { "inputs": [ - { "internalType": "string", "name": "limit", "type": "string" }, + { + "internalType": "enum CollectionLimits", + "name": "limit", + "type": "uint8" + }, + { "internalType": "bool", "name": "status", "type": "bool" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "setCollectionLimit", --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -13,7 +13,11 @@ } /// @title A contract that allows you to work with collections. +<<<<<<< HEAD /// @dev the ERC-165 identifier for this interface is 0xb5e1747f +======= +/// @dev the ERC-165 identifier for this interface is 0xf8ebdec0 +>>>>>>> 32e011ce... added `collectionLimits` function in `Collection` interface, changed signture for `setCollectionLimit` interface Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -104,6 +108,23 @@ /// or in textual repr: collectionSponsor() function collectionSponsor() external view returns (Tuple8 memory); + /// Get current collection limits. + /// + /// @return Array of tuples (byte, bool, uint256) with limits and their values. Order of limits: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// Return `false` if a limit not set. + /// @dev EVM selector for this function is: 0xf63bc572, + /// or in textual repr: collectionLimits() + function collectionLimits() external view returns (Tuple19[] memory); + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -117,9 +138,13 @@ /// "ownerCanDestroy", /// "transfersEnabled" /// @param value Value of the limit. - /// @dev EVM selector for this function is: 0x4ad890a8, - /// or in textual repr: setCollectionLimit(string,uint256) - function setCollectionLimit(string memory limit, uint256 value) external; + /// @dev EVM selector for this function is: 0x88150bd0, + /// or in textual repr: setCollectionLimit(uint8,bool,uint256) + function setCollectionLimit( + CollectionLimits limit, + bool status, + uint256 value + ) external; /// Get contract address. /// @dev EVM selector for this function is: 0xf6b4dfb4, @@ -288,6 +313,7 @@ uint256 sub; } +<<<<<<< HEAD /// @dev anonymous struct struct Tuple23 { CollectionPermissions field_0; @@ -303,6 +329,25 @@ struct Tuple20 { bool field_0; uint256[] field_1; +======= +enum CollectionLimits { + AccountTokenOwnership, + SponsoredDataSize, + SponsoredDataRateLimit, + TokenLimit, + SponsorTransferTimeout, + SponsorApproveTimeout, + OwnerCanTransfer, + OwnerCanDestroy, + TransferEnabled +} + +/// @dev anonymous struct +struct Tuple19 { + CollectionLimits field_0; + bool field_1; + uint256 field_2; +>>>>>>> 32e011ce... added `collectionLimits` function in `Collection` interface, changed signture for `setCollectionLimit` } /// @dev Property struct --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -104,7 +104,11 @@ } /// @title A contract that allows you to work with collections. +<<<<<<< HEAD /// @dev the ERC-165 identifier for this interface is 0xb5e1747f +======= +/// @dev the ERC-165 identifier for this interface is 0xf8ebdec0 +>>>>>>> 32e011ce... added `collectionLimits` function in `Collection` interface, changed signture for `setCollectionLimit` interface Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -195,6 +199,23 @@ /// or in textual repr: collectionSponsor() function collectionSponsor() external view returns (Tuple27 memory); + /// Get current collection limits. + /// + /// @return Array of tuples (byte, bool, uint256) with limits and their values. Order of limits: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// Return `false` if a limit not set. + /// @dev EVM selector for this function is: 0xf63bc572, + /// or in textual repr: collectionLimits() + function collectionLimits() external view returns (Tuple30[] memory); + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -208,9 +229,13 @@ /// "ownerCanDestroy", /// "transfersEnabled" /// @param value Value of the limit. - /// @dev EVM selector for this function is: 0x4ad890a8, - /// or in textual repr: setCollectionLimit(string,uint256) - function setCollectionLimit(string memory limit, uint256 value) external; + /// @dev EVM selector for this function is: 0x88150bd0, + /// or in textual repr: setCollectionLimit(uint8,bool,uint256) + function setCollectionLimit( + CollectionLimits limit, + bool status, + uint256 value + ) external; /// Get contract address. /// @dev EVM selector for this function is: 0xf6b4dfb4, @@ -379,6 +404,25 @@ uint256 sub; } +enum CollectionLimits { + AccountTokenOwnership, + SponsoredDataSize, + SponsoredDataRateLimit, + TokenLimit, + SponsorTransferTimeout, + SponsorApproveTimeout, + OwnerCanTransfer, + OwnerCanDestroy, + TransferEnabled +} + +/// @dev anonymous struct +struct Tuple30 { + CollectionLimits field_0; + bool field_1; + uint256 field_2; +} + /// @dev anonymous struct struct Tuple34 { CollectionPermissions field_0; --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -105,7 +105,11 @@ } /// @title A contract that allows you to work with collections. +<<<<<<< HEAD /// @dev the ERC-165 identifier for this interface is 0xb5e1747f +======= +/// @dev the ERC-165 identifier for this interface is 0xf8ebdec0 +>>>>>>> 32e011ce... added `collectionLimits` function in `Collection` interface, changed signture for `setCollectionLimit` interface Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -196,6 +200,23 @@ /// or in textual repr: collectionSponsor() function collectionSponsor() external view returns (Tuple26 memory); + /// Get current collection limits. + /// + /// @return Array of tuples (byte, bool, uint256) with limits and their values. Order of limits: + /// "accountTokenOwnershipLimit", + /// "sponsoredDataSize", + /// "sponsoredDataRateLimit", + /// "tokenLimit", + /// "sponsorTransferTimeout", + /// "sponsorApproveTimeout" + /// "ownerCanTransfer", + /// "ownerCanDestroy", + /// "transfersEnabled" + /// Return `false` if a limit not set. + /// @dev EVM selector for this function is: 0xf63bc572, + /// or in textual repr: collectionLimits() + function collectionLimits() external view returns (Tuple29[] memory); + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -209,9 +230,13 @@ /// "ownerCanDestroy", /// "transfersEnabled" /// @param value Value of the limit. - /// @dev EVM selector for this function is: 0x4ad890a8, - /// or in textual repr: setCollectionLimit(string,uint256) - function setCollectionLimit(string memory limit, uint256 value) external; + /// @dev EVM selector for this function is: 0x88150bd0, + /// or in textual repr: setCollectionLimit(uint8,bool,uint256) + function setCollectionLimit( + CollectionLimits limit, + bool status, + uint256 value + ) external; /// Get contract address. /// @dev EVM selector for this function is: 0xf6b4dfb4, @@ -380,6 +405,25 @@ uint256 sub; } +enum CollectionLimits { + AccountTokenOwnership, + SponsoredDataSize, + SponsoredDataRateLimit, + TokenLimit, + SponsorTransferTimeout, + SponsorApproveTimeout, + OwnerCanTransfer, + OwnerCanDestroy, + TransferEnabled +} + +/// @dev anonymous struct +struct Tuple29 { + CollectionLimits field_0; + bool field_1; + uint256 field_2; +} + /// @dev anonymous struct struct Tuple33 { CollectionPermissions field_0; --- a/tests/src/eth/createFTCollection.test.ts +++ b/tests/src/eth/createFTCollection.test.ts @@ -17,7 +17,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {evmToAddress} from '@polkadot/util-crypto'; import {Pallets, requirePalletsOrSkip} from '../util'; -import {expect, itEth, usingEthPlaygrounds} from './util'; +import {CollectionLimits, expect, itEth, usingEthPlaygrounds} from './util'; const DECIMALS = 18; @@ -79,6 +79,56 @@ expect(await collection.methods.description().call()).to.deep.equal(description); }); + itEth('Set limits', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId, collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Limits', DECIMALS, 'absolutely anything', 'INSI'); + const limits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + sponsoredDataRateLimit: 30, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: 0, + ownerCanDestroy: 0, + transfersEnabled: 0, + }; + + const expectedLimits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + sponsoredDataRateLimit: 30, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: false, + ownerCanDestroy: false, + transfersEnabled: false, + }; + + const collection = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); + await collection.methods.setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, limits.accountTokenOwnershipLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsoredDataSize, true, limits.sponsoredDataSize).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsoredDataRateLimit, true, limits.sponsoredDataRateLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.TokenLimit, true, limits.tokenLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsorTransferTimeout, true, limits.sponsorTransferTimeout).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsorApproveTimeout, true, limits.sponsorApproveTimeout).send(); + await collection.methods.setCollectionLimit(CollectionLimits.OwnerCanTransfer, true, limits.ownerCanTransfer).send(); + await collection.methods.setCollectionLimit(CollectionLimits.OwnerCanDestroy, true, limits.ownerCanDestroy).send(); + await collection.methods.setCollectionLimit(CollectionLimits.TransferEnabled, true, limits.transfersEnabled).send(); + + const data = (await helper.rft.getData(collectionId))!; + expect(data.raw.limits.accountTokenOwnershipLimit).to.be.eq(expectedLimits.accountTokenOwnershipLimit); + expect(data.raw.limits.sponsoredDataSize).to.be.eq(expectedLimits.sponsoredDataSize); + expect(data.raw.limits.sponsoredDataRateLimit.blocks).to.be.eq(expectedLimits.sponsoredDataRateLimit); + expect(data.raw.limits.tokenLimit).to.be.eq(expectedLimits.tokenLimit); + expect(data.raw.limits.sponsorTransferTimeout).to.be.eq(expectedLimits.sponsorTransferTimeout); + expect(data.raw.limits.sponsorApproveTimeout).to.be.eq(expectedLimits.sponsorApproveTimeout); + expect(data.raw.limits.ownerCanTransfer).to.be.eq(expectedLimits.ownerCanTransfer); + expect(data.raw.limits.ownerCanDestroy).to.be.eq(expectedLimits.ownerCanDestroy); + expect(data.raw.limits.transfersEnabled).to.be.eq(expectedLimits.transfersEnabled); + }); + itEth('Collection address exist', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233'; @@ -197,6 +247,7 @@ { await expect(peasantCollection.methods .setCollectionLimit('accountTokenOwnershipLimit', '1000') + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, 1000) .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); @@ -224,5 +275,31 @@ .setCollectionLimit('accountTokenOwnershipLimit', '1000') .call()).to.be.rejectedWith(EXPECTED_ERROR); } - }); + }); + + itEth('(!negative test!) Set limits', async ({helper}) => { + + const invalidLimits = { + accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER), + transfersEnabled: 3, + }; + + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Limits', DECIMALS, 'absolutely anything', 'ISNI'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); + await expect(collectionEvm.methods + .setCollectionLimit(20, true, '1') + .call()).to.be.rejectedWith('Returned error: VM Exception while processing transaction: revert Value not convertible into enum "CollectionLimits"'); + + await expect(collectionEvm.methods + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, invalidLimits.accountTokenOwnershipLimit) + .call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`); + + await expect(collectionEvm.methods + .setCollectionLimit(CollectionLimits.TransferEnabled, true, invalidLimits.transfersEnabled) + .call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`); + }); + + + }); --- a/tests/src/eth/createNFTCollection.test.ts +++ b/tests/src/eth/createNFTCollection.test.ts @@ -16,7 +16,7 @@ import {evmToAddress} from '@polkadot/util-crypto'; import {IKeyringPair} from '@polkadot/types/types'; -import {expect, itEth, usingEthPlaygrounds} from './util'; +import {CollectionLimits, expect, itEth, usingEthPlaygrounds} from './util'; describe('Create NFT collection from EVM', () => { @@ -120,6 +120,56 @@ expect(await sponsorCollection.methods.description().call()).to.deep.equal(description); }); + itEth('Set limits', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'FLO'); + const limits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + sponsoredDataRateLimit: 30, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: 0, + ownerCanDestroy: 0, + transfersEnabled: 0, + }; + + const expectedLimits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + sponsoredDataRateLimit: 30, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: false, + ownerCanDestroy: false, + transfersEnabled: false, + }; + + const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + await collection.methods.setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, limits.accountTokenOwnershipLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsoredDataSize, true, limits.sponsoredDataSize).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsoredDataRateLimit, true, limits.sponsoredDataRateLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.TokenLimit, true, limits.tokenLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsorTransferTimeout, true, limits.sponsorTransferTimeout).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsorApproveTimeout, true, limits.sponsorApproveTimeout).send(); + await collection.methods.setCollectionLimit(CollectionLimits.OwnerCanTransfer, true, limits.ownerCanTransfer).send(); + await collection.methods.setCollectionLimit(CollectionLimits.OwnerCanDestroy, true, limits.ownerCanDestroy).send(); + await collection.methods.setCollectionLimit(CollectionLimits.TransferEnabled, true, limits.transfersEnabled).send(); + + const data = (await helper.rft.getData(collectionId))!; + expect(data.raw.limits.accountTokenOwnershipLimit).to.be.eq(expectedLimits.accountTokenOwnershipLimit); + expect(data.raw.limits.sponsoredDataSize).to.be.eq(expectedLimits.sponsoredDataSize); + expect(data.raw.limits.sponsoredDataRateLimit.blocks).to.be.eq(expectedLimits.sponsoredDataRateLimit); + expect(data.raw.limits.tokenLimit).to.be.eq(expectedLimits.tokenLimit); + expect(data.raw.limits.sponsorTransferTimeout).to.be.eq(expectedLimits.sponsorTransferTimeout); + expect(data.raw.limits.sponsorApproveTimeout).to.be.eq(expectedLimits.sponsorApproveTimeout); + expect(data.raw.limits.ownerCanTransfer).to.be.eq(expectedLimits.ownerCanTransfer); + expect(data.raw.limits.ownerCanDestroy).to.be.eq(expectedLimits.ownerCanDestroy); + expect(data.raw.limits.transfersEnabled).to.be.eq(expectedLimits.transfersEnabled); + }); + itEth('Collection address exist', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233'; @@ -207,7 +257,7 @@ } { await expect(malfeasantCollection.methods - .setCollectionLimit('accountTokenOwnershipLimit', '1000') + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, 1000) .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); @@ -232,11 +282,30 @@ } { await expect(malfeasantCollection.methods - .setCollectionLimit('accountTokenOwnershipLimit', '1000') + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, 1000) .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); + itEth('(!negative test!) Set limits', async ({helper}) => { + const invalidLimits = { + accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER), + transfersEnabled: 3, + }; + + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'OLF'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + await expect(collectionEvm.methods + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, invalidLimits.accountTokenOwnershipLimit) + .call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`); + + await expect(collectionEvm.methods + .setCollectionLimit(CollectionLimits.TransferEnabled, true, invalidLimits.transfersEnabled) + .call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`); + }); + itEth('destroyCollection', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'OLF'); --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -17,7 +17,7 @@ import {evmToAddress} from '@polkadot/util-crypto'; import {IKeyringPair} from '@polkadot/types/types'; import {Pallets, requirePalletsOrSkip} from '../util'; -import {expect, itEth, usingEthPlaygrounds} from './util'; +import {CollectionLimits, expect, itEth, usingEthPlaygrounds} from './util'; describe('Create RFT collection from EVM', () => { @@ -152,6 +152,56 @@ expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); }); + itEth('Set limits', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'INSI'); + const limits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + sponsoredDataRateLimit: 30, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: 0, + ownerCanDestroy: 0, + transfersEnabled: 0, + }; + + const expectedLimits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + sponsoredDataRateLimit: 30, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: false, + ownerCanDestroy: false, + transfersEnabled: false, + }; + + const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + await collection.methods.setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, limits.accountTokenOwnershipLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsoredDataSize, true, limits.sponsoredDataSize).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsoredDataRateLimit, true, limits.sponsoredDataRateLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.TokenLimit, true, limits.tokenLimit).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsorTransferTimeout, true, limits.sponsorTransferTimeout).send(); + await collection.methods.setCollectionLimit(CollectionLimits.SponsorApproveTimeout, true, limits.sponsorApproveTimeout).send(); + await collection.methods.setCollectionLimit(CollectionLimits.OwnerCanTransfer, true, limits.ownerCanTransfer).send(); + await collection.methods.setCollectionLimit(CollectionLimits.OwnerCanDestroy, true, limits.ownerCanDestroy).send(); + await collection.methods.setCollectionLimit(CollectionLimits.TransferEnabled, true, limits.transfersEnabled).send(); + + const data = (await helper.rft.getData(collectionId))!; + expect(data.raw.limits.accountTokenOwnershipLimit).to.be.eq(expectedLimits.accountTokenOwnershipLimit); + expect(data.raw.limits.sponsoredDataSize).to.be.eq(expectedLimits.sponsoredDataSize); + expect(data.raw.limits.sponsoredDataRateLimit.blocks).to.be.eq(expectedLimits.sponsoredDataRateLimit); + expect(data.raw.limits.tokenLimit).to.be.eq(expectedLimits.tokenLimit); + expect(data.raw.limits.sponsorTransferTimeout).to.be.eq(expectedLimits.sponsorTransferTimeout); + expect(data.raw.limits.sponsorApproveTimeout).to.be.eq(expectedLimits.sponsorApproveTimeout); + expect(data.raw.limits.ownerCanTransfer).to.be.eq(expectedLimits.ownerCanTransfer); + expect(data.raw.limits.ownerCanDestroy).to.be.eq(expectedLimits.ownerCanDestroy); + expect(data.raw.limits.transfersEnabled).to.be.eq(expectedLimits.transfersEnabled); + }); + itEth('Collection address exist', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233'; @@ -239,7 +289,7 @@ } { await expect(peasantCollection.methods - .setCollectionLimit('accountTokenOwnershipLimit', '1000') + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, 1000) .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); @@ -264,10 +314,29 @@ } { await expect(peasantCollection.methods - .setCollectionLimit('accountTokenOwnershipLimit', '1000') + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, 1000) .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); + + itEth('(!negative test!) Set limits', async ({helper}) => { + const invalidLimits = { + accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER), + transfersEnabled: 3, + }; + + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'ISNI'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + + await expect(collectionEvm.methods + .setCollectionLimit(CollectionLimits.AccountTokenOwnership, true, invalidLimits.accountTokenOwnershipLimit) + .call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`); + + await expect(collectionEvm.methods + .setCollectionLimit(CollectionLimits.TransferEnabled, true, invalidLimits.transfersEnabled) + .call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`); + }); itEth('destroyCollection', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); --- a/tests/src/eth/util/index.ts +++ b/tests/src/eth/util/index.ts @@ -26,6 +26,17 @@ Allowlisted = 1, Generous = 2, } +export enum CollectionLimits { + AccountTokenOwnership, + SponsoredDataSize, + SponsoredDataRateLimit, + TokenLimit, + SponsorTransferTimeout, + SponsorApproveTimeout, + OwnerCanTransfer, + OwnerCanDestroy, + TransferEnabled +} export const usingEthPlaygrounds = async (code: (helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise) => Promise) => { const silentConsole = new SilentConsole();