Gas: Pair registry lookup is O(n); exhausts CosmWasm gas at 1000+ pairs #122
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#122
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
assert_pair_in_registryin the factory contract performs a linear scan over all indexed pairs (PAIR_COUNT+PAIR_INDEX). At 1000+ pairs this exhausts CosmWasm gas limits, blocking governance flows that validate pair membership.Location
File:
smartcontracts/contracts/factory/src/contract.rs(approximately line 229)Reported behavior
Impact
Used by (among others): SetPairFee, SetPairHooks, SweepPair, SetPairPaused, and similar entry points. Linear cost per check makes these operations impractical at scale.
Suggested direction
Add a reverse index (e.g.
Map<Addr, bool>orMap<Addr, u64>) maintained on pair register/update/remove so membership lookup is O(1) and gas stays bounded.Acceptance criteria (draft)
PAIR_COUNT(or document expected limits).assert_pair_in_registry(and any related paths).Opened for investigation from an external report; details and line numbers should be verified against current
main.mentioned in commit
bf8e55a6a7mentioned in commit
7084a9776aImplemented (GitLab #122)
Summary: Factory
assert_pair_in_registrynow uses storage mappair_addr_reg(PAIR_ADDR_REGISTERED) for O(1) membership checks instead of scanningPAIR_INDEX.Code:
smartcontracts/contracts/factory/src/state.rs,contract.rs(cw2 version 1.1.0, migrate backfills reverse map fromPAIR_INDEX). Reply handler registers each new pair addr; migrate path for legacy 1.0.0 factories.Docs:
docs/contracts-terraclassic.md§ Factory storage & upgrades,skills/AGENTS_TERRACLASSIC_GAS.md,docs/runbooks/wasm-admin-migration.md.Tests: Unit migrate backfill; integration
test_factory_many_pairs_governance_fee_update_uses_registry_lookup(35 pairs +SetPairFeeon last); fullcargo test -p cl8y-dex-testsgreen onmain(follow-up commit fixes per-blockCreatePairin that test).@brouie please verify on your side:
Checklist
migrateonce; confirm governance ops (SetPairFee,SweepPair, etc.) succeed on a pair deep in the registry without gas exhaustion.CreatePair, everypair_index[i].contract_addrhaspair_addr_regset (spot-check raw keys or behaviour).PairNotInRegistry.Pairsquery pagination / indexer discovery still behaves as before (linear scan there is intentional).SetDiscountRegistryAll/ governance fan-out still intentionally iterate all pairs (gas scales with pair count).Leaving issue open per request.
mentioned in commit
d283a695c2mentioned in issue #133
mentioned in issue #121
@PlasticDigits — source-side verification PASS on the O(1) registry lookup.
ran your named test:
full workspace 321/321 PASS, 0 failures.
source review:
PAIR_ADDR_REGISTERED: Map<Addr, bool>declared instate.rs:37(keypair_addr_regmatches your description)assert_pair_in_registryrewritten toPAIR_ADDR_REGISTERED.has(deps.storage, pair_addr.clone())atcontract.rs:258— pure O(1) map lookup, noPAIR_INDEXscanCreatePairatcontract.rs:702PAIR_INDEXatcontract.rs:765for legacy 1.0.0 factoriesCONTRACT_VERSION = "1.1.0"atcontract.rs:22, migrate usescw2::ensure_from_older_versionatcontract.rs:759— proper cw2 versioningcontract.rs:777-814asserts pairs are present inPAIR_ADDR_REGISTEREDafter backfillcross-checked all five governance entry points use the same
assert_pair_in_registry:so SetPairFee, SetPairHooks, SweepPair, SetPairPaused etc. all benefit from the O(1) lookup. matches the audit fix scope.
still pending: live ops verification — store 1.1.0 wasm → migrate once on a 1.0.0 factory → confirm governance ops on a deep-index pair succeed. plus invariant spot-check that every
pair_index[i].contract_addrhaspair_addr_regset after a freshCreatePairpost-migrate. queued behind the other live-walk debt.regression noted: random non-registry address rejected with
PairNotInRegistryis preserved by the new path (the.has()returns false, falls through to the existing error).Pairsquery pagination +SetDiscountRegistryAllbroadcast-all behavior unchanged — that intentional iteration still scales with pair count, which is the contract for those entry points.ready for close on your side once you are happy with source PASS, or hold for chain-ops walk.
mentioned in issue #258