--- a/Cargo.lock +++ b/Cargo.lock @@ -6091,6 +6091,7 @@ "pallet-nonfungible", "parity-scale-codec 3.1.2", "scale-info", + "serde_json", "sp-core", "sp-runtime", "sp-std", --- a/pallets/evm-collection/Cargo.toml +++ b/pallets/evm-collection/Cargo.toml @@ -10,6 +10,7 @@ ] } ethereum = { version = "0.12.0", default-features = false } log = { default-features = false, version = "0.4.14" } +serde_json = { version = "1.0.68", default-features = false, features = ["alloc"] } # Substrate frame-support = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.21' } @@ -27,7 +28,7 @@ pallet-common = { default-features = false, path = '../../pallets/common' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } pallet-nonfungible = { default-features = false, path = '../../pallets/nonfungible' } -up-data-structs = { default-features = false, path = '../../primitives/data-structs' } +up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = ["serde1"] } [dependencies.codec] default-features = false --- a/pallets/evm-collection/src/eth.rs +++ b/pallets/evm-collection/src/eth.rs @@ -112,21 +112,74 @@ // Ok(()) // } - // fn set_offchain_shema(shema: string) -> Result { - // Ok(()) - // } + fn set_offchain_shema(shema: string) -> Result { + let shema = shema + .into_bytes() + .try_into() + .map_err(|_| error_feild_too_long(stringify!(shema), OFFCHAIN_SCHEMA_LIMIT))?; + collection.offchain_schema = shema; + save(collection) + } - // fn set_const_on_chain_schema(shema: string) -> Result { - // Ok(()) - // } + fn confirm_sponsorship(&self, caller: caller, collection_address: address) -> Result { + let (_, mut collection) = collection_from_address(collection_address, &self.0)?; + let caller = T::CrossAccountId::from_eth(caller); + if !collection.confirm_sponsorship(caller.as_sub()) { + return Err(Error::Revert("Caller is not set as sponsor".into())); + } + save(collection) + } // fn set_variable_on_chain_schema(shema: string) -> Result { // Ok(()) // } - // fn set_limits(limits: string) -> Result { - // Ok(()) - // } + fn set_variable_on_chain_schema( + &self, + caller: caller, + collection_address: address, + variable: string, + ) -> Result { + let (_, mut collection) = collection_from_address(collection_address, &self.0)?; + check_is_owner(caller, &collection)?; + + let variable = variable.into_bytes().try_into().map_err(|_| { + error_feild_too_long(stringify!(variable), VARIABLE_ON_CHAIN_SCHEMA_LIMIT) + })?; + collection.variable_on_chain_schema = variable; + save(collection) + } + + fn set_const_on_chain_schema( + &self, + caller: caller, + collection_address: address, + const_on_chain: string, + ) -> Result { + let (_, mut collection) = collection_from_address(collection_address, &self.0)?; + check_is_owner(caller, &collection)?; + + let const_on_chain = const_on_chain.into_bytes().try_into().map_err(|_| { + error_feild_too_long(stringify!(const_on_chain), CONST_ON_CHAIN_SCHEMA_LIMIT) + })?; + collection.const_on_chain_schema = const_on_chain; + save(collection) + } + + fn set_limits( + &self, + caller: caller, + collection_address: address, + limits_json: string, + ) -> Result { + let (_, mut collection) = collection_from_address(collection_address, &self.0)?; + check_is_owner(caller, &collection)?; + + let limits = serde_json::from_str(limits_json.as_ref()) + .map_err(|e| Error::Revert(format!("Parse JSON error: {}", e)))?; + collection.limits = limits; + save(collection) + } } fn error_feild_too_long(feild: &str, bound: u32) -> Error { --- a/pallets/evm-collection/src/stubs/Collection.sol +++ b/pallets/evm-collection/src/stubs/Collection.sol @@ -21,7 +21,7 @@ } } -// Selector: d32d5104 +// Selector: 037b69c8 contract Collection is Dummy, ERC165 { // Selector: create721Collection(string,string,string) 951c0151 function create721Collection( @@ -87,4 +87,15 @@ constOnChain; dummy; } + + // Selector: setLimits(address,string) d05638cc + function setLimits(address collectionAddress, string memory limitsJson) + public + view + { + require(false, stub_error); + collectionAddress; + limitsJson; + dummy; + } } --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -506,6 +506,7 @@ } #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum MetaUpdatePermission { ItemOwner, Admin, --- a/tests/src/eth/api/Collection.sol +++ b/tests/src/eth/api/Collection.sol @@ -12,7 +12,7 @@ function supportsInterface(bytes4 interfaceID) external view returns (bool); } -// Selector: d32d5104 +// Selector: 037b69c8 interface Collection is Dummy, ERC165 { // Selector: create721Collection(string,string,string) 951c0151 function create721Collection( @@ -45,4 +45,9 @@ address collectionAddress, string memory constOnChain ) external view; + + // Selector: setLimits(address,string) d05638cc + function setLimits(address collectionAddress, string memory limitsJson) + external + view; } --- a/tests/src/eth/collectionAbi.json +++ b/tests/src/eth/collectionAbi.json @@ -44,6 +44,20 @@ "name": "collectionAddress", "type": "address" }, + { "internalType": "string", "name": "limitsJson", "type": "string" } + ], + "name": "setLimits", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "collectionAddress", + "type": "address" + }, { "internalType": "string", "name": "shema", "type": "string" } ], "name": "setOffchainShema", --- a/tests/src/eth/createCollection.test.ts +++ b/tests/src/eth/createCollection.test.ts @@ -100,4 +100,45 @@ const collection = (await getDetailedCollectionInfo(api, collectionId))!; expect(collection.constOnChainSchema.toHuman()).to.be.eq(constShema); }); + + itWeb3('Set limits', async ({api, web3}) => { + const owner = await createEthAccountWithBalance(api, web3); + const helper = collectionHelper(web3, owner); + const result = await helper.methods.create721Collection('Const collection', '4', '4').send(); + const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); + const limits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + // sponsoredDataRateLimit: { sponsoringDisabled: null }, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: false, + ownerCanDestroy: false, + transfersEnabled: false, + }; + const limitsJson = '{' + + '"account_token_ownership_limit": '+ limits.accountTokenOwnershipLimit +',' + + '"sponsored_data_size": ' + limits.sponsoredDataSize + ',' + + // '"sponsored_data_rate_limit": { sponsoringDisabled: null },' + + '"token_limit": ' + limits.tokenLimit + ',' + + '"sponsor_transfer_timeout": ' + limits.sponsorTransferTimeout + ',' + + '"sponsor_approve_timeout": ' + limits.sponsorApproveTimeout + ',' + + '"owner_can_transfer": ' + limits.ownerCanTransfer + ',' + + '"owner_can_destroy": ' + limits.ownerCanDestroy + ',' + + '"transfers_enabled": ' + limits.transfersEnabled + + '}'; + + await helper.methods.setLimits(collectionIdAddress, limitsJson).send(); + + const collection = (await getDetailedCollectionInfo(api, collectionId))!; + expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.be.eq(limits.accountTokenOwnershipLimit); + expect(collection.limits.sponsoredDataSize.unwrap().toNumber()).to.be.eq(limits.sponsoredDataSize); + expect(collection.limits.tokenLimit.unwrap().toNumber()).to.be.eq(limits.tokenLimit); + expect(collection.limits.sponsorTransferTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorTransferTimeout); + expect(collection.limits.sponsorApproveTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorApproveTimeout); + expect(collection.limits.ownerCanTransfer.toHuman()).to.be.eq(limits.ownerCanTransfer); + expect(collection.limits.ownerCanDestroy.toHuman()).to.be.eq(limits.ownerCanDestroy); + expect(collection.limits.transfersEnabled.toHuman()).to.be.eq(limits.transfersEnabled); + }); }); \ No newline at end of file