--- /dev/null +++ b/tests/src/eth/contractSponsoring.test.ts @@ -0,0 +1,96 @@ +// +// This file is subject to the terms and conditions defined in +// file 'LICENSE', which is part of this source code package. +// + +import privateKey from '../substrate/privateKey'; +import { expect } from 'chai'; +import { createCollectionExpectSuccess, + createFungibleItemExpectSuccess, + transferExpectSuccess, + transferFromExpectSuccess, + createItemExpectSuccess, + setContractSponsoringRateLimitExpectSuccess, + enableContractSponsoringExpectSuccess, + setCollectionSponsorExpectSuccess, + confirmSponsorshipExpectSuccess, + enableContractSponsoringExpectFailure} from '../util/helpers'; +import { collectionIdToAddress, + contractHelpers, + createEthAccountWithBalance, + createEthAccount, + transferBalanceToEth, + subToEth, + deployFlipper, + usingWeb3Http, + deployFungibleContract, + GAS_ARGS, itWeb3 } from './util/helpers'; +import waitNewBlocks from '../substrate/wait-new-blocks'; +import fungibleAbi from './fungibleAbi.json'; +import nonFungibleAbi from './nonFungibleAbi.json'; + +describe.only('Sponsoring EVM contracts', () => { + itWeb3.skip('Sponsoring can be set by the address that has deployed the contract', async ({api, web3}) => { + await usingWeb3Http(async web3Http => { + const owner = await createEthAccountWithBalance(api, web3Http); + const fungible = await deployFungibleContract(web3Http, owner); + await waitNewBlocks(api, 1); + const helpers = contractHelpers(web3Http, owner); + await waitNewBlocks(api, 1); + expect(await helpers.methods.sponsoringEnabled(fungible.options.address).call()).to.be.false; + await waitNewBlocks(api, 1); + await helpers.methods.toggleSponsoring(fungible.options.address, true).send({from: owner}); + await waitNewBlocks(api, 1); + expect(await helpers.methods.sponsoringEnabled(fungible.options.address).call()).to.be.true; + }); + }); + + itWeb3.skip('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3}) => { + await usingWeb3Http(async web3Http => { + const owner = await createEthAccountWithBalance(api, web3Http); + const notOwner = await createEthAccountWithBalance(api, web3Http); + const fungible = await deployFungibleContract(web3Http, owner); + await waitNewBlocks(api, 1); + const helpers = contractHelpers(web3Http, owner); + await waitNewBlocks(api, 1); + expect(await helpers.methods.sponsoringEnabled(fungible.options.address).call()).to.be.false; + await waitNewBlocks(api, 1); + await expect(helpers.methods.toggleSponsoring(notOwner, true).send({from: notOwner})).to.rejected; + await waitNewBlocks(api, 1); + expect(await helpers.methods.sponsoringEnabled(fungible.options.address).call()).to.be.false; + }); + }); + + itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works', async ({api, web3}) => { + await usingWeb3Http(async web3Http => { + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const collection = await createCollectionExpectSuccess({ + mode: { type: 'Fungible', decimalPoints: 0 }, + }); + await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, alice.address); + const owner = await createEthAccountWithBalance(api, web3Http); + const notOwner = await createEthAccountWithBalance(api, web3Http); + await transferExpectSuccess(collection, 0, alice, { ethereum: notOwner } , 200, 'Fungible'); + const address = collectionIdToAddress(collection); + const fungible = await deployFungibleContract(web3Http, address); + await transferBalanceToEth(api, alice, fungible.options.address); + await waitNewBlocks(api, 1); + const balanceBefore = await web3.eth.getBalance(fungible.options.address); + + const helpers = contractHelpers(web3Http, address); + await waitNewBlocks(api, 1); + expect(await helpers.methods.sponsoringEnabled(fungible.options.address).call()).to.be.false; + await waitNewBlocks(api, 1); + await helpers.methods.toggleSponsoring(fungible.options.address, true).send({from: owner}); + await waitNewBlocks(api, 1); + expect(await helpers.methods.sponsoringEnabled(fungible.options.address).call()).to.be.true; + +// await fungible.methods.approve(owner, 50).send({ from: owner }); + await fungible.methods.transferFrom(notOwner, owner, 50).send({ from: notOwner }); + const balanceAfter = await web3.eth.getBalance(fungible.options.address); + expect(+balanceAfter).to.lessThan(+balanceBefore); + }); + }); + +}); --- /dev/null +++ b/tests/src/eth/util/ERC721.sol @@ -0,0 +1,159 @@ +contract ERC721 { + uint8 _dummy = 0; + address _dummy_addr = 0x0000000000000000000000000000000000000000; + string _dummy_string = ""; + string stub_error = + "this contract does not exists, code for collections is implemented at pallet side"; + + event Transfer( + address indexed from, + address indexed to, + uint256 indexed tokenId + ); + + event Approval( + address indexed owner, + address indexed approved, + uint256 indexed tokenId + ); + + event ApprovalForAll( + address indexed owner, + address indexed operator, + bool approved + ); + + // 0x18160ddd + function totalSupply() external view returns (uint256) { + require(false, stub_error); + return 0; + } + + function name() external view returns (string memory res_name) { + require(false, stub_error); + res_name = _dummy_string; + } + + function symbol() external view returns (string memory res_symbol) { + require(false, stub_error); + res_symbol = _dummy_string; + } + + function tokenURI(uint256 tokenId) external view returns (string memory) { + require(false, stub_error); + tokenId; + return _dummy_string; + } + + function tokenByIndex(uint256 index) external view returns (uint256) { + require(false, stub_error); + index; + return 0; + } + + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) { + require(false, stub_error); + owner; + index; + return 0; + } + + // 0x70a08231 + function balanceOf(address owner) external view returns (uint256) { + require(false, stub_error); + owner; + return 0; + } + + // 0x6352211e + function ownerOf(uint256 tokenId) external view returns (address) { + require(false, stub_error); + tokenId; + return _dummy_addr; + } + + // 0xb88d4fde + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external payable { + require(false, stub_error); + from; + to; + tokenId; + data; + } + + // 0x42842e0e + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external payable { + require(false, stub_error); + from; + to; + tokenId; + } + + // 0x23b872dd + function transferFrom( + address from, + address to, + uint256 tokenId + ) external payable { + require(false, stub_error); + from; + to; + tokenId; + } + + // 0x095ea7b3 + function approve(address approved, uint256 tokenId) external payable { + require(false, stub_error); + approved; + tokenId; + } + + // 0xa22cb465 + function setApprovalForAll(address operator, bool approved) external { + require(false, stub_error); + operator; + approved; + _dummy = 0; + } + + // 0x081812fc + function getApproved(uint256 tokenId) external view returns (address) { + require(false, stub_error); + tokenId; + return _dummy_addr; + } + + // 0xe985e9c5 + function isApprovedForAll(address owner, address operator) + external + view + returns (bool) + { + require(false, stub_error); + owner; + operator; + return false; + } + + // 0x01ffc9a7 + function supportsInterface(bytes4 interfaceID) public pure returns (bool) { + return + // ERC721 + interfaceID == 0x80ac58cd || + // ERC721Metadata + interfaceID == 0x5b5e139f || + // ERC721Enumerable + interfaceID == 0x780e9d63 || + // ERC165 + interfaceID == 0x01ffc9a7; + } +} --- a/tests/src/eth/util/helpers.ts +++ b/tests/src/eth/util/helpers.ts @@ -17,6 +17,7 @@ import config from '../../config'; import privateKey from '../../substrate/privateKey'; import contractHelpersAbi from './contractHelpersAbi.json'; +import fs from 'fs'; export const GAS_ARGS = { gas: 0x1000000, gasPrice: '0x01' }; @@ -175,6 +176,21 @@ }; } +export async function deployFungibleContract(web3: Web3 & Web3HttpMarker, deployer: string) { + + const sol = fs.readFileSync(__dirname + '/ERC721.sol').toString(); + + const compiled = compileContract('ERC721', sol); + const ERC721 = new web3.eth.Contract(compiled.abi, undefined, { + data: compiled.object, + from: deployer, + ...GAS_ARGS, + }); + const fungible = await ERC721.deploy({ data: compiled.object }).send({from: deployer}); + + return fungible; +} + export async function deployFlipper(web3: Web3 & Web3HttpMarker, deployer: string) { const compiled = compileContract('Flipper', ` contract Flipper { --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -95,23 +95,6 @@ checkMsgSysMethod: string; } -export interface IFungibleTokenDataType { - Value: number; -} - -export interface IChainLimits { - CollectionNumbersLimit: number; - AccountTokenOwnershipLimit: number; - CollectionsAdminsLimit: number; - CustomDataLimit: number; - NftSponsorTransferTimeout: number; - FungibleSponsorTransferTimeout: number; - RefungibleSponsorTransferTimeout: number; - OffchainSchemaLimit: number; - VariableOnChainSchemaLimit: number; - ConstOnChainSchemaLimit: number; -} - export interface IReFungibleTokenDataType { Owner: IReFungibleOwner[]; ConstData: number[]; @@ -469,11 +452,11 @@ }); } -export async function removeCollectionSponsorExpectFailure(collectionId: number, senderSeed = '//Alice') { +export async function removeCollectionSponsorExpectFailure(collectionId: number) { await usingApi(async (api) => { // Run the transaction - const alicePrivateKey = privateKey(senderSeed); + const alicePrivateKey = privateKey('//Alice'); const tx = api.tx.nft.removeCollectionSponsor(collectionId); await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; }); @@ -782,15 +765,6 @@ }); } -export async function addCollectionAdminExpectSuccess(sender: IKeyringPair, collectionId: number, address: IKeyringPair) { - await usingApi(async (api) => { - const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(address.address)); - const events = await submitTransactionAsync(sender, changeAdminTx); - const result = getCreateCollectionResult(events); - expect(result.success).to.be.true; - }); -} - export async function scheduleTransferExpectSuccess( collectionId: number, @@ -1040,17 +1014,6 @@ await usingApi(async (api) => { // Run the transaction const tx = api.tx.nft.setMintPermission(collectionId, enabled); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function setChainLimitsExpectFailure(sender: IKeyringPair, limits: IChainLimits) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.nft.setChainLimits(limits); const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; const result = getCreateCollectionResult(events); // tslint:disable-next-line:no-unused-expression