Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions contracts/test/EVMPrecompileTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -125,8 +125,19 @@ 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;
// Use lib.js proposeParamChange (Autobahn-compatible: -b sync + poll
// gov state) instead of an inline `seid tx -b block` which hangs
// 60s under Autobahn. Mirrors ../contracts/test/param_change_proposal.json
// (kept on disk for any tooling that still consumes it).
govProposal = await proposeParamChange(
"Gov Param Change",
"Update quorum to 0.45",
[{ subspace: "gov", key: "tallyparams", value: { quorum: "0.45" } }],
"200000000usei",
"20000usei",
"admin",
false, // not expedited — matches the JSON file
);

const signer = accounts[0].signer
const contractABIPath = '../../precompiles/gov/abi.json';
Expand Down
127 changes: 106 additions & 21 deletions contracts/test/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ async function delay() {
await sleep(1000)
}

// Wait until the chain advances by `blocks` blocks. Used after `-b sync`
// submissions to give the chain time to include the tx — engine-agnostic
// substitute for a fixed sleep, since block times differ between CometBFT
// (~1s) and Autobahn (~100ms). Default is 2 blocks rather than 1 because
// the next block can be empty (the submitted tx is still in mempool and
// lands one block later); 2 blocks closes that race in nearly all cases
// without re-introducing a fixed sleep.
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])
}
Expand All @@ -92,18 +110,25 @@ 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`);
// -b sync (returns on mempool acceptance) instead of -b block (subscribes
// to EventDataTx). Under Autobahn, executeBlock doesn't fire EventDataTx,
// so -b block hangs to its 60s timeout. -b sync is sufficient because
// callers don't read the deliver_tx event payload from this output.
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") {
Expand Down Expand Up @@ -155,8 +180,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")
}
Expand Down Expand Up @@ -219,16 +244,36 @@ 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);
// -b sync everywhere (no -b block hang under Autobahn). The tokenfactory
// denom format is factory/<creator-bech32>/<subdenom>, so we construct
// the denom locally from the from-key's address rather than reading
// the create_denom event from the tx response — -b sync doesn't carry
// deliver_tx events.
const creator = await getKeySeiAddress(from);
const token_denom = `factory/${creator}/${name}`;

const createOut = await execute(`seid tx tokenfactory create-denom ${name} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode sync -o json`);
const createResp = JSON.parse(createOut);
if (createResp.code !== 0) {
throw new Error(`createTokenFactoryTokenAndMint: create-denom rejected: ${createResp.raw_log}`);
}
await waitForBlocks();

const mintOut = await execute(`seid tx tokenfactory mint ${amount}${token_denom} --from ${from} --gas=5000000 --fees=1000000usei -y --broadcast-mode sync -o json`);
const mintResp = JSON.parse(mintOut);
if (mintResp.code !== 0) {
throw new Error(`createTokenFactoryTokenAndMint: mint rejected: ${mintResp.raw_log}`);
}
await waitForBlocks();

const sendOut = await execute(`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(sendOut);
if (sendResp.code !== 0) {
throw new Error(`createTokenFactoryTokenAndMint: bank send rejected: ${sendResp.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 block -o json`
await execute(send_command);
return token_denom
return token_denom;
}

async function getChainId() {
Expand Down Expand Up @@ -402,19 +447,59 @@ 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`;

// Snapshot max proposal id before submit so we can identify the new one
// by polling — see comment below for why we don't use the tx response.
const maxIdBefore = await maxProposalId();

// -b sync (returns on mempool acceptance) instead of -b block (subscribes
// to EventDataTx). Under Autobahn, executeBlock doesn't fire EventDataTx,
// so -b block hangs to its 60s timeout on every call. -b sync also means
// the tx response no longer carries the deliver_tx events — including the
// submit_proposal event we used to read proposal_id from — so we instead
// poll gov state until a new proposal appears.
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");

// Poll for the new proposal to land. Tx hash lookup (`seid q tx <hash>`)
// would need a Cosmos-side tx indexer, which Autobahn doesn't run.
// 30s should comfortably cover the inclusion + commit window.
const deadline = Date.now() + 30000;
while (Date.now() < deadline) {
const cur = await maxProposalId();
if (cur > maxIdBefore) 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 with "Error: no proposals found" on an empty
// gov set; treat that as id=0 and fall through to JSON parsing for
// the populated case. Avoid using a `|| echo` fallback inside the
// pipeline because the docker-exec wrapper double-quotes the inner
// command and a JSON literal with single quotes would terminate
// the outer quote region.
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) {
Expand Down Expand Up @@ -498,9 +583,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++) {
Expand Down
Loading