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 83655dc40d..3bb7b3a74d 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -77,6 +77,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]) } @@ -92,18 +105,21 @@ async function fundAddress(addr, amount="1000000000000000000000") { } 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") { @@ -155,8 +171,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") } @@ -219,15 +235,18 @@ 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` + const command = `seid tx tokenfactory create-denom ${name} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode sync -o json` + await execute(command); + // 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` await execute(mint_command); + await waitForBlocks() - const send_command = `seid tx bank send ${from} ${recipient} ${amount}${token_denom} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode block -o json` + const send_command = `seid tx bank send ${from} ${recipient} ${amount}${token_denom} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode sync -o json` await execute(send_command); + await waitForBlocks() return token_denom } @@ -402,19 +421,52 @@ 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. + const 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(); + if (cur > maxIdBefore) { + const detail = JSON.parse(await execute(`seid q gov proposal ${cur} -o json`)); + const observedTitle = detail.content?.title ?? detail.title; + if (observedTitle !== title) { + throw new Error(`proposal ${cur} title "${observedTitle}" does not match submitted "${title}" — concurrent submission?`); + } + return String(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) { @@ -498,9 +550,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++) {