1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';1819import {itEth, expect, usingEthPlaygrounds, EthUniqueHelper} from './util';2021describe('EVM payable contracts', () => {22 let donor: IKeyringPair;2324 before(async function() {25 await usingEthPlaygrounds(async (_, privateKey) => {26 donor = await privateKey({filename: __filename});27 });28 });2930 itEth('Evm contract can receive wei from eth account', async ({helper}) => {31 const deployer = await helper.eth.createAccountWithBalance(donor);32 const contract = await helper.eth.deployCollectorContract(deployer);3334 const web3 = helper.getWeb3();3536 await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', gas: helper.eth.DEFAULT_GAS});3738 expect(await contract.methods.getCollected().call()).to.be.equal('10000');39 });4041 itEth('Evm contract can receive wei from substrate account', async ({helper}) => {42 const deployer = await helper.eth.createAccountWithBalance(donor);43 const contract = await helper.eth.deployCollectorContract(deployer);44 const [alice] = await helper.arrange.createAccounts([10n], donor);4546 const weiCount = '10000';4748 49 50 await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n);515253 await helper.eth.sendEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount);5455 expect(await contract.methods.getCollected().call()).to.be.equal(weiCount);56 });5758 59 itEth('Wei sent directly to backing storage of evm contract balance is unaccounted', async({helper}) => {60 const deployer = await helper.eth.createAccountWithBalance(donor);61 const contract = await helper.eth.deployCollectorContract(deployer);62 const [alice] = await helper.arrange.createAccounts([10n], donor);6364 const weiCount = 10_000n;6566 await helper.eth.transferBalanceFromSubstrate(alice, contract.options.address, weiCount, false);6768 expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString());69 });7071 itEth('Balance can be retrieved from evm contract', async({helper}) => {72 const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal();73 const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal();7475 const deployer = await helper.eth.createAccountWithBalance(donor);76 const contract = await helper.eth.deployCollectorContract(deployer);77 const [alice] = await helper.arrange.createAccounts([20n], donor);7879 const web3 = helper.getWeb3();8081 await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS});8283 const [receiver] = await helper.arrange.createAccounts([0n], donor);8485 86 {87 const ethReceiver = helper.address.substrateToEth(receiver.address);88 expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0');89 await contract.methods.withdraw(ethReceiver).send({from: deployer});90 expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString());91 }9293 94 await helper.balance.transferToSubstrate(alice, receiver.address, FEE_BALANCE);95 9697 98 {99 const initialReceiverBalance = await helper.balance.getSubstrate(receiver.address);100 await helper.executeExtrinsic(receiver, 'api.tx.evm.withdraw', [helper.address.substrateToEth(receiver.address), CONTRACT_BALANCE.toString()], true);101 const finalReceiverBalance = await helper.balance.getSubstrate(receiver.address);102103 expect(finalReceiverBalance > initialReceiverBalance).to.be.true;104 }105 });106});107108describe('EVM transaction fees', () => {109 let donor: IKeyringPair;110111 before(async function() {112 await usingEthPlaygrounds(async (_, privateKey) => {113 donor = await privateKey({filename: __filename});114 });115 });116117 itEth('Fee is withdrawn from the user', async({helper}) => {118 const deployer = await helper.eth.createAccountWithBalance(donor);119 const caller = await helper.eth.createAccountWithBalance(donor);120 const contract = await helper.eth.deployFlipper(deployer);121 122 const initialCallerBalance = await helper.balance.getEthereum(caller);123 await contract.methods.flip().send({from: caller});124 const finalCallerBalance = await helper.balance.getEthereum(caller);125 expect(finalCallerBalance < initialCallerBalance).to.be.true;126 });127128 itEth('Fee for nested calls is withdrawn from the user', async({helper}) => {129 const deployer = await helper.eth.createAccountWithBalance(donor);130 const caller = await helper.eth.createAccountWithBalance(donor);131 const contract = await deployProxyContract(helper, deployer);132 133 const initialCallerBalance = await helper.balance.getEthereum(caller);134 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);135 await contract.methods.flip().send({from: caller});136 const finalCallerBalance = await helper.balance.getEthereum(caller);137 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);138 expect(finalCallerBalance < initialCallerBalance).to.be.true;139 expect(finalContractBalance == initialContractBalance).to.be.true;140 });141 142 itEth('Fee for nested calls to native methods is withdrawn from the user', async({helper}) => {143 const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();144145 const deployer = await helper.eth.createAccountWithBalance(donor);146 const caller = await helper.eth.createAccountWithBalance(donor);147 const contract = await deployProxyContract(helper, deployer);148149 const collectionAddress = (await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;150 const initialCallerBalance = await helper.balance.getEthereum(caller);151 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);152 await contract.methods.mintNftToken(collectionAddress).send({from: caller});153 const finalCallerBalance = await helper.balance.getEthereum(caller);154 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);155 expect(finalCallerBalance < initialCallerBalance).to.be.true;156 expect(finalContractBalance == initialContractBalance).to.be.true;157 });158 159 itEth('Fee for nested calls to create*Collection methods is withdrawn from the user and from the contract', async({helper}) => {160 const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();161 const deployer = await helper.eth.createAccountWithBalance(donor);162 const caller = await helper.eth.createAccountWithBalance(donor);163 const contract = await deployProxyContract(helper, deployer);164165 const initialCallerBalance = await helper.balance.getEthereum(caller);166 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);167 await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});168 const finalCallerBalance = await helper.balance.getEthereum(caller);169 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);170 expect(finalCallerBalance < initialCallerBalance).to.be.true;171 expect(finalContractBalance == initialContractBalance).to.be.true;172 });173174 itEth('Negative test: call createNFTCollection with wrong fee', async({helper}) => {175 const SMALL_FEE = 1n * helper.balance.getOneTokenNominal();176 const BIG_FEE = 3n * helper.balance.getOneTokenNominal();177 const caller = await helper.eth.createAccountWithBalance(donor);178 const collectionHelper = helper.ethNativeContract.collectionHelpers(caller);179 180 await expect(collectionHelper.methods.createNonfungibleCollection('A', 'B', 'C').call({value: Number(SMALL_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');181 await expect(collectionHelper.methods.createNonfungibleCollection('A', 'B', 'C').call({value: Number(BIG_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');182 });183184 itEth('Negative test: call createRFTCollection with wrong fee', async({helper}) => {185 const SMALL_FEE = 1n * helper.balance.getOneTokenNominal();186 const BIG_FEE = 3n * helper.balance.getOneTokenNominal();187 const caller = await helper.eth.createAccountWithBalance(donor);188 const collectionHelper = helper.ethNativeContract.collectionHelpers(caller);189 190 await expect(collectionHelper.methods.createRFTCollection('A', 'B', 'C').call({value: Number(SMALL_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');191 await expect(collectionHelper.methods.createRFTCollection('A', 'B', 'C').call({value: Number(BIG_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');192 });193194 itEth('Get collection creation fee', async({helper}) => {195 const deployer = await helper.eth.createAccountWithBalance(donor);196 expect(await helper.eth.getCollectionCreationFee(deployer)).to.be.equal(String(2n * helper.balance.getOneTokenNominal()));197 });198199 async function deployProxyContract(helper: EthUniqueHelper, deployer: string) {200 return await helper.ethContract.deployByCode(201 deployer,202 'ProxyContract',203 `204 // SPDX-License-Identifier: UNLICENSED205 pragma solidity ^0.8.6;206207 import {CollectionHelpers} from "../api/CollectionHelpers.sol";208 import {UniqueNFT} from "../api/UniqueNFT.sol";209210 error Value(uint256 value);211212 contract ProxyContract {213 bool value = false;214 address innerContract;215216 event CollectionCreated(address collection);217 event TokenMinted(uint256 tokenId);218219 receive() external payable {}220221 constructor() {222 innerContract = address(new InnerContract());223 }224225 function flip() public {226 value = !value;227 InnerContract(innerContract).flip();228 }229230 function createNonfungibleCollection() external payable {231 address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;232 address nftCollection = CollectionHelpers(collectionHelpers).createNonfungibleCollection{value: msg.value}("A", "B", "C");233 emit CollectionCreated(nftCollection);234 }235236 function mintNftToken(address collectionAddress) external {237 UniqueNFT collection = UniqueNFT(collectionAddress);238 uint256 tokenId = collection.nextTokenId();239 collection.mint(msg.sender, tokenId);240 emit TokenMinted(tokenId);241 }242243 function getValue() external view returns (bool) {244 return InnerContract(innerContract).getValue();245 }246 }247248 contract InnerContract {249 bool value = false;250 function flip() external {251 value = !value;252 }253 function getValue() external view returns (bool) {254 return value;255 }256 }257 `,258 [259 {260 solPath: 'api/CollectionHelpers.sol',261 fsPath: `${__dirname}/api/CollectionHelpers.sol`,262 },263 {264 solPath: 'api/UniqueNFT.sol',265 fsPath: `${__dirname}/api/UniqueNFT.sol`,266 },267 ],268 );269 }270});