diff --git a/tests/package.json b/tests/package.json index 73d6ddde9fb37..a04282353c526 100644 --- a/tests/package.json +++ b/tests/package.json @@ -21,5 +21,6 @@ "ts-node": "^8.10.2", "typescript": "^3.9.6", "web3": "^1.2.9" - } + }, + "devDependencies": {} } diff --git a/tests/tests/constants/index.ts b/tests/tests/constants/index.ts new file mode 100644 index 0000000000000..613bb600375ea --- /dev/null +++ b/tests/tests/constants/index.ts @@ -0,0 +1,68 @@ +export { + TEST_CONTRACT_BYTECODE, + TEST_CONTRACT_ABI, + FIRST_CONTRACT_ADDRESS, + TEST_CONTRACT_BYTECODE_INCR, + TEST_CONTRACT_INCR_ABI, + INFINITE_CONTRACT_BYTECODE, + INFINITE_CONTRACT_ABI, + INFINITE_CONTRACT_BYTECODE_VAR, + INFINITE_CONTRACT_ABI_VAR, + FINITE_LOOP_CONTRACT_BYTECODE, + FINITE_LOOP_CONTRACT_ABI, +} from "./testContracts"; + +export { basicTransfertx, contractCreation } from "./transactionConfigs"; + +export const PORT = 19931; +export const RPC_PORT = 19932; +export const WS_PORT = 19933; +export const SPECS_PATH = `./moonbeam-test-specs`; + +export const DISPLAY_LOG = process.env.MOONBEAM_LOG || false; +export const MOONBEAM_LOG = process.env.MOONBEAM_LOG || "info"; + +export const BINARY_PATH = + process.env.BINARY_PATH || `../node/standalone/target/release/moonbase-standalone`; +export const SPAWNING_TIME = 30000; + +// Test variables +export const GENESIS_ACCOUNT = "0x6be02d1d3665660d22ff9624b7be0551ee1ac91b"; +export const GENESIS_ACCOUNT_PRIVATE_KEY = + "0x99B3C12287537E38C90A9219D4CB074A89A16E9CDB20BF85728EBD97C343E342"; +export const TEST_ACCOUNT = "0x1111111111111111111111111111111111111111"; + +// TESTING NOTES +// +// BLOCK TESTING +// - block dont seem to have gas limit but it's usually around 1500 tx per block +// and the time it takes to construct the block increases until around 12s for 1500tx +// - after the first 1500tx block, following block have around 100-300 tx per block +// until all blocks are incuded. 10 blockds for 3000tx +// - between 7k and 10k, for some reason block creation doesnt work and we get +// one Pool(ImmediatelyDropped) error +// and Pool(TooLowPriority { old: 0, new: 0 })': 819 for the contract creation + +// 8192 is the number of tx that can be sent to the Pool before it throws an error and drops all tx +// from the pool (we can see in the logs that the ‘ready’ field goes from 8192 to zero) + +// It does say however, 8182/20480kB ready, 819/2048kB future and I’m not sure what that means +// +// INFINITE LOOP +// - infinite loop contract should throw out of gas error, but they don't and +// they are included in the block. +// - there are some rpc errors sometimes +// - the state remains unchanged tho (test with infinite incremental contract) +// +// FINITE LOOP +// - making a 1000 loop incr on a smart contract doesnt pass but doesnt throw +// error either (although it does include the tx in a block) +// => is there a problem with out of gas error +// =>probably because we don't have the concept of gas? +// - posting a tx that goes over the gas limit/tx does throw an out of gas error +// in the debug log but not in js + +//NB: https://github.com/paritytech/frontier/blob/master/frame/ethereum/src/lib.rs +// show that root=0 when error is thrown, +//which is something we can see when fethcing receipt +// also the current block limit is zero diff --git a/tests/tests/constants/testContracts.ts b/tests/tests/constants/testContracts.ts new file mode 100644 index 0000000000000..abb217d70b02c --- /dev/null +++ b/tests/tests/constants/testContracts.ts @@ -0,0 +1,192 @@ +import { AbiItem } from "web3-utils"; + +// Solidity: contract test {function multiply(uint a) public pure returns(uint d) {return a * 7;}} +export const TEST_CONTRACT_BYTECODE = + "0x6080604052348015600f57600080fd5b5060ae8061001e6000396000f3fe6080604052348015600f57600080fd" + + "5b506004361060285760003560e01c8063c6888fa114602d575b600080fd5b605660048036036020811015604157" + + "600080fd5b8101908080359060200190929190505050606c565b6040518082815260200191505060405180910390" + + "f35b600060078202905091905056fea265627a7a72315820f06085b229f27f9ad48b2ff3dd9714350c1698a37853" + + "a30136fa6c5a7762af7364736f6c63430005110032"; + +export const TEST_CONTRACT_ABI = { + constant: true, + inputs: [{ internalType: "uint256", name: "a", type: "uint256" }], + name: "multiply", + outputs: [{ internalType: "uint256", name: "d", type: "uint256" }], + payable: false, + stateMutability: "pure", + type: "function", +} as AbiItem; + +export const FIRST_CONTRACT_ADDRESS = "0xc2bf5f29a4384b1ab0c063e1c666f02121b6084a"; + +// simple incremental count contract to test contract with state changes + +// Solidity: +// contract Test3 { +// uint public count; + +// constructor() public { +// count = 0; +// } + +// function incr() public { +// count=count+1; +// } +// } +export const TEST_CONTRACT_BYTECODE_INCR = + "6080604052348015600f57600080fd5b506000808190555060a5806100256000396000f3fe608060405234801560" + + "0f57600080fd5b506004361060325760003560e01c806306661abd146037578063119fbbd4146053575b600080fd" + + "5b603d605b565b6040518082815260200191505060405180910390f35b60596061565b005b60005481565b600160" + + "00540160008190555056fea26469706673582212204780263fff0edc01286caed1851cc629033bc25ec1f84995a7" + + "1199017a4623dd64736f6c634300060b0033"; + +export const TEST_CONTRACT_INCR_ABI = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "count", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "incr", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as AbiItem[]; + +// infinite loop call + +// Solidity: +// contract test { +// function infinite(uint a) public pure returns(uint d) {while (true) {}} +// } +export const INFINITE_CONTRACT_BYTECODE = + "6080604052348015600f57600080fd5b5060788061001e6000396000f3fe6080604052348015600f57600080fd5b" + + "506004361060285760003560e01c80635bec9e6714602d575b600080fd5b60336035565b005b5b60011560405760" + + "36565b56fea264697066735822122015c7d339c1118112e1d9b33ea79ded52efa22f4e3cefe34097578a63e128f8" + + "a264736f6c63430007040033"; + +export const INFINITE_CONTRACT_ABI = { + inputs: [], + name: "infinite", + outputs: [], + stateMutability: "pure", + type: "function", +} as AbiItem; + +// infinite loop call with variable alocation + +// Solidity: +// contract test { +// function infinite(uint a) public pure returns(uint d) {while (true) {data=data+1;}} +// } +export const INFINITE_CONTRACT_BYTECODE_VAR = + "608060405234801561001057600080fd5b50600160008190555060b0806100276000396000f3fe60806040523480" + + "15600f57600080fd5b506004361060325760003560e01c80635bec9e6714603757806373d4a13a14603f575b6000" + + "80fd5b603d605b565b005b60456074565b6040518082815260200191505060405180910390f35b5b600115607257" + + "600160005401600081905550605c565b565b6000548156fea264697066735822122053e7fd0d4629f7d9cd16b045" + + "6521ea0cf78e595e9627c45ee8a4f27f4119f39c64736f6c634300060b0033"; + +export const INFINITE_CONTRACT_ABI_VAR = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "data", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "infinite", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as AbiItem[]; + +// definite loop call with variable alocation + +// Solidity: +// contract Test4 { +// uint public count; + +// constructor() public { +// count = 0; +// } + +// function incr(uint n) public { +// uint i=0; +// while (i { +describeWithMoonbeam("Moonbeam RPC (Balance)", `simple-specs.json`, (context) => { const GENESIS_ACCOUNT = "0x6be02d1d3665660d22ff9624b7be0551ee1ac91b"; const GENESIS_ACCOUNT_BALANCE = "340282366920938463463374607431768211455"; const GENESIS_ACCOUNT_PRIVATE_KEY = @@ -33,7 +33,7 @@ describeWithMoonbeam("Frontier RPC (Balance)", `simple-specs.json`, (context) => GENESIS_ACCOUNT_PRIVATE_KEY ); await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal( "340282366920938463463374607431768189943" ); @@ -54,7 +54,7 @@ describeWithMoonbeam("Frontier RPC (Balance)", `simple-specs.json`, (context) => GENESIS_ACCOUNT_PRIVATE_KEY ); await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal( (await context.polkadotApi.query.system.account(GENESIS_ACCOUNT)).data.free.toString() ); @@ -68,7 +68,7 @@ describeWithMoonbeam("Frontier RPC (Balance)", `simple-specs.json`, (context) => const testAccount = await keyring.addFromUri(GENESIS_ACCOUNT_PRIVATE_KEY, null, "ethereum"); await context.polkadotApi.tx.balances.transfer(TEST_ACCOUNT_2, 123).signAndSend(testAccount); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect(await context.web3.eth.getBalance(TEST_ACCOUNT_2)).to.equal("123"); }); }); diff --git a/tests/tests/test-block.ts b/tests/tests/test-block.ts index 1baacca1fc137..391fb0b976c0d 100644 --- a/tests/tests/test-block.ts +++ b/tests/tests/test-block.ts @@ -1,7 +1,8 @@ import { expect } from "chai"; import { step } from "mocha-steps"; +import { contractCreation, GENESIS_ACCOUNT } from "./constants"; -import { createAndFinalizeBlock, describeWithMoonbeam } from "./util"; +import { createAndFinalizeBlock, describeWithMoonbeam, fillBlockWithTx } from "./util"; describeWithMoonbeam("Moonbeam RPC (Block)", `simple-specs.json`, (context) => { let previousBlock; @@ -13,7 +14,7 @@ describeWithMoonbeam("Moonbeam RPC (Block)", `simple-specs.json`, (context) => { expect(await context.web3.eth.getBlockNumber()).to.equal(0); }); - it("should return genesis block", async function () { + step("should return genesis block", async function () { expect(await context.web3.eth.getBlockNumber()).to.equal(0); const block = await context.web3.eth.getBlock(0); expect(block).to.include({ @@ -42,14 +43,14 @@ describeWithMoonbeam("Moonbeam RPC (Block)", `simple-specs.json`, (context) => { }); let firstBlockCreated = false; - it("should be at block 1 after block production", async function () { + step("should be at block 1 after block production", async function () { this.timeout(15000); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect(await context.web3.eth.getBlockNumber()).to.equal(1); firstBlockCreated = true; }); - it("should have valid timestamp after block production", async function () { + step("should have valid timestamp after block production", async function () { // Originally ,this test required the timestamp be in the last finve minutes. // This requirement doesn't make sense when we forge timestamps in manual seal. const block = await context.web3.eth.getBlock("latest"); @@ -58,7 +59,7 @@ describeWithMoonbeam("Moonbeam RPC (Block)", `simple-specs.json`, (context) => { expect(block.timestamp).to.be.below(next5Minutes); }); - it("retrieve block information", async function () { + step("retrieve block information", async function () { expect(firstBlockCreated).to.be.true; const block = await context.web3.eth.getBlock("latest"); @@ -93,22 +94,77 @@ describeWithMoonbeam("Moonbeam RPC (Block)", `simple-specs.json`, (context) => { expect(block.timestamp).to.be.a("number"); }); - it("get block by hash", async function () { + step("get block by hash", async function () { const latest_block = await context.web3.eth.getBlock("latest"); const block = await context.web3.eth.getBlock(latest_block.hash); expect(block.hash).to.be.eq(latest_block.hash); }); - it("get block by number", async function () { + step("get block by number", async function () { const block = await context.web3.eth.getBlock(1); expect(block).not.null; }); - it("should include previous block hash as parent", async function () { + step("should include previous block hash as parent (block 2)", async function () { this.timeout(15000); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); const block = await context.web3.eth.getBlock("latest"); expect(block.hash).to.not.equal(previousBlock.hash); expect(block.parentHash).to.equal(previousBlock.hash); }); + + // tx/block tests + + step("genesis balance enough to make all the transfers", async function () { + expect(Number(await context.web3.eth.getBalance(GENESIS_ACCOUNT))).to.gte(512 * 100000); + }); + + // the maximum number of tx/ blocks is not constant but is always around 1500 + + it("should be able to fill a block with a 1 tx", async function () { + this.timeout(0); + let { txPassedFirstBlock } = await fillBlockWithTx(context, 1); + expect(txPassedFirstBlock).to.eq(1); + }); + + it("should be able to fill a block with a 1000 tx", async function () { + this.timeout(0); + let { txPassedFirstBlock } = await fillBlockWithTx(context, 1000); + expect(txPassedFirstBlock).to.eq(1000); + }); + + it("should be able to fill a block with 1000 contract creations tx", async function () { + this.timeout(0); + let { txPassedFirstBlock } = await fillBlockWithTx(context, 1000, contractCreation); + expect(txPassedFirstBlock).to.eq(1000); + }); + + // 8192 is the number of tx that can be sent to the Pool + // before it throws an error and drops all tx + + it("should be able to send 8192 tx to the pool and have them all published\ + within the following blocks", async function () { + this.timeout(0); + let { txPassed } = await fillBlockWithTx(context, 8192); + expect(txPassed).to.eq(8192); + }); + + it("but shouldn't work for 8193", async function () { + this.timeout(0); + let { txPassed } = await fillBlockWithTx(context, 8193); + expect(txPassed).to.eq(0); + }); + + it("should be able to send 8192 tx to the pool and have them all published\ + within the following blocks - bigger tx", async function () { + this.timeout(0); + let { txPassed } = await fillBlockWithTx(context, 8192, contractCreation); + expect(txPassed).to.eq(8192); + }); + + it("but shouldn't work for 8193 - bigger tx", async function () { + this.timeout(0); + let { txPassed } = await fillBlockWithTx(context, 8193, contractCreation); + expect(txPassed).to.eq(0); + }); }); diff --git a/tests/tests/test-contract-loops.ts b/tests/tests/test-contract-loops.ts new file mode 100644 index 0000000000000..4eaeed369b591 --- /dev/null +++ b/tests/tests/test-contract-loops.ts @@ -0,0 +1,126 @@ +import { expect } from "chai"; + +import { TransactionReceipt } from "web3-core"; + +import { callContractFunctionMS, deployContractManualSeal, describeWithMoonbeam } from "./util"; +import { + FINITE_LOOP_CONTRACT_ABI, + FINITE_LOOP_CONTRACT_BYTECODE, + INFINITE_CONTRACT_ABI, + INFINITE_CONTRACT_ABI_VAR, + INFINITE_CONTRACT_BYTECODE, + INFINITE_CONTRACT_BYTECODE_VAR, + TEST_CONTRACT_BYTECODE_INCR, + TEST_CONTRACT_INCR_ABI, +} from "./constants"; + +describeWithMoonbeam("Moonbeam RPC (Contract Loops)", `simple-specs.json`, (context) => { + it("should increment contract state - to check normal contract behavior", async function () { + // // instantiate contract + const contract = await deployContractManualSeal( + context.polkadotApi, + context.web3, + TEST_CONTRACT_BYTECODE_INCR, + TEST_CONTRACT_INCR_ABI + ); + + // check variable initializaion + expect(await contract.methods.count().call()).to.eq("0"); + + // call incr function + let bytesCode: string = await contract.methods.incr().encodeABI(); + await callContractFunctionMS(context, contract.options.address, bytesCode); + + // check variable incrementation + expect(await contract.methods.count().call()).to.eq("1"); + }); + + it("inifinite loop call should return OutOfGas", async function () { + this.timeout(0); + + //deploy infinite contract + const contract = await deployContractManualSeal( + context.polkadotApi, + context.web3, + INFINITE_CONTRACT_BYTECODE, + [INFINITE_CONTRACT_ABI] + ); + + // call infinite loop + await contract.methods + .infinite() + .call({ gas: "0x100000" }) + .catch((err) => expect(err.message).to.equal(`Returned error: evm error: OutOfGas`)); + }); + + it("inifinite loop send with incr should return OutOfGas", async function () { + this.timeout(0); + + // deploy contract + const contract = await deployContractManualSeal( + context.polkadotApi, + context.web3, + INFINITE_CONTRACT_BYTECODE_VAR, + INFINITE_CONTRACT_ABI_VAR + ); + + //make infinite loop function call + let bytesCode: string = await contract.methods.infinite().encodeABI(); + try { + await callContractFunctionMS(context, contract.options.address, bytesCode); + let block = await context.web3.eth.getBlock("latest"); + const receipt: TransactionReceipt = await context.web3.eth.getTransactionReceipt( + block.transactions[0] + ); + expect(receipt.status).to.eq(false); + } catch (e) { + console.log("error caught", e); + throw new Error(e); + } + }); + + it("finite loop with incr: check gas usage, with normal gas limit,\ + should error before 700 loops", async function () { + this.timeout(0); + // For a normal 1048576 gas limit, loop should revert out of gas between 600 and 700 loops + + //deploy finite loop contract + const contract = await deployContractManualSeal( + context.polkadotApi, + context.web3, + FINITE_LOOP_CONTRACT_BYTECODE, + FINITE_LOOP_CONTRACT_ABI + ); + + //make finite loop function call + async function callLoopIncrContract(nb: number): Promise { + const startIncr: number = Number(await contract.methods.count().call()); + const bytesCode: string = await contract.methods.incr(nb).encodeABI(); + try { + await callContractFunctionMS(context, contract.options.address, bytesCode); + return Number(await contract.methods.count().call()) - startIncr; + } catch (e) { + console.log("error caught", e); + } + } + // 1 loop to make sure it works + expect(await callLoopIncrContract(1)).to.eq(1); + let block = await context.web3.eth.getBlock("latest"); + expect(block.gasUsed).to.eq(42343); //check that gas costs stay the same + + // // 600 loop + expect(await callLoopIncrContract(600)).to.eq(600); + block = await context.web3.eth.getBlock("latest"); + expect(block.gasUsed).to.eq(1028284); //check that gas costs stay the same + + // 700 loop should revert out of gas + expect(await callLoopIncrContract(700)).to.eq(0); + block = await context.web3.eth.getBlock("latest"); + expect(block.gasUsed).to.eq(1048576); //check that gas is the gas limit + const receipt: TransactionReceipt = await context.web3.eth.getTransactionReceipt( + block.transactions[0] + ); + expect(receipt.status).to.eq(false); + }); + // TODO : add test when we have a block limit +}); diff --git a/tests/tests/test-contract-methods.ts b/tests/tests/test-contract-methods.ts index d13607ddae21d..694e307fa74e5 100644 --- a/tests/tests/test-contract-methods.ts +++ b/tests/tests/test-contract-methods.ts @@ -1,38 +1,15 @@ import { expect } from "chai"; import { createAndFinalizeBlock, customRequest, describeWithMoonbeam } from "./util"; -import { AbiItem } from "web3-utils"; +import { + FIRST_CONTRACT_ADDRESS, + GENESIS_ACCOUNT, + GENESIS_ACCOUNT_PRIVATE_KEY, + TEST_CONTRACT_ABI, + TEST_CONTRACT_BYTECODE, +} from "./constants"; describeWithMoonbeam("Moonbeam RPC (Contract Methods)", `simple-specs.json`, (context) => { - const GENESIS_ACCOUNT = "0x6be02d1d3665660d22ff9624b7be0551ee1ac91b"; - const GENESIS_ACCOUNT_PRIVATE_KEY = - "0x99B3C12287537E38C90A9219D4CB074A89A16E9CDB20BF85728EBD97C343E342"; - - // Solidity: - // contract test { - // function multiply(uint a) public pure returns(uint d) {return a * 7;} - // } - const TEST_CONTRACT_BYTECODE = - "0x6080604052348015600f57600080fd5b5060ae8061001e6000396000f3fe6080604052348015600f57600080f" + - "d5b506004361060285760003560e01c8063c6888fa114602d575b600080fd5b6056600480360360208110156041" + - "57600080fd5b8101908080359060200190929190505050606c565b6040518082815260200191505060405180910" + - "390f35b600060078202905091905056fea265627a7a72315820f06085b229f27f9ad48b2ff3dd9714350c1698a3" + - "7853a30136fa6c5a7762af7364736f6c63430005110032"; - - const TEST_CONTRACT_ABI = { - constant: true, - inputs: [{ internalType: "uint256", name: "a", type: "uint256" }], - name: "multiply", - outputs: [{ internalType: "uint256", name: "d", type: "uint256" }], - payable: false, - stateMutability: "pure", - type: "function", - } as AbiItem; - - // Those test are ordered. In general this should be avoided, but due to the time it takes - // to spin up a Moonbeam node, it saves a lot of time. - const FIRST_CONTRACT_ADDRESS = "0xc2bf5f29a4384b1ab0c063e1c666f02121b6084a"; - before("create the contract", async function () { this.timeout(15000); const tx = await context.web3.eth.accounts.signTransaction( @@ -46,7 +23,7 @@ describeWithMoonbeam("Moonbeam RPC (Contract Methods)", `simple-specs.json`, (co GENESIS_ACCOUNT_PRIVATE_KEY ); await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); }); it("get transaction by hash", async () => { @@ -66,9 +43,8 @@ describeWithMoonbeam("Moonbeam RPC (Contract Methods)", `simple-specs.json`, (co expect(await contract.methods.multiply(3).call()).to.equal("21"); }); - // Requires error handling - it.skip("should fail for missing parameters", async function () { + it("should fail for missing parameters", async function () { const contract = new context.web3.eth.Contract( [{ ...TEST_CONTRACT_ABI, inputs: [] }], FIRST_CONTRACT_ADDRESS, @@ -82,13 +58,13 @@ describeWithMoonbeam("Moonbeam RPC (Contract Methods)", `simple-specs.json`, (co .call() .catch((err) => expect(err.message).to.equal( - `Returned error: VM Exception while processing transaction: revert.` + `Returned error: VM Exception while processing transaction: revert` ) ); }); // Requires error handling - it.skip("should fail for too many parameters", async function () { + it("should fail for too many parameters", async function () { const contract = new context.web3.eth.Contract( [ { @@ -110,13 +86,13 @@ describeWithMoonbeam("Moonbeam RPC (Contract Methods)", `simple-specs.json`, (co .call() .catch((err) => expect(err.message).to.equal( - `Returned error: VM Exception while processing transaction: revert.` + `Returned error: VM Exception while processing transaction: revert` ) ); }); // Requires error handling - it.skip("should fail for invalid parameters", async function () { + it("should fail for invalid parameters", async function () { const contract = new context.web3.eth.Contract( [ { @@ -138,7 +114,7 @@ describeWithMoonbeam("Moonbeam RPC (Contract Methods)", `simple-specs.json`, (co .call() .catch((err) => expect(err.message).to.equal( - `Returned error: VM Exception while processing transaction: revert.` + `Returned error: VM Exception while processing transaction: revert` ) ); }); diff --git a/tests/tests/test-contract-precompiled.ts b/tests/tests/test-contract-precompiled.ts index 53c139a6574b9..6cbfc5db9ec6c 100644 --- a/tests/tests/test-contract-precompiled.ts +++ b/tests/tests/test-contract-precompiled.ts @@ -24,7 +24,7 @@ describeWithMoonbeam("Moonbeam RPC (Precompiles)", `simple-specs.json`, (context const tx = await context.web3.eth.accounts.signTransaction(RAW_TX, GENESIS_ACCOUNT_PRIVATE_KEY); const tx_res = await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); let tx_receipt = await customRequest(context.web3, "eth_getTransactionReceipt", [ tx_res.result, diff --git a/tests/tests/test-contract.ts b/tests/tests/test-contract.ts index d1d6d4b1e3c2b..a7c6c7edaaa62 100644 --- a/tests/tests/test-contract.ts +++ b/tests/tests/test-contract.ts @@ -1,20 +1,14 @@ import { expect } from "chai"; +import { + FIRST_CONTRACT_ADDRESS, + GENESIS_ACCOUNT, + GENESIS_ACCOUNT_PRIVATE_KEY, + TEST_CONTRACT_BYTECODE, +} from "./constants"; import { createAndFinalizeBlock, customRequest, describeWithMoonbeam } from "./util"; describeWithMoonbeam("Moonbeam RPC (Contract)", `simple-specs.json`, (context) => { - const GENESIS_ACCOUNT = "0x6be02d1d3665660d22ff9624b7be0551ee1ac91b"; - const GENESIS_ACCOUNT_PRIVATE_KEY = - "0x99B3C12287537E38C90A9219D4CB074A89A16E9CDB20BF85728EBD97C343E342"; - - // Solidity: contract test {function multiply(uint a) public pure returns(uint d) {return a * 7;}} - const TEST_CONTRACT_BYTECODE = - "0x6080604052348015600f57600080fd5b5060ae8061001e6000396000f3fe6080604052348015600f57600080fd" + - "5b506004361060285760003560e01c8063c6888fa114602d575b600080fd5b605660048036036020811015604157" + - "600080fd5b8101908080359060200190929190505050606c565b6040518082815260200191505060405180910390" + - "f35b600060078202905091905056fea265627a7a72315820f06085b229f27f9ad48b2ff3dd9714350c1698a37853" + - "a30136fa6c5a7762af7364736f6c63430005110032"; - const FIRST_CONTRACT_ADDRESS = "0xc2bf5f29a4384b1ab0c063e1c666f02121b6084a"; // Those test are ordered. In general this should be avoided, but due to the time it takes // to spin up a Moonbeam node, it saves a lot of time. @@ -49,7 +43,7 @@ describeWithMoonbeam("Moonbeam RPC (Contract)", `simple-specs.json`, (context) = }); // Verify the contract is stored after the block is produced - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect( await customRequest(context.web3, "eth_getCode", [FIRST_CONTRACT_ADDRESS]) ).to.deep.equal({ diff --git a/tests/tests/test-existential-deposit.ts b/tests/tests/test-existential-deposit.ts index efa8bcea9547c..0bce1455aa286 100644 --- a/tests/tests/test-existential-deposit.ts +++ b/tests/tests/test-existential-deposit.ts @@ -22,7 +22,7 @@ describeWithMoonbeam("Moonbeam RPC (Existential Deposit)", `simple-specs.json`, GENESIS_ACCOUNT_PRIVATE_KEY ); await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect(parseInt(await context.web3.eth.getBalance(GENESIS_ACCOUNT))).to.eq(0); expect(await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT)).to.eq(1); }); diff --git a/tests/tests/test-gas.ts b/tests/tests/test-gas.ts index 6e12843d97fb3..6d687f41e0c44 100644 --- a/tests/tests/test-gas.ts +++ b/tests/tests/test-gas.ts @@ -51,7 +51,7 @@ describeWithMoonbeam("Moonbeam RPC (Gas)", `simple-specs.json`, (context) => { this.timeout(15000); const gasLimit = (await context.web3.eth.getBlock("latest")).gasLimit; - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); // Gas limit is expected to have decreased as the gasUsed by the block is lower than 2/3 of the // previous gas limit. diff --git a/tests/tests/test-nonce.ts b/tests/tests/test-nonce.ts index 86c4d163bd4aa..a39995bf1ccaf 100644 --- a/tests/tests/test-nonce.ts +++ b/tests/tests/test-nonce.ts @@ -29,7 +29,7 @@ describeWithMoonbeam("Moonbeam RPC (Nonce)", `simple-specs.json`, (context) => { expect(await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT, "latest")).to.eq(0); expect(await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT, "pending")).to.eq(1); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect(await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT, "latest")).to.eq(1); expect(await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT, "pending")).to.eq(1); diff --git a/tests/tests/test-precompiles.ts b/tests/tests/test-precompiles.ts index f1aa4c94f2b07..f1d4b22809593 100644 --- a/tests/tests/test-precompiles.ts +++ b/tests/tests/test-precompiles.ts @@ -56,7 +56,7 @@ describeWithMoonbeam("Moonbeam (Precompiles)", `simple-specs.json`, (context) => GENESIS_ACCOUNT_PRIVATE_KEY ); await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); expect(await context.web3.eth.getCode("0xc2bf5f29a4384b1ab0c063e1c666f02121b6084a")).equals( "0x6080604052600080fdfea26469706673582212202febccafbee65a134279d3397fecfc56a3d21259" + "87802a91add0260c7efa94d264736f6c634300060c0033" diff --git a/tests/tests/test-revert-receipt.ts b/tests/tests/test-revert-receipt.ts index b45e2267329fb..bc9f67e5c09a0 100644 --- a/tests/tests/test-revert-receipt.ts +++ b/tests/tests/test-revert-receipt.ts @@ -49,7 +49,7 @@ describeWithMoonbeam("Frontier RPC (Constructor Revert)", `simple-specs.json`, ( }); // Verify the receipt exists after the block is created - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); const receipt = await context.web3.eth.getTransactionReceipt(GOOD_TX_HASH); expect(receipt).to.include({ blockNumber: 1, @@ -88,7 +88,7 @@ describeWithMoonbeam("Frontier RPC (Constructor Revert)", `simple-specs.json`, ( result: FAIL_TX_HASH, }); - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); const receipt = await context.web3.eth.getTransactionReceipt(FAIL_TX_HASH); expect(receipt).to.include({ blockNumber: 2, diff --git a/tests/tests/test-subscription.ts b/tests/tests/test-subscription.ts index 8207b54642b97..939415338277b 100644 --- a/tests/tests/test-subscription.ts +++ b/tests/tests/test-subscription.ts @@ -127,7 +127,7 @@ describeWithMoonbeam( } step("should connect", async function () { - await createAndFinalizeBlock(context.web3); + await createAndFinalizeBlock(context.polkadotApi); // @ts-ignore const connected = context.web3.currentProvider.connected; expect(connected).to.equal(true); @@ -155,7 +155,7 @@ describeWithMoonbeam( subscription = context.web3.eth.subscribe("newBlockHeaders", function (error, result) {}); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; resolve(); @@ -192,7 +192,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -218,7 +218,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -259,7 +259,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -290,7 +290,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -345,7 +345,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -379,7 +379,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -416,7 +416,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -457,7 +457,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; @@ -495,7 +495,7 @@ describeWithMoonbeam( const tx = await sendTransaction(context); let data = null; await new Promise((resolve) => { - createAndFinalizeBlock(context.web3); + createAndFinalizeBlock(context.polkadotApi); subscription.on("data", function (d: any) { data = d; logs_generated += 1; diff --git a/tests/tests/util/fillBlockWithTx.ts b/tests/tests/util/fillBlockWithTx.ts new file mode 100644 index 0000000000000..4d5da6396d70c --- /dev/null +++ b/tests/tests/util/fillBlockWithTx.ts @@ -0,0 +1,191 @@ +import Web3 from "web3"; + +import { JsonRpcResponse } from "web3-core-helpers"; +import { SignedTransaction, TransactionConfig } from "web3-core"; +import { basicTransfertx, GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "../constants"; +import { wrappedCustomRequest } from "./web3Requests"; +import { createAndFinalizeBlock } from "."; +import { Context } from "./testWithMoonbeam"; + +function isSignedTransaction(tx: Error | SignedTransaction): tx is SignedTransaction { + return (tx as SignedTransaction).rawTransaction !== undefined; +} +function isJsonRpcResponse(res: Error | JsonRpcResponse): res is JsonRpcResponse { + return (res as JsonRpcResponse).jsonrpc !== undefined; +} + +// sign tx with error catching +async function wrappedSignTx( + web3: Web3, + txConfig: TransactionConfig, + privateKey: string +): Promise { + try { + let tx = await web3.eth.accounts.signTransaction(txConfig, privateKey); + return tx; + } catch (e) { + return new Error(e.toString()); + } +} + +// Sign tx sequentially +async function serialSignTx( + web3: Web3, + n: number, + startingNonce: number, + customTxConfig: TransactionConfig +): Promise<(Error | SignedTransaction)[]> { + const resArray = []; + for (let index = 0; index < n; index++) { + resArray.push( + await wrappedSignTx( + web3, + { ...customTxConfig, nonce: startingNonce + index }, + GENESIS_ACCOUNT_PRIVATE_KEY + ) + ); + } + return resArray; +} + +// Send tx to the pool sequentially +async function serialSendTx( + web3: Web3, + n: number, + _txList: (Error | SignedTransaction)[] +): Promise<(Error | JsonRpcResponse)[]> { + const resArray = []; + for (let index = 0; index < n; index++) { + if (isSignedTransaction(_txList[index])) { + resArray.push( + await wrappedCustomRequest(web3, "eth_sendRawTransaction", [ + (_txList[index] as SignedTransaction).rawTransaction, + ]) + ); + } else { + resArray.push(_txList[index] as Error); + } + } + return resArray; +} + +interface FillBlockReport { + txPassed: number; + txPassedFirstBlock: number; + numberOfBlocks: number; + signingTime: number; + sendingTime: number; +} + +// This functiom sends a batch of signed transactions to the pool and records both +// how many tx were included in the first block and the total numbe rof tx that were +// included in a block +// By default, the tx is a simple transfer, but a TransactionConfig can be specified as an option +export async function fillBlockWithTx( + context: Context, + numberOfTx: number, + customTxConfig: TransactionConfig = basicTransfertx +): Promise { + let nonce: number = await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT); + + const numberArray = new Array(numberOfTx).fill(1); + + interface ErrorReport { + [key: string]: { + [key: string]: number; + }; + } + + let errorReport: ErrorReport = { + signing: {}, + customreq: {}, + }; + + function reportError(e, domain: string) { + let message: string = e.error ? e.error.message : e.message ? e.message : JSON.stringify(e); + if (errorReport[domain][message]) { + errorReport[domain][message] += 1; + } else { + errorReport[domain][message] = 1; + } + } + + const startSigningTime: number = Date.now(); + + // First sign all transactions + + let txList: (Error | SignedTransaction)[] = await serialSignTx( + context.web3, + numberOfTx, + nonce, + customTxConfig + ); + + const signingTime: number = Date.now() - startSigningTime; + + console.log( + "Time it took to sign " + txList.length + " tx is " + signingTime / 1000 + " seconds" + ); + + const startSendingTime: number = Date.now(); + + //Then, send them to the pool + + let respList: (Error | JsonRpcResponse)[] = await serialSendTx(context.web3, numberOfTx, txList); + + respList.forEach((res) => { + if (isJsonRpcResponse(res) && res.error) { + reportError(res.error, "customreq"); + } else if (!isJsonRpcResponse(res)) { + reportError(res, "signing"); + } + }); + + const sendingTime: number = Date.now() - startSendingTime; + + console.log( + "Time it took to send " + respList.length + " tx is " + sendingTime / 1000 + " seconds" + ); + + console.log("Error Report : ", errorReport); + + console.log( + "created block in ", + (await createAndFinalizeBlock(context.polkadotApi)) / 1000, + " seconds" + ); + + let numberOfBlocks = 0; + let block = await context.web3.eth.getBlock("latest"); + let txPassed: number = block.transactions.length; + const txPassedFirstBlock: number = txPassed; + console.log( + "block.gasUsed", + block.gasUsed, + "block.number", + block.number, + "block.transactions.length", + block.transactions.length + ); + + let i: number = 2; + + while (block.transactions.length !== 0) { + await createAndFinalizeBlock(context.polkadotApi); + + block = await context.web3.eth.getBlock("latest"); + console.log( + "following block, block" + i + ".gasUsed", + block.gasUsed, + "block" + i + ".number", + block.number, + "block" + i + ".transactions.length", + block.transactions.length + ); + txPassed += block.transactions.length; + numberOfTx += 1; + i += 1; + } + + return { txPassed, txPassedFirstBlock, sendingTime, signingTime, numberOfBlocks }; +} diff --git a/tests/tests/util/index.ts b/tests/tests/util/index.ts new file mode 100644 index 0000000000000..e131112517b28 --- /dev/null +++ b/tests/tests/util/index.ts @@ -0,0 +1,9 @@ +export { startMoonbeamNode, describeWithMoonbeam } from "./testWithMoonbeam"; +export { fillBlockWithTx } from "./fillBlockWithTx"; +export { + customRequest, + wrappedCustomRequest, + deployContractManualSeal, + callContractFunctionMS, +} from "./web3Requests"; +export { createAndFinalizeBlock } from "./polkadotApiRequests"; diff --git a/tests/tests/util/polkadotApiRequests.ts b/tests/tests/util/polkadotApiRequests.ts new file mode 100644 index 0000000000000..e01fe746ecd04 --- /dev/null +++ b/tests/tests/util/polkadotApiRequests.ts @@ -0,0 +1,13 @@ +import { ApiPromise } from "@polkadot/api"; + +// Create a block and finalize it. +// It will include all previously executed transactions since the last finalized block. +export async function createAndFinalizeBlock(api: ApiPromise): Promise { + const startTime: number = Date.now(); + try { + await api.rpc.engine.createBlock(true, true); + } catch (e) { + console.log("ERROR DURING BLOCK FINALIZATION", e); + } + return Date.now() - startTime; +} diff --git a/tests/tests/util.ts b/tests/tests/util/testWithMoonbeam.ts similarity index 70% rename from tests/tests/util.ts rename to tests/tests/util/testWithMoonbeam.ts index 1715d75e271ca..c0d7b0173a443 100644 --- a/tests/tests/util.ts +++ b/tests/tests/util/testWithMoonbeam.ts @@ -1,52 +1,17 @@ import Web3 from "web3"; import { ApiPromise, WsProvider } from "@polkadot/api"; -import { JsonRpcResponse } from "web3-core-helpers"; import { spawn, ChildProcess } from "child_process"; - -export const PORT = 19931; -export const RPC_PORT = 19932; -export const WS_PORT = 19933; -export const SPECS_PATH = `./moonbeam-test-specs`; - -export const DISPLAY_LOG = process.env.MOONBEAM_LOG || false; -export const MOONBEAM_LOG = process.env.MOONBEAM_LOG || "info"; - -export const BINARY_PATH = - process.env.BINARY_PATH || `../node/standalone/target/release/moonbase-standalone`; -export const SPAWNING_TIME = 30000; - -export async function customRequest(web3: Web3, method: string, params: any[]) { - return new Promise((resolve, reject) => { - (web3.currentProvider as any).send( - { - jsonrpc: "2.0", - id: 1, - method, - params, - }, - (error: Error | null, result?: JsonRpcResponse) => { - if (error) { - reject( - `Failed to send custom request (${method} (${params.join(",")})): ${ - error.message || error.toString() - }` - ); - } - resolve(result); - } - ); - }); -} - -// Create a block and finalize it. -// It will include all previously executed transactions since the last finalized block. -export async function createAndFinalizeBlock(web3: Web3) { - const response = await customRequest(web3, "engine_createBlock", [true, true, null]); - if (!response.result) { - throw new Error(`Unexpected result: ${JSON.stringify(response)}`); - } -} +import { + BINARY_PATH, + DISPLAY_LOG, + MOONBEAM_LOG, + PORT, + RPC_PORT, + SPAWNING_TIME, + SPECS_PATH, + WS_PORT, +} from "../constants"; export interface Context { web3: Web3; @@ -128,8 +93,8 @@ export async function startMoonbeamNode( binary.stdout.on("data", onData); }); - const polkadotJsTypes = require("../../polkadot-js/standalone-types.json"); - const polkadotJsRpc = require("../../polkadot-js/frontier-rpc-types"); + const polkadotJsTypes = require("../../../polkadot-js/standalone-types.json"); + const polkadotJsRpc = require("../../../polkadot-js/frontier-rpc-types"); const wsProvider = new WsProvider(`ws://localhost:${WS_PORT}`); const polkadotApi = await ApiPromise.create({ diff --git a/tests/tests/util/web3Requests.ts b/tests/tests/util/web3Requests.ts new file mode 100644 index 0000000000000..9a7754d8668cc --- /dev/null +++ b/tests/tests/util/web3Requests.ts @@ -0,0 +1,109 @@ +import Web3 from "web3"; +import { JsonRpcResponse } from "web3-core-helpers"; +import { TransactionReceipt } from "web3-core"; +import { AbiItem } from "web3-utils"; +import { Contract } from "web3-eth-contract"; +import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "../constants"; +import { createAndFinalizeBlock } from "./polkadotApiRequests"; +import { ApiPromise } from "@polkadot/api"; +import { Context } from "./testWithMoonbeam"; + +// make a web3 request, adapted to manual seal testing +export async function customRequest(web3: Web3, method: string, params: any[]) { + return new Promise((resolve, reject) => { + (web3.currentProvider as any).send( + { + jsonrpc: "2.0", + id: 1, + method, + params, + }, + (error: Error | null, result?: JsonRpcResponse) => { + if (error) { + reject( + `Failed to send custom request (${method} (${params.join(",")})): ${ + error.message || error.toString() + }` + ); + } + resolve(result); + } + ); + }); +} + +// wrap the above function to catch errors and return them into a JsonRpc format +export async function wrappedCustomRequest( + web3: Web3, + method: string, + params: any[] +): Promise { + try { + let resp = await customRequest(web3, method, params); + return resp; + } catch (e) { + return { + jsonrpc: "req error", + id: 0, + error: typeof e === "string" ? e.toString() : JSON.stringify(e), + }; + } +} + +// Deploy and instantiate a contract with manuel seal +export async function deployContractManualSeal( + api: ApiPromise, + web3: Web3, + contractByteCode: string, + contractABI: AbiItem[], + account: string = GENESIS_ACCOUNT, + privateKey: string = GENESIS_ACCOUNT_PRIVATE_KEY +): Promise { + const tx = await web3.eth.accounts.signTransaction( + { + from: account, + data: contractByteCode, + value: "0x00", + gasPrice: "0x01", + gas: "0x100000", + }, + privateKey + ); + await customRequest(web3, "eth_sendRawTransaction", [tx.rawTransaction]); + await createAndFinalizeBlock(api); + let rcpt: TransactionReceipt = await web3.eth.getTransactionReceipt(tx.transactionHash); + return new web3.eth.Contract(contractABI, rcpt.contractAddress); +} + +interface FnCallOptions { + account?: string; + privateKey?: string; + gas?: string; +} + +// Call a function from a contract instance using manual seal +export async function callContractFunctionMS( + context: Context, + contractAddress: string, + bytesCode: string, + options?: FnCallOptions +) { + try { + const contractCall = { + from: options && options.account ? options.account : GENESIS_ACCOUNT, + to: contractAddress, + data: bytesCode, + gasPrice: "0x01", + gas: options && options.gas ? options.gas : "0x100000", + }; + const txCall = await context.web3.eth.accounts.signTransaction( + contractCall, + options && options.privateKey ? options.privateKey : GENESIS_ACCOUNT_PRIVATE_KEY + ); + await customRequest(context.web3, "eth_sendRawTransaction", [txCall.rawTransaction]); + return await createAndFinalizeBlock(context.polkadotApi); + } catch (e) { + console.log("error caught during callContractFunctionMS", e); + throw new Error(e); + } +}