From 02783a223963bfa3c2887683b2abfab6ec9cdf21 Mon Sep 17 00:00:00 2001 From: kozyrevdev <73348153+kozyrevdev@users.noreply.github.com> Date: Thu, 21 Apr 2022 16:32:38 +0000 Subject: [PATCH] Merge pull request #336 from UniqueNetwork/feature/CORE-325 Feature/core-325 --- --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -14,13 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +extern crate alloc; use core::{ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, }; use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight}; use frame_support::BoundedVec; -use up_data_structs::TokenId; +use up_data_structs::{TokenId, SchemaVersion}; use pallet_evm_coder_substrate::dispatch_to_evm; use sp_core::{H160, U256}; use sp_std::{vec::Vec, vec}; @@ -35,6 +36,15 @@ SelfWeightOf, weights::WeightInfo, }; +fn error_unsupported_schema_version() -> Error { + alloc::format!( + "Unsupported schema version! Support only {:?}", + SchemaVersion::ImageURL + ) + .as_str() + .into() +} + #[derive(ToLog)] pub enum ERC721Events { Transfer { @@ -76,6 +86,7 @@ .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) .collect::()) } + fn symbol(&self) -> Result { Ok(string::from_utf8_lossy(&self.token_prefix).into()) } @@ -83,6 +94,10 @@ /// Returns token's const_metadata #[solidity(rename_selector = "tokenURI")] fn token_uri(&self, token_id: uint256) -> Result { + if !matches!(self.schema_version, SchemaVersion::ImageURL) { + return Err(error_unsupported_schema_version()); + } + self.consume_store_reads(1)?; let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?; Ok(string::from_utf8_lossy( @@ -270,6 +285,10 @@ token_id: uint256, token_uri: string, ) -> Result { + if !matches!(self.schema_version, SchemaVersion::ImageURL) { + return Err(error_unsupported_schema_version()); + } + let caller = T::CrossAccountId::from_eth(caller); let to = T::CrossAccountId::from_eth(to); let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?; @@ -411,6 +430,10 @@ to: address, tokens: Vec<(uint256, string)>, ) -> Result { + if !matches!(self.schema_version, SchemaVersion::ImageURL) { + return Err(error_unsupported_schema_version()); + } + let caller = T::CrossAccountId::from_eth(caller); let to = T::CrossAccountId::from_eth(to); let mut expected_index = >::get(self.id) --- a/tests/src/eth/collectionSponsoring.test.ts +++ b/tests/src/eth/collectionSponsoring.test.ts @@ -1,19 +1,16 @@ import privateKey from '../substrate/privateKey'; import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, setCollectionSponsorExpectSuccess} from '../util/helpers'; -import {itWeb3, transferBalanceToEth, subToEth, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents} from './util/helpers'; +import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents} from './util/helpers'; import nonFungibleAbi from './nonFungibleAbi.json'; import {expect} from 'chai'; describe('evm collection sponsoring', () => { - itWeb3('sponsors mint transactions', async ({api, web3}) => { + itWeb3('sponsors mint transactions', async ({web3}) => { const alice = privateKey('//Alice'); const collection = await createCollectionExpectSuccess(); await setCollectionSponsorExpectSuccess(collection, alice.address); await confirmSponsorshipExpectSuccess(collection); - - // Wouldn't be needed after CORE-300 - await transferBalanceToEth(api, alice, subToEth(alice.address)); const minter = createEthAccount(web3); expect(await web3.eth.getBalance(minter)).to.equal('0'); --- a/tests/src/eth/metadata.test.ts +++ b/tests/src/eth/metadata.test.ts @@ -16,8 +16,11 @@ import {expect} from 'chai'; import {createCollectionExpectSuccess} from '../util/helpers'; -import {collectionIdToAddress, createEthAccountWithBalance, GAS_ARGS, itWeb3} from './util/helpers'; +import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents} from './util/helpers'; import fungibleMetadataAbi from './fungibleMetadataAbi.json'; +import privateKey from '../substrate/privateKey'; +import {submitTransactionAsync} from '../substrate/substrate-api'; +import nonFungibleAbi from './nonFungibleAbi.json'; describe('Common metadata', () => { itWeb3('Returns collection name', async ({api, web3}) => { @@ -62,4 +65,146 @@ expect(+decimals).to.equal(6); }); -}); \ No newline at end of file +}); + +describe('Support ERC721Metadata', () => { + itWeb3('Check unsupport ERC721Metadata SchemaVersion::Unique', async ({web3, api}) => { + const collectionId = await createCollectionExpectSuccess({ + mode: {type: 'NFT'}, + schemaVersion: 'Unique', + name: 'some_name', + tokenPrefix: 'some_prefix', + }); + const collection = await api.rpc.unique.collectionById(collectionId); + expect(collection.isSome).to.be.true; + expect(collection.unwrap().schemaVersion.toHuman()).to.be.eq('Unique'); + + const alice = privateKey('//Alice'); + + const caller = await createEthAccountWithBalance(api, web3); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, {Ethereum: caller}); + await submitTransactionAsync(alice, changeAdminTx); + + const address = collectionIdToAddress(collectionId); + const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + + expect(await contract.methods.name().call()).to.be.eq('some_name'); + expect(await contract.methods.symbol().call()).to.be.eq('some_prefix'); + + const receiver = createEthAccount(web3); + const nextTokenId = await contract.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + await expect(contract.methods.mintWithTokenURI( + receiver, + nextTokenId, + 'Test URI', + ).call({from: caller})).to.be.rejectedWith('Unsupported schema version! Support only ImageURL'); + + await expect(contract.methods.mintBulkWithTokenURI( + receiver, + [ + [nextTokenId, 'Test URI 0'], + [+nextTokenId + 1, 'Test URI 1'], + [+nextTokenId + 2, 'Test URI 2'], + ], + ).call({from: caller})).to.be.rejectedWith('Unsupported schema version! Support only ImageURL'); + }); + + itWeb3('Check support ERC721Metadata for SchemaVersion::ImageURL', async ({web3, api}) => { + const collectionId = await createCollectionExpectSuccess({ + mode: {type: 'NFT'}, + name: 'some_name', + tokenPrefix: 'some_prefix', + }); + const collection = await api.rpc.unique.collectionById(collectionId); + expect(collection.isSome).to.be.true; + expect(collection.unwrap().schemaVersion.toHuman()).to.be.eq('ImageURL'); + + const alice = privateKey('//Alice'); + + const caller = await createEthAccountWithBalance(api, web3); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, {Ethereum: caller}); + await submitTransactionAsync(alice, changeAdminTx); + + const address = collectionIdToAddress(collectionId); + const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + + expect(await contract.methods.name().call()).to.be.eq('some_name'); + expect(await contract.methods.symbol().call()).to.be.eq('some_prefix'); + + const receiver = createEthAccount(web3); + { // mintWithTokenURI + const nextTokenId = await contract.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + const result = await contract.methods.mintWithTokenURI( + receiver, + nextTokenId, + 'Test URI', + ).send({from: caller}); + const events = normalizeEvents(result.events); + + expect(events).to.be.deep.equal([ + { + address, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: receiver, + tokenId: nextTokenId, + }, + }, + ]); + + expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); + } + + { // mintBulkWithTokenURI + const nextTokenId = await contract.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('2'); + const result = await contract.methods.mintBulkWithTokenURI( + receiver, + [ + [nextTokenId, 'Test URI 0'], + [+nextTokenId + 1, 'Test URI 1'], + [+nextTokenId + 2, 'Test URI 2'], + ], + ).send({from: caller}); + const events = normalizeEvents(result.events); + + expect(events).to.be.deep.equal([ + { + address, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: receiver, + tokenId: nextTokenId, + }, + }, + { + address, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: receiver, + tokenId: String(+nextTokenId + 1), + }, + }, + { + address, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: receiver, + tokenId: String(+nextTokenId + 2), + }, + }, + ]); + + expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0'); + expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1'); + expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2'); + } + }); +}); + --- a/tests/src/setConstOnChainSchema.test.ts +++ b/tests/src/setConstOnChainSchema.test.ts @@ -32,16 +32,16 @@ let alice: IKeyringPair; let bob: IKeyringPair; -let shema: any; -let largeShema: any; +let schema: any; +let largeSchema: any; before(async () => { await usingApi(async () => { const keyring = new Keyring({type: 'sr25519'}); alice = keyring.addFromUri('//Alice'); bob = keyring.addFromUri('//Bob'); - shema = '0x31'; - largeShema = new Array(1024 * 1024 + 10).fill(0xff); + schema = '0x31'; + largeSchema = new Array(1024 * 1024 + 10).fill(0xff); }); }); describe('Integration Test ext. setConstOnChainSchema()', () => { @@ -51,8 +51,8 @@ const collectionId = await createCollectionExpectSuccess(); const collection = await queryCollectionExpectSuccess(api, collectionId); expect(collection.owner.toString()).to.be.eq(alice.address); - const setShema = api.tx.unique.setConstOnChainSchema(collectionId, shema); - await submitTransactionAsync(alice, setShema); + const setSchema = api.tx.unique.setConstOnChainSchema(collectionId, schema); + await submitTransactionAsync(alice, setSchema); }); }); @@ -62,18 +62,18 @@ const collection = await queryCollectionExpectSuccess(api, collectionId); expect(collection.owner.toString()).to.be.eq(alice.address); await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); - const setShema = api.tx.unique.setConstOnChainSchema(collectionId, shema); - await submitTransactionAsync(bob, setShema); + const setSchema = api.tx.unique.setConstOnChainSchema(collectionId, schema); + await submitTransactionAsync(bob, setSchema); }); }); it('Checking collection data using the ConstOnChainSchema parameter', async () => { await usingApi(async (api) => { const collectionId = await createCollectionExpectSuccess(); - const setShema = api.tx.unique.setConstOnChainSchema(collectionId, shema); - await submitTransactionAsync(alice, setShema); + const setSchema = api.tx.unique.setConstOnChainSchema(collectionId, schema); + await submitTransactionAsync(alice, setSchema); const collection = await queryCollectionExpectSuccess(api, collectionId); - expect(collection.constOnChainSchema.toString()).to.be.eq(shema); + expect(collection.constOnChainSchema.toString()).to.be.eq(schema); }); }); }); @@ -84,8 +84,8 @@ await usingApi(async (api) => { // tslint:disable-next-line: radix const collectionId = await getCreatedCollectionCount(api) + 1; - const setShema = api.tx.unique.setConstOnChainSchema(collectionId, shema); - await expect(submitTransactionExpectFailAsync(alice, setShema)).to.be.rejected; + const setSchema = api.tx.unique.setConstOnChainSchema(collectionId, schema); + await expect(submitTransactionExpectFailAsync(alice, setSchema)).to.be.rejected; }); }); @@ -93,16 +93,16 @@ await usingApi(async (api) => { const collectionId = await createCollectionExpectSuccess(); await destroyCollectionExpectSuccess(collectionId); - const setShema = api.tx.unique.setConstOnChainSchema(collectionId, shema); - await expect(submitTransactionExpectFailAsync(alice, setShema)).to.be.rejected; + const setSchema = api.tx.unique.setConstOnChainSchema(collectionId, schema); + await expect(submitTransactionExpectFailAsync(alice, setSchema)).to.be.rejected; }); }); it('Set invalid data in schema (size too large:> 1MB)', async () => { await usingApi(async (api) => { const collectionId = await createCollectionExpectSuccess(); - const setShema = api.tx.unique.setConstOnChainSchema(collectionId, largeShema); - await expect(submitTransactionExpectFailAsync(alice, setShema)).to.be.rejected; + const setSchema = api.tx.unique.setConstOnChainSchema(collectionId, largeSchema); + await expect(submitTransactionExpectFailAsync(alice, setSchema)).to.be.rejected; }); }); @@ -111,8 +111,8 @@ const collectionId = await createCollectionExpectSuccess(); const collection = await queryCollectionExpectSuccess(api, collectionId); expect(collection.owner.toString()).to.be.eq(alice.address); - const setShema = api.tx.unique.setConstOnChainSchema(collectionId, shema); - await expect(submitTransactionExpectFailAsync(bob, setShema)).to.be.rejected; + const setSchema = api.tx.unique.setConstOnChainSchema(collectionId, schema); + await expect(submitTransactionExpectFailAsync(bob, setSchema)).to.be.rejected; }); }); --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -268,6 +268,7 @@ name: string, description: string, tokenPrefix: string, + schemaVersion: string, }; const defaultCreateCollectionParams: CreateCollectionParams = { @@ -275,10 +276,11 @@ mode: {type: 'NFT'}, name: 'name', tokenPrefix: 'prefix', + schemaVersion: 'ImageURL', }; export async function createCollectionExpectSuccess(params: Partial = {}): Promise { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + const {name, description, mode, tokenPrefix, schemaVersion} = {...defaultCreateCollectionParams, ...params}; let collectionId = 0; await usingApi(async (api) => { @@ -297,7 +299,13 @@ modeprm = {refungible: null}; } - const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any}); + const tx = api.tx.unique.createCollectionEx({ + name: strToUTF16(name), + description: strToUTF16(description), + tokenPrefix: strToUTF16(tokenPrefix), + mode: modeprm as any, + schemaVersion: schemaVersion, + }); const events = await submitTransactionAsync(alicePrivateKey, tx); const result = getCreateCollectionResult(events); -- gitstuff