diff --git a/contracts/test/EVMPrecompileTest.js b/contracts/test/EVMPrecompileTest.js index 88450768e6..1ea20ba2d3 100755 --- a/contracts/test/EVMPrecompileTest.js +++ b/contracts/test/EVMPrecompileTest.js @@ -4,7 +4,7 @@ const fs = require('fs'); const path = require('path'); const { expectRevert } = require('@openzeppelin/test-helpers'); -const { setupSigners, getAdmin, deployWasm, storeWasm, execute, isDocker, ABI, createTokenFactoryTokenAndMint, getSeiBalance, rawHttpDebugTraceWithCallTracer} = require("./lib"); +const { setupSigners, getAdmin, deployWasm, storeWasm, execute, isDocker, ABI, createTokenFactoryTokenAndMint, getSeiBalance, rawHttpDebugTraceWithCallTracer, proposeParamChange} = require("./lib"); function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -125,8 +125,16 @@ describe("EVM Precompile Tester", function () { let govProposal; before(async function () { - const govProposalResponse = JSON.parse(await execute(`seid tx gov submit-proposal param-change ../contracts/test/param_change_proposal.json --from admin --fees 20000usei -b block -y -o json`)) - govProposal = govProposalResponse.logs[0].events[3].attributes[1].value; + const proposalSpec = require('./param_change_proposal.json'); + govProposal = await proposeParamChange( + proposalSpec.title, + proposalSpec.description, + proposalSpec.changes, + "200000000usei", + "20000usei", + "admin", + proposalSpec.is_expedited, + ); const signer = accounts[0].signer const contractABIPath = '../../precompiles/gov/abi.json'; diff --git a/contracts/test/lib.js b/contracts/test/lib.js index 73456b9918..d291a49fa6 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -82,6 +82,19 @@ async function delay() { await sleep(1000) } +// Default 2 because the very next block after submit can be empty +// (tx still in mempool, lands one block later). +async function waitForBlocks(blocks=2, timeoutMs=15000) { + const start = await ethers.provider.getBlockNumber() + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + const cur = await ethers.provider.getBlockNumber() + if (cur >= start + blocks) return + await sleep(50) + } + throw new Error(`block didn't advance by ${blocks} in ${timeoutMs}ms (start=${start})`) +} + async function getCosmosTx(provider, evmTxHash) { return await provider.send("sei_getCosmosTx", [evmTxHash]) } @@ -91,24 +104,25 @@ async function getEvmTx(provider, cosmosTxHash) { } async function fundAddress(addr, amount="1000000000000000000000") { - const result = await evmSend(addr, adminKeyName, amount) - await delay() - return result + return await evmSend(addr, adminKeyName, amount) } async function evmSend(addr, fromKey, amount="10000000000000000000000000") { - const output = await execute(`seid tx evm send ${addr} ${amount} --from ${fromKey} -b block -y`); + const output = await execute(`seid tx evm send ${addr} ${amount} --from ${fromKey} -b sync -y`); + await waitForBlocks() return output.replace(/.*0x/, "0x").trim() } async function bankSend(toAddr, fromKey, amount="100000000000", denom="usei") { - const result = await execute(`seid tx bank send ${fromKey} ${toAddr} ${amount}${denom} -b block --fees 20000usei -y`); - await delay() + const result = await execute(`seid tx bank send ${fromKey} ${toAddr} ${amount}${denom} -b sync --fees 20000usei -y`); + await waitForBlocks() return result } async function fundSeiAddress(seiAddr, amount="100000000000", denom="usei", funder=adminKeyName) { - return await execute(`seid tx bank send ${funder} ${seiAddr} ${amount}${denom} -b block --fees 20000usei -y`); + const result = await execute(`seid tx bank send ${funder} ${seiAddr} ${amount}${denom} -b sync --fees 20000usei -y`); + await waitForBlocks() + return result } async function getSeiBalance(seiAddr, denom="usei") { @@ -141,7 +155,6 @@ async function getNativeAccount(keyName) { await associateKey(adminKeyName) const seiAddress = await getKeySeiAddress(keyName) await fundSeiAddress(seiAddress) - await delay() const evmAddress = await getEvmAddress(seiAddress) return { seiAddress, @@ -160,8 +173,8 @@ async function getKeySeiAddress(name) { async function associateKey(keyName) { try { - await execute(`seid tx evm associate-address --from ${keyName} -b block`) - await delay() + await execute(`seid tx evm associate-address --from ${keyName} -b sync`) + await waitForBlocks() }catch(e){ console.log("skipping associate") } @@ -243,15 +256,21 @@ async function rawHttpDebugTraceWithCallTracer(txHash) { } async function createTokenFactoryTokenAndMint(name, amount, recipient, from=adminKeyName) { - const command = `seid tx tokenfactory create-denom ${name} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode block -o json` - const output = await execute(command); - const response = JSON.parse(output) - const token_denom = getEventAttribute(response, "create_denom", "new_token_denom") - const mint_command = `seid tx tokenfactory mint ${amount}${token_denom} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode block -o json` - await execute(mint_command); - - const send_command = `seid tx bank send ${from} ${recipient} ${amount}${token_denom} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode block -o json` - await execute(send_command); + const command = `seid tx tokenfactory create-denom ${name} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode sync -o json` + const response = JSON.parse(await execute(command)) + if (response.code !== 0) throw new Error(`create-denom rejected: ${response.raw_log}`) + // Tokenfactory denom is deterministic: factory//. + const token_denom = `factory/${await getKeySeiAddress(from)}/${name}` + await waitForBlocks() + const mint_command = `seid tx tokenfactory mint ${amount}${token_denom} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode sync -o json` + const mintResp = JSON.parse(await execute(mint_command)) + if (mintResp.code !== 0) throw new Error(`mint rejected: ${mintResp.raw_log}`) + await waitForBlocks() + + const send_command = `seid tx bank send ${from} ${recipient} ${amount}${token_denom} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode sync -o json` + const sendResp = JSON.parse(await execute(send_command)) + if (sendResp.code !== 0) throw new Error(`bank send rejected: ${sendResp.raw_log}`) + await waitForBlocks() return token_denom } @@ -426,19 +445,50 @@ async function proposeParamChange(title, description, changes, deposit="20000000 }; const proposalJson = JSON.stringify(proposal); const tempFile = `/tmp/param_change_${Date.now()}.json`; - + // Use base64 encoding to avoid quote escaping issues in Docker const base64Json = Buffer.from(proposalJson).toString('base64'); await execute(`echo ${base64Json} | base64 -d > ${tempFile}`); - - const command = `seid tx gov submit-proposal param-change ${tempFile} --from ${from} --fees ${fees} -y -o json --broadcast-mode=block`; + + // Identify the new proposal by diffing gov state before vs after submit. + let maxIdBefore = await maxProposalId(); + + const command = `seid tx gov submit-proposal param-change ${tempFile} --from ${from} --fees ${fees} -y -o json -b sync`; const output = await execute(command); await execute(`rm ${tempFile}`); const response = JSON.parse(output); if (response.code !== 0) { throw new Error(`Failed to submit proposal: ${response.raw_log}`); } - return getEventAttribute(response, "submit_proposal", "proposal_id"); + + const deadline = Date.now() + 30000; + while (Date.now() < deadline) { + const cur = await maxProposalId(); + for (let id = maxIdBefore + 1; id <= cur; id++) { + const detail = JSON.parse(await execute(`seid q gov proposal ${id} -o json`)); + const observedTitle = detail.content?.title ?? detail.title; + if (observedTitle === title) return String(id); + } + maxIdBefore = cur; + await sleep(250); + } + throw new Error(`proposal submitted (tx ${response.txhash}) but did not appear in gov state within 30s`); +} + +// Returns the highest existing proposal id, or 0 if there are no proposals. +async function maxProposalId() { + let out; + try { + // seid exits non-zero ("Error: no proposals found") on an empty + // gov set; the try/catch treats that as id=0. + out = await execute(`seid q gov proposals --reverse --limit 1 -o json 2>/dev/null`); + } catch (e) { + return 0; + } + if (!out || !out.trim()) return 0; + const proposals = JSON.parse(out).proposals || []; + if (proposals.length === 0) return 0; + return Number(proposals[0].proposal_id || proposals[0].id); } async function proposeDisableWasm(title="Disable WASM", description="Disable cosmwasm store code and instantiate operations", deposit="200000000usei", fees="200000usei", from=adminKeyName) { @@ -522,9 +572,9 @@ async function ensureWasmDisabled(from=adminKeyName) { async function passProposal(proposalId, desposit="200000000usei", fees="200000usei", from=adminKeyName) { if(await isDocker()) { - await executeOnAllNodes(`seid tx gov vote ${proposalId} yes --from node_admin -b block -y --fees ${fees}`) + await executeOnAllNodes(`seid tx gov vote ${proposalId} yes --from node_admin -b sync -y --fees ${fees}`) } else { - await execute(`seid tx gov vote ${proposalId} yes --from ${from} -b block -y --fees ${fees}`) + await execute(`seid tx gov vote ${proposalId} yes --from ${from} -b sync -y --fees ${fees}`) } // Poll for proposal status with shorter delay for faster tests for(let i=0; i<200; i++) { @@ -604,10 +654,8 @@ async function setupSigners(signers) { let seiAddress = await associateSigner(signer); if (seiAddress) { await fundSeiAddress(seiAddress); - await delay() } else { await fundAddress(evmAddress); - await delay() const resp = await signer.sendTransaction({ to: evmAddress, value: 0