Skip to content

Tests: Precompile Tests Phase 3 (PLT-372) - #3985

Open
alexander-sei wants to merge 2 commits into
mainfrom
alexanderh/plt-372-precompile-tests-phase-3
Open

Tests: Precompile Tests Phase 3 (PLT-372)#3985
alexander-sei wants to merge 2 commits into
mainfrom
alexanderh/plt-372-precompile-tests-phase-3

Conversation

@alexander-sei

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

Phase 3 of the precompile test refactor (PLT-372), building on the phase 1 framework (#3777) and the phase 2 coverage (#3786).

Phase 2 covered the wasm-free precompiles as they existed then. Since that merged, Generate v6.7 precompiles (#3961), the scoped module authorizations (#3893, #3846) and the module Msg rpc surface (#3825) landed. This phase brings the suite back in line with the deployed interface.

New specs

Precompile Spec Coverage
auth (0x…100D) auth.spec.ts account / accounts / params / nextAccountNumber, LCD parity incl. the nested base account module accounts wrap theirs in
authz (0x…100E) authz.spec.ts grants / granterGrants / granteeGrants; the non-empty fixture is a staking.grantStakingAuthorization, since authz itself cannot grant
evidence (0x…100F) evidence.spec.ts evidence / allEvidence against the module's own list
mint (0x…1012) mint.spec.ts params / minter vs Sei's own /seichain/mint/v1beta1/... routes
params (0x…1013) params.spec.ts params(subspace, key) vs the staking module, plus the unknown/empty subspace reverts
slashing (0x…1014) slashing.spec.ts params / signingInfo / signingInfos, grant + revoke of unjail authorization, and the unjail guards
upgrade (0x…1015) upgrade.spec.ts currentPlan / appliedPlan / upgradedConsensusState / moduleVersions

Existing specs extended

  • bankspendableBalances, totalSupply, params, denomMetadata (via a tokenfactory denom), denomsMetadata (pages until it reaches that denom, so nextKey round-trips).
  • stakinggrantStakingAuthorizationdelegateWithAuthorization → Cosmos-side delegation → revokeStakingAuthorization → reverts; redelegate/undelegateWithAuthorization; and the remaining query surface (validators, validatorDelegations, unbondingDelegation, delegatorDelegations, redelegations, historicalInfo, …).
  • gov — vote and proposal authorizations (grant/voteWithAuthorization/submitProposalWithAuthorization/revoke), plus proposal, proposals, getVote, votes, params, getDeposit, deposits, tallyResult.
  • distributionwithdrawValidatorCommission, the withdraw authorizations, and params, validatorOutstandingRewards, validatorCommission, validatorSlashes, delegationRewards, delegatorValidators, delegatorWithdrawAddress, communityPool.

Framework

  • New addresses on PRECOMPILE_ADDRESSES.
  • cosmosUtils.cosmosRest — an LCD reader for the modules cosmjs does not wrap (mint, params, upgrade, evidence, slashing, authz, auth). Every path was taken from the module's decoded pb.gw.go gateway pattern rather than guessed, and every response field from its proto tag; the API server marshals with OrigName + EmitDefaults, so the reads are snake_case.
  • Bootstrap pool 48 → 80, with the claimPool budget (29) recorded next to it — a non-blocking note from the phase 2 review.

Out of scope, unchanged: wasm-gated flows (wasmd, pointer addCW*, solo), the ibc precompile, and feegrant (0x…1010), which is not registered and whose module was removed in #3958. oracle stays the phase 2 retirement assertion.

Testing performed to validate your change

  • npx tsc --noEmit clean; no lint errors.
  • Every assertion was reviewed against the Go executor and the sei-cosmos keeper behind it rather than against assumption. That pass corrected, among others: historicalInfo pinning an unreachable executor fallback instead of the querier's historical info for height %d not found; nextAccountNumber reading next_account_number where the response field is count; and mint's LCD path, which was three guesses deep with the real route last.
  • It also removed tests that could not fail — a historicalInfo try/catch asserting success in both branches, evidence hardcoding an empty chain in four places, authz index-matching expirations across two independently ordered lists, bank.denomsMetadata asserting only "is an array" — and replaced receipt-only verification of the authorized reward withdrawal with the DelegationRewardsWithdrawn credit its non-authz sibling already pins.

Not yet run against a live cluster. The remaining risk is concentrated in real chain responses, so this wants a make docker-cluster-start + npm run precompile:ci run before merge; I will post the result on this PR.

🤖 Generated with Cursor

Made with Cursor

Extends the precompile suite to the non-wasm precompiles generated for
v6.7 and to the methods added to the existing ones since phase 2, so the
suite tracks the deployed interface rather than the phase 2 snapshot.

New specs: auth (0x100D), authz (0x100E), evidence (0x100F), mint
(0x1012), params (0x1013), slashing (0x1014), upgrade (0x1015).

Existing specs grow to cover the scoped module authorizations and the
query surface they shipped alongside: bank spendableBalances/totalSupply/
params/denomMetadata/denomsMetadata; staking grant/delegate/redelegate/
undelegate/revokeWithAuthorization plus the remaining delegation and
validator queries; gov vote and proposal authorizations plus
proposal/votes/deposits/tally; distribution withdrawValidatorCommission,
the withdraw authorizations, and the validator/delegator queries.

Framework: the new addresses join PRECOMPILE_ADDRESSES, cosmosUtils gains
a cosmosRest LCD reader for the modules cosmjs does not wrap, and the
bootstrap pool grows 48 -> 80 with the claim budget recorded.

Wasm-gated flows (wasmd, pointer addCW*, solo), ibc, and the unregistered
feegrant precompile stay out of scope.

Co-Authored-By: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 24, 2026, 12:15 AM

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d1c4802. Configure here.

Comment thread integration_test/precompile_tests/precompiles/gov.spec.ts Outdated
@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.88%. Comparing base (73f917b) to head (f996dac).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3985      +/-   ##
==========================================
- Coverage   58.98%   57.88%   -1.10%     
==========================================
  Files        2310     2211      -99     
  Lines      197500   185930   -11570     
==========================================
- Hits       116487   107626    -8861     
+ Misses      70256    68486    -1770     
+ Partials    10757     9818     -939     
Flag Coverage Δ
sei-db 69.80% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.
see 99 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase 3 of the precompile suite is well grounded — every new address, ABI field name, LCD gateway path and Go revert string I spot-checked matches the deployed interface, and the claimPool budget note (29) is accurate. One new assertion in slashing.spec.ts compares two unpinned reads of per-block-mutating signing info and will fail intermittently; two smaller test-strength notes are non-blocking.

Findings: 1 blocking | 2 non-blocking | 3 posted inline

Blockers

  • None at the file/PR level.
  • 1 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • None at the file/PR level.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

);
const first = listed.signingInfos[0];
const viaOne: ethers.Result = await slashing.signingInfo(first.validatorAddress);
expect(asSigningInfo(viaOne)).to.deep.equal(asSigningInfo(first));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocker] signingInfos() and signingInfo() are two separate eth_calls at latest, and the fields being compared move every block: HandleValidatorSignature bumps signInfo.IndexOffset for every validator in the last commit (sei-cosmos/x/slashing/keeper/infractions.go:72), and MissedBlocksCounter can change too. Whenever a block commits between the two calls this deep.equal fails on indexOffset — an intermittent CI failure with no real defect behind it.

