1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';1819import {itEth, expect, usingEthPlaygrounds, EthUniqueHelper} from './util';20import {makeNames} from '../util';2122const {dirname} = makeNames(import.meta.url);2324describe('EVM payable contracts', () => {25 let donor: IKeyringPair;2627 before(async function() {28 await usingEthPlaygrounds(async (_, privateKey) => {29 donor = await privateKey({url: import.meta.url});30 });31 });3233 itEth('Evm contract can receive wei from eth account', async ({helper}) => {34 const deployer = await helper.eth.createAccountWithBalance(donor);35 const contract = await helper.eth.deployCollectorContract(deployer);3637 const web3 = helper.getWeb3();3839 await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', gas: helper.eth.DEFAULT_GAS});4041 expect(await contract.methods.getCollected().call()).to.be.equal('10000');42 });4344 itEth('Evm contract can receive wei from substrate account', async ({helper}) => {45 const deployer = await helper.eth.createAccountWithBalance(donor);46 const contract = await helper.eth.deployCollectorContract(deployer);47 const [alice] = await helper.arrange.createAccounts([40n], donor);4849 const weiCount = '10000';5051 52 53 await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n);545556 await helper.eth.sendEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount);5758 expect(await contract.methods.getCollected().call()).to.be.equal(weiCount);59 });6061 62 itEth('Wei sent directly to backing storage of evm contract balance is unaccounted', async({helper}) => {63 const deployer = await helper.eth.createAccountWithBalance(donor);64 const contract = await helper.eth.deployCollectorContract(deployer);65 const [alice] = await helper.arrange.createAccounts([10n], donor);6667 const weiCount = 10_000n;6869 await helper.eth.transferBalanceFromSubstrate(alice, contract.options.address, weiCount, false);7071 expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString());72 });7374 itEth('Balance can be retrieved from evm contract', async({helper}) => {75 const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal();76 const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal();7778 const deployer = await helper.eth.createAccountWithBalance(donor);79 const contract = await helper.eth.deployCollectorContract(deployer);80 const [alice] = await helper.arrange.createAccounts([20n], donor);8182 const web3 = helper.getWeb3();8384 await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS});8586 const [receiver] = await helper.arrange.createAccounts([0n], donor);8788 89 {90 const ethReceiver = helper.address.substrateToEth(receiver.address);91 expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0');92 await contract.methods.withdraw(ethReceiver).send({from: deployer});93 expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString());94 }9596 97 await helper.balance.transferToSubstrate(alice, receiver.address, FEE_BALANCE);98 99100 101 {102 const initialReceiverBalance = await helper.balance.getSubstrate(receiver.address);103 await helper.executeExtrinsic(receiver, 'api.tx.evm.withdraw', [helper.address.substrateToEth(receiver.address), CONTRACT_BALANCE.toString()], true);104 const finalReceiverBalance = await helper.balance.getSubstrate(receiver.address);105106 expect(finalReceiverBalance > initialReceiverBalance).to.be.true;107 }108 });109});110111describe('EVM transaction fees', () => {112 let donor: IKeyringPair;113114 before(async function() {115 await usingEthPlaygrounds(async (_, privateKey) => {116 donor = await privateKey({url: import.meta.url});117 });118 });119120 itEth('Fee is withdrawn from the user', async({helper}) => {121 const deployer = await helper.eth.createAccountWithBalance(donor);122 const caller = await helper.eth.createAccountWithBalance(donor);123 const contract = await helper.eth.deployFlipper(deployer);124125 const initialCallerBalance = await helper.balance.getEthereum(caller);126 await contract.methods.flip().send({from: caller});127 const finalCallerBalance = await helper.balance.getEthereum(caller);128 expect(finalCallerBalance < initialCallerBalance).to.be.true;129 });130131 itEth('Fee for nested calls is withdrawn from the user', async({helper}) => {132 const deployer = await helper.eth.createAccountWithBalance(donor);133 const caller = await helper.eth.createAccountWithBalance(donor);134 const contract = await deployProxyContract(helper, deployer);135136 const initialCallerBalance = await helper.balance.getEthereum(caller);137 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);138 await contract.methods.flip().send({from: caller});139 const finalCallerBalance = await helper.balance.getEthereum(caller);140 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);141 expect(finalCallerBalance < initialCallerBalance).to.be.true;142 expect(finalContractBalance == initialContractBalance).to.be.true;143 });144145 itEth('Fee for nested calls to native methods is withdrawn from the user', async({helper}) => {146 const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();147148 const deployer = await helper.eth.createAccountWithBalance(donor);149 const caller = await helper.eth.createAccountWithBalance(donor);150 const contract = await deployProxyContract(helper, deployer);151152 const collectionAddress = (await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;153 const initialCallerBalance = await helper.balance.getEthereum(caller);154 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);155 await contract.methods.mintNftToken(collectionAddress).send({from: caller});156 const finalCallerBalance = await helper.balance.getEthereum(caller);157 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);158 expect(finalCallerBalance < initialCallerBalance).to.be.true;159 expect(finalContractBalance == initialContractBalance).to.be.true;160 });161162 itEth('Fee for nested calls to create*Collection methods is withdrawn from the user and from the contract', async({helper}) => {163 const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();164 const deployer = await helper.eth.createAccountWithBalance(donor);165 const caller = await helper.eth.createAccountWithBalance(donor);166 const contract = await deployProxyContract(helper, deployer);167168 const initialCallerBalance = await helper.balance.getEthereum(caller);169 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);170 await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});171 const finalCallerBalance = await helper.balance.getEthereum(caller);172 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);173 expect(finalCallerBalance < initialCallerBalance).to.be.true;174 expect(finalContractBalance == initialContractBalance).to.be.true;175 });176177 itEth('Negative test: call createNFTCollection with wrong fee', async({helper}) => {178 const SMALL_FEE = 1n * helper.balance.getOneTokenNominal();179 const BIG_FEE = 3n * helper.balance.getOneTokenNominal();180 const caller = await helper.eth.createAccountWithBalance(donor);181 const collectionHelper = await helper.ethNativeContract.collectionHelpers(caller);182183 await expect(collectionHelper.methods.createNFTCollection('A', 'B', 'C').call({value: Number(SMALL_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');184 await expect(collectionHelper.methods.createNFTCollection('A', 'B', 'C').call({value: Number(BIG_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');185 });186187 itEth('Negative test: call createRFTCollection with wrong fee', async({helper}) => {188 const SMALL_FEE = 1n * helper.balance.getOneTokenNominal();189 const BIG_FEE = 3n * helper.balance.getOneTokenNominal();190 const caller = await helper.eth.createAccountWithBalance(donor);191 const collectionHelper = await helper.ethNativeContract.collectionHelpers(caller);192193 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)');194 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)');195 });196197 itEth('Get collection creation fee', async({helper}) => {198 const deployer = await helper.eth.createAccountWithBalance(donor);199 expect(await helper.eth.getCollectionCreationFee(deployer)).to.be.equal(String(2n * helper.balance.getOneTokenNominal()));200 });201202 async function deployProxyContract(helper: EthUniqueHelper, deployer: string) {203 return await helper.ethContract.deployByCode(204 deployer,205 'ProxyContract',206 `207 // SPDX-License-Identifier: UNLICENSED208 pragma solidity ^0.8.6;209210 import {CollectionHelpers} from "../api/CollectionHelpers.sol";211 import {UniqueNFT} from "../api/UniqueNFT.sol";212213 error Value(uint256 value);214215 contract ProxyContract {216 bool value = false;217 address innerContract;218219 event CollectionCreated(address collection);220 event TokenMinted(uint256 tokenId);221222 receive() external payable {}223224 constructor() {225 innerContract = address(new InnerContract());226 }227228 function flip() public {229 value = !value;230 InnerContract(innerContract).flip();231 }232233 function createNFTCollection() external payable {234 address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;235 address nftCollection = CollectionHelpers(collectionHelpers).createNFTCollection{value: msg.value}("A", "B", "C");236 emit CollectionCreated(nftCollection);237 }238239 function mintNftToken(address collectionAddress) external {240 UniqueNFT collection = UniqueNFT(collectionAddress);241 uint256 tokenId = collection.mint(msg.sender);242 emit TokenMinted(tokenId);243 }244245 function getValue() external view returns (bool) {246 return InnerContract(innerContract).getValue();247 }248 }249250 contract InnerContract {251 bool value = false;252 function flip() external {253 value = !value;254 }255 function getValue() external view returns (bool) {256 return value;257 }258 }259 `,260 [261 {262 solPath: 'api/CollectionHelpers.sol',263 fsPath: `${dirname}/api/CollectionHelpers.sol`,264 },265 {266 solPath: 'api/UniqueNFT.sol',267 fsPath: `${dirname}/api/UniqueNFT.sol`,268 },269 ],270 );271 }272});