1234567891011121314151617import type {IKeyringPair} from '@polkadot/types/types';1819import {itEth, expect, usingEthPlaygrounds} from './util/index.js';20import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';21import {makeNames} from '../util/index.js';2223const {dirname} = makeNames(import.meta.url);2425describe('EVM payable contracts', () => {26 let donor: IKeyringPair;2728 before(async function() {29 await usingEthPlaygrounds(async (_, privateKey) => {30 donor = await privateKey({url: import.meta.url});31 });32 });3334 itEth('Evm contract can receive wei from eth account', async ({helper}) => {35 const deployer = await helper.eth.createAccountWithBalance(donor);36 const contract = await helper.eth.deployCollectorContract(deployer);3738 const web3 = helper.getWeb3();3940 await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', gas: helper.eth.DEFAULT_GAS});4142 expect(await contract.methods.getCollected().call()).to.be.equal('10000');43 });4445 itEth('Evm contract can receive wei from substrate account', async ({helper}) => {46 const deployer = await helper.eth.createAccountWithBalance(donor);47 const contract = await helper.eth.deployCollectorContract(deployer);48 const [alice] = await helper.arrange.createAccounts([40n], donor);4950 const weiCount = '10000';5152 53 54 await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n);555657 await helper.eth.sendEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount);5859 expect(await contract.methods.getCollected().call()).to.be.equal(weiCount);60 });6162 63 itEth('Wei sent directly to backing storage of evm contract balance is unaccounted', async({helper}) => {64 const deployer = await helper.eth.createAccountWithBalance(donor);65 const contract = await helper.eth.deployCollectorContract(deployer);66 const [alice] = await helper.arrange.createAccounts([10n], donor);6768 const weiCount = 10_000n;6970 await helper.eth.transferBalanceFromSubstrate(alice, contract.options.address, weiCount, false);7172 expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString());73 });7475 itEth('Balance can be retrieved from evm contract', async({helper}) => {76 const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal();77 const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal();7879 const deployer = await helper.eth.createAccountWithBalance(donor);80 const contract = await helper.eth.deployCollectorContract(deployer);81 const [alice] = await helper.arrange.createAccounts([20n], donor);8283 const web3 = helper.getWeb3();8485 await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS});8687 const [receiver] = await helper.arrange.createAccounts([0n], donor);8889 90 {91 const ethReceiver = helper.address.substrateToEth(receiver.address);92 expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0');93 await contract.methods.withdraw(ethReceiver).send({from: deployer});94 expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString());95 }9697 98 await helper.balance.transferToSubstrate(alice, receiver.address, FEE_BALANCE);99 100101 102 {103 const initialReceiverBalance = await helper.balance.getSubstrate(receiver.address);104 await helper.executeExtrinsic(receiver, 'api.tx.evm.withdraw', [helper.address.substrateToEth(receiver.address), CONTRACT_BALANCE.toString()], true);105 const finalReceiverBalance = await helper.balance.getSubstrate(receiver.address);106107 expect(finalReceiverBalance > initialReceiverBalance).to.be.true;108 }109 });110});111112describe('EVM transaction fees', () => {113 let donor: IKeyringPair;114115 before(async function() {116 await usingEthPlaygrounds(async (_, privateKey) => {117 donor = await privateKey({url: import.meta.url});118 });119 });120121 itEth('Fee is withdrawn from the user', async({helper}) => {122 const deployer = await helper.eth.createAccountWithBalance(donor);123 const caller = await helper.eth.createAccountWithBalance(donor);124 const contract = await helper.eth.deployFlipper(deployer);125126 const initialCallerBalance = await helper.balance.getEthereum(caller);127 await contract.methods.flip().send({from: caller});128 const finalCallerBalance = await helper.balance.getEthereum(caller);129 expect(finalCallerBalance < initialCallerBalance).to.be.true;130 });131132 itEth('Fee for nested calls is withdrawn from the user', async({helper}) => {133 const deployer = await helper.eth.createAccountWithBalance(donor);134 const caller = await helper.eth.createAccountWithBalance(donor);135 const contract = await deployProxyContract(helper, deployer);136137 const initialCallerBalance = await helper.balance.getEthereum(caller);138 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);139 await contract.methods.flip().send({from: caller});140 const finalCallerBalance = await helper.balance.getEthereum(caller);141 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);142 expect(finalCallerBalance < initialCallerBalance).to.be.true;143 expect(finalContractBalance == initialContractBalance).to.be.true;144 });145146 itEth('Fee for nested calls to native methods is withdrawn from the user', async({helper}) => {147 const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();148149 const deployer = await helper.eth.createAccountWithBalance(donor);150 const caller = await helper.eth.createAccountWithBalance(donor);151 const contract = await deployProxyContract(helper, deployer);152153 const collectionAddress = (await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;154 const initialCallerBalance = await helper.balance.getEthereum(caller);155 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);156 await contract.methods.mintNftToken(collectionAddress).send({from: caller});157 const finalCallerBalance = await helper.balance.getEthereum(caller);158 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);159 expect(finalCallerBalance < initialCallerBalance).to.be.true;160 expect(finalContractBalance == initialContractBalance).to.be.true;161 });162163 itEth('Fee for nested calls to create*Collection methods is withdrawn from the user and from the contract', async({helper}) => {164 const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();165 const deployer = await helper.eth.createAccountWithBalance(donor);166 const caller = await helper.eth.createAccountWithBalance(donor);167 const contract = await deployProxyContract(helper, deployer);168169 const initialCallerBalance = await helper.balance.getEthereum(caller);170 const initialContractBalance = await helper.balance.getEthereum(contract.options.address);171 await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});172 const finalCallerBalance = await helper.balance.getEthereum(caller);173 const finalContractBalance = await helper.balance.getEthereum(contract.options.address);174 expect(finalCallerBalance < initialCallerBalance).to.be.true;175 expect(finalContractBalance == initialContractBalance).to.be.true;176 });177178 itEth('Negative test: call createNFTCollection with wrong fee', async({helper}) => {179 const SMALL_FEE = 1n * helper.balance.getOneTokenNominal();180 const BIG_FEE = 3n * helper.balance.getOneTokenNominal();181 const caller = await helper.eth.createAccountWithBalance(donor);182 const collectionHelper = await helper.ethNativeContract.collectionHelpers(caller);183184 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)');185 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)');186 });187188 itEth('Negative test: call createRFTCollection with wrong fee', async({helper}) => {189 const SMALL_FEE = 1n * helper.balance.getOneTokenNominal();190 const BIG_FEE = 3n * helper.balance.getOneTokenNominal();191 const caller = await helper.eth.createAccountWithBalance(donor);192 const collectionHelper = await helper.ethNativeContract.collectionHelpers(caller);193194 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)');195 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)');196 });197198 itEth('Get collection creation fee', async({helper}) => {199 const deployer = await helper.eth.createAccountWithBalance(donor);200 expect(await helper.eth.getCollectionCreationFee(deployer)).to.be.equal(String(2n * helper.balance.getOneTokenNominal()));201 });202203 async function deployProxyContract(helper: EthUniqueHelper, deployer: string) {204 return await helper.ethContract.deployByCode(205 deployer,206 'ProxyContract',207 `208 // SPDX-License-Identifier: UNLICENSED209 pragma solidity ^0.8.6;210211 import {CollectionHelpers} from "../api/CollectionHelpers.sol";212 import {UniqueNFT} from "../api/UniqueNFT.sol";213214 error Value(uint256 value);215216 contract ProxyContract {217 bool value = false;218 address innerContract;219220 event CollectionCreated(address collection);221 event TokenMinted(uint256 tokenId);222223 receive() external payable {}224225 constructor() {226 innerContract = address(new InnerContract());227 }228229 function flip() public {230 value = !value;231 InnerContract(innerContract).flip();232 }233234 function createNFTCollection() external payable {235 address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;236 address nftCollection = CollectionHelpers(collectionHelpers).createNFTCollection{value: msg.value}("A", "B", "C");237 emit CollectionCreated(nftCollection);238 }239240 function mintNftToken(address collectionAddress) external {241 UniqueNFT collection = UniqueNFT(collectionAddress);242 uint256 tokenId = collection.mint(msg.sender);243 emit TokenMinted(tokenId);244 }245246 function getValue() external view returns (bool) {247 return InnerContract(innerContract).getValue();248 }249 }250251 contract InnerContract {252 bool value = false;253 function flip() external {254 value = !value;255 }256 function getValue() external view returns (bool) {257 return value;258 }259 }260 `,261 [262 {263 solPath: 'api/CollectionHelpers.sol',264 fsPath: `${dirname}/api/CollectionHelpers.sol`,265 },266 {267 solPath: 'api/UniqueNFT.sol',268 fsPath: `${dirname}/api/UniqueNFT.sol`,269 },270 ],271 );272 }273});