The suite already has the fix pattern for exactly this: distribution.spec.ts pins both reads to one height ("Rewards grow every block — pin both reads to the same height or the byte-equality below races block production"). Take const blockTag = await provider.getBlockNumber() and pass { blockTag } to both calls, or restrict asSigningInfo to the fields that don't move (validatorAddress, startHeight, jailedUntil, tombstoned).

// Supply can grow between reads (block rewards / mint); bracket the
// paginated totalSupply read with two supply(usei) reads.
const before: bigint = await bank.supply('usei');
const [coins] = (await bank.totalSupply(new Uint8Array())) as [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] totalSupply(empty) reads only the first page (the precompile passes PageRequest{Key: …} with no limit, so the module default of 100 applies). Supply is keyed by denom, and factory/… sorts before usei, so on a chain that has accumulated ~100 tokenfactory denoms — one per run of the denomMetadata test below, plus anything else on the chain — usei falls off page one and this fails with "totalSupply must contain a usei entry" rather than a real defect. The denomsMetadata test right below already walks pages for this reason; consider doing the same here (or at least noting the single-page assumption).


type DistrCoin = { amount: bigint; decimals: bigint; denom: string };

function expectCoinArray(coins: readonly DistrCoin[], label: string): void {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] expectCoinArray degenerates to to.be.an('array') when the array is empty — the per-coin loop never runs — which is the shape the PR description calls out as removed elsewhere ("bank.denomsMetadata asserting only 'is an array'"). Three of the four call sites have a fixture that guarantees content (the delegator has an accruing delegation to validator, which is bonded), so asserting coins.length > 0 there would make them able to fail. communityPool is the one where emptiness is genuinely chain-dependent (community_tax may be 0), so it's worth either asserting non-empty explicitly or saying in the label why it can't be.

slashing: signingInfos and signingInfo were two eth_calls at latest, and
HandleValidatorSignature advances indexOffset for every validator in the
last commit, so a block committing between them failed the equality with
no defect behind it. Both reads now pin the same blockTag, which keeps the
full-struct comparison rather than dropping the moving fields.

bank: totalSupply sends no page limit, so the module default of 100
applies and every factory/... denom this suite mints sorts before usei.
It now walks pages the way denomsMetadata already does instead of
assuming usei is on page one.

distribution: expectCoinArray degenerated to an "is an array" check on an
empty array. It now requires non-empty by default, which the fixture
guarantees for the delegation and validator reads, and communityPool opts
out because sei-cosmos defaults community_tax to 0.

gov: the dispatch section claimed gov has no view methods while a new case
asserted proposal() answers under STATICCALL. The executor dispatches its
queries before the readOnly check, so the label now says transaction
methods, and the duplicate vote rejection that mined the same failing tx
twice is gone.

Co-Authored-By: Cursor <cursoragent@cursor.com>
@alexander-sei

Copy link
Copy Markdown
Contributor Author

Addressed all three of @seidroid's findings, plus the Bugbot note, in f996dac. Each claim was re-verified against the source rather than taken on the review's word.

[blocker] slashing.spec.ts — racing block production. Confirmed: infractions.go:72 advances IndexOffset for every validator in the last commit, so the two eth_calls at latest really could straddle a block. Both reads now pin the same blockTag, following the precedent in distribution.spec.ts. I took the pinning option rather than narrowing asSigningInfo, since that keeps the full-struct equality instead of dropping the moving fields.

[suggestion] bank.spec.ts — single-page assumption. Real latent flake, and self-inflicted: this suite mints a tokenfactory denom on every run, and factory/… sorts before usei. totalSupply now walks pages the way the denomsMetadata test below it does. The supply('usei') bracketing is unchanged, since supply legitimately grows between reads.

[suggestion] distribution.spec.ts — vacuous on empty. expectCoinArray now requires non-empty by default, with a mayBeEmpty opt-out for communityPool — sei-cosmos defaults community_tax to 0% (x/distribution/types/params.go:28), unlike upstream's 2%, so an empty pool is the correct answer there rather than a defect.

One correction to the reasoning, since the conclusion is right but the mechanism isn't: the accruing delegation does not by itself guarantee validatorCommission is non-empty — commission is the operator's cut, so a 0%-rate validator would leave it empty regardless of how healthy the delegation is. What actually guarantees it here is that the devnet's genesis validators are created by seid gentx with no --commission-rate (docker/localnode/scripts/step1_configure_init.sh:58), taking the CLI default of 10%, and DefaultMinCommissionRate is 5% so no validator can drop to zero. I tightened the assertion and recorded that reason.

[Bugbot, low] gov.spec.ts — self-contradicting dispatch section. Also correct. Execute dispatches all eight query methods before the readOnly check, so gov views do answer under STATICCALL and only transaction methods are refused; the stale "gov has no view methods" label is now accurate. The duplicate vote STATICCALL case is removed, so the suite no longer mines the same failing tx twice.

npx tsc --noEmit clean, no lint errors. Still not run against a live cluster — that remains the gate before merge and I'll post the result here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant