feat: V3 Grid vault + permissionless Rebalance (from #546) #617
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#617
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
Implement the V3 Grid vault: a pair-scoped CosmWasm strategy that owns resting limits, flips them buy↔sell with a user-set spread and no drift, and compounds leftover inventory on a permissionless
Rebalance. The crank caller may take 5% of accrued grid fees as a tip. This is the protocol half of #546 option E (architecture item 5).Not this issue: keeper binary (
grid-keeperpackage),/v3UI, APR chrome. Those are sibling issues linked from #546.Parent decision: #546.
Current codebase
The DEX already has a one-shot, one-sided limit ladder. It does not keep a maker in a range after fills. Filled makers receive the other token in their wallet; nothing re-escrows it on the opposite side. There is no grid/range strategy object and no vault that owns orders.
Cw20HookMsg::PlaceLimitOrderBatch/PlaceLimitOrderLadder. Ladder expands on-chain (equalonly). One side per tx — bid escrows token1, ask escrows token0. Mixed buy+sell grids need two placements.max_batch_rungs(factory default /SetPairLimitBatchMax). Hard ceilingMAX_LIMIT_BATCH_RUNGS_HARD_CAP= 100.execute_swapwalks the book undermax_maker_fills(hard cap 100) andMAX_SCAN_STEPS(500) — L5. Maker payouts are deferred CW20 transfers to the order owner at the end of the swap (#248). No opposite-side place.UpdateLimitOrderPricedoes not re-charge place fee, but cannot change side or size.GetDiscountuses the order owner. A vault thatSends CW20 is the owner — the depositor’s CL8Y tier does not apply unless the vault itself is registered / holds CL8Y.max_batch_rungs(#246). Pause blocks place/cancel/claim (L6).OrderStatus→Active/ParkedRefund/Unknown(L21). Indexerlifecycle_status=activeis not proof the row is still inORDERS(#530).community-tax-autolp) is a permissionlessSkimToLpcrank into v2 LP shares. It is not a limit grid and must not be reused as the V3 Grid vault.CreateGrid, noRebalance, no crank tip, no “on fill, place opposite” hook, no in-swap flip.Product confusion to avoid: this is maker inventory on the FIFO limit book, not v2 AMM LP (
provide_liquidity) and not/ust1mint (#531).Why this is needed
/limits.authzMsgExecuteContractgrant can cancel and sweep the grid. A vault that is the orderowneris the only non-custodial “set and forget” path accepted on #546.Rebalance, not a user Claim / Reinvest flow.Constraints / guardrails
insert_bid/insert_askinsideexecute_swapor themax_maker_fillswalk.OrderStatusremains the custody oracle.fee_bps.Rebalance.Rebalance { claim_tip: bool }. Iftrue, payfloor(unclaimed_grid_fees * 500 / 10000)(500 bps = 5%) toinfo.senderin the fee asset(s). Remainder stays in the position. Do not mint unbacked limits to pay the tip (fail closed).Rebalance. Independent ofmax_maker_fills. Bound inserts, CW20 sends, cancel/claim ids (≤ pairmax_batch_rungs, hard cap 100). Cheap no-op when nothing to do (A15).BlacklistCheckfor the beneficiary on deposit / withdraw / param update /Rebalance. Pair pause → no-op or revert cheaply; do not retry-loop. Blacklisted beneficiary: no withdraw, no new place; do not keep flipping.SKIP (no LocalTerra)withoutmake setup-cloud-localterra.indexer/into root Docker cargo (seeAGENTS.md§ Rust / Docker gotchas).Relevant files
smartcontracts/packages/dex-common/src/pair.rsPlaceLimitOrderBatch/PlaceLimitOrderLadder, hybrid paramssmartcontracts/packages/dex-common/src/limit_placement.rssmartcontracts/contracts/pair/src/limit_placement.rssmartcontracts/contracts/pair/src/orderbook.rssmartcontracts/contracts/pair/src/contract.rsexecute_swap,OrderStatus, pausesmartcontracts/contracts/pair/src/limit_batch_withdraw.rssmartcontracts/contracts/community-tax-autolp/smartcontracts/contracts/factory/src/contract.rsdocs/limit-orders.mddocs/contracts-security-audit.mddocs/reference/fee-discount-tiers.mdfrontend-dapp/src/services/terraclassic/terraGas.tssmartcontracts/Cargo.tomlskills/AGENTS_LIMIT_ORDER_BATCH_LADDER.mdRecommended direction
smartcontracts/contracts/grid-vault(product name V3 Grid; crate name staysgrid-vault). Optional thingrid-factoryin the same issue if instantiate-per-(beneficiary, pair) needs an index. Do not reuse AutoLP.owner. Isolation for cancel blast radius and a path to park via blacklisting that vault if ops ever need L19 on the book. Factory (or grid-factory) recordscode_id, listed pair, beneficiary; paginatedVaults { beneficiary }/AllVaultsfor the keeper.[P_low, P_high]; spread 1–2500 bps; rung count (≥ 2, ≤ pairmax_batch_rungs); equal inventory split unless a later spec says otherwise. Reject spread0or> 2500.P→ next ask atP * (1 + spread_bps/10000); that ask fill → next bid back atP, notP*(1+s)*(1-s). Prices must stay inside[P_low, P_high]and inside L20. If the flip would cross the live book, skip that rung (post-only); do not take the vault through the spread.Rebalance(permissionless):OrderStatus/ pair queries (not indexer).ClaimExpiredLimitOrdersfor vault-owned parks (L22 — do not treat parked dust as live inventory).Unknown+ not inORDERS) rung, compute the paired opposite and place using received inventory + idle balances, after reserving the optional tip.claim_tip = true→ pay 5% of unclaimed realized grid fees to caller; writeunclaimed -= paid.action=grid_rebalance, position/vault, flips, tip_paid, fees_remaining) so the indexer can ingest withoutwasm_attr_last.unclaimed_grid_feesis only realized surplus (completed buy+sell at the paired prices), never an optimistic mark. Tip cannot exceedunclaimed_grid_feesor spend escrow needed to keep in-range rungs live.GetDiscount. Do not spoof the depositor astraderon the pair. UI/APR sibling may show the user’s tier as an estimate; execution stays vault-address. Optional later: vault holds/registers CL8Y — out of band, not required to ship the vault.gas_usedfor existing ladder G1–G4/G6 and vault deposit + first grid +Rebalanceafter 1 fill + tip path. Compare toterraGas.ts. Paper-only estimate for in-swap flip (G5) — do not prototype onmain.docs/contracts-terraclassic.md+docs/contracts-security-audit.md(new G-series).make verify-issue-<iid>greps the note + runs crate tests.Acceptance criteria
grid-vault(and factory index if used) is a workspace member; instantiate binds beneficiary + factory-listed pair only.P,Rebalanceplaces an ask atP*(1+s)(human, L20). After that ask fills, the next bid isP. No multiplicative drift.Claim/Reinvestexecute.claim_tip=falsepays 0.claim_tip=truepays exactly 500 bps ofunclaimed_grid_fees(integer floor) toinfo.senderand reduces the pot. Cannot pay more than the pot; cannot unback live rungs.Rebalance. Work and CW20 sends are capped. Second call with no fills is a cheap no-op.execute_swapis unchanged (no in-swap insert). L5 still holds.reason(L22); flip must not re-place from parked dust.make verify-issue-<iid>; LocalTerra gas table attached or in docs.Test plan (all paths)
OrderStatusnotActiveRebalanceafter T4P*(1+s); bid slot empty atPRebalancerestores bid atP(no drift)claim_tip=truewith pot > 0claim_tip=truewith pot = 0claim_tip=truepot < 1 unit after floorRebalanceRebalance/ deposit place / withdraw-cancel blocked or no-op per L6; no retry burnRebalanceclaims; does not place a dust oppositeplace_limit_order_ladder/ fill testsLocalTerra
gas_usedrecorded vsterraGas.tslimit-bookRebalancegas_usedTest plan — attack, hack, and abuse vectors
Rebalance.PlaceLimitOrderBatch.hint_after_order_idcannot reorder the book (L14).Rebalancefail-closed if inventory < place-fee + escrow + reserved tip.Rebalancere-entry locked (AutoLPSKIMMINGpattern). Owner of orders is the vault, neverinfo.senderof the crank.Rebalance(tip at most 5% of realized fees), not withdraw.[P_low, P_high]; do not convert leftover to AMM LP. Idle inventory waits; withdraw returns it.min_remaining_*stays idle, not a ghost rung.Rebalancewith nothing to do is cheap. Attacker pays their own gas; cannot lock the book.Verification criteria
execute_swapdiff (there should be none).Rebalance/withdraw withgas_usedand LocalTerra tx hashes.make verify-issue-<iid>runs crate tests + doc greps (AC headings, 500 bps tip, no in-swap flip, E2 no-drift)./v3issues are not blocked on UI, but they are blocked on message shapes and events from this issue.Out of scope
grid-keeperpackage and tip→LUNC swap./v3page, APR estimates, Pool/Limits links.max_maker_fills, mixed-side batch, or taker swap envelopes.authzkeeper.marked as related to #546
mentioned in issue #618
marked as related to #618
mentioned in issue #619
marked as related to #619
mentioned in issue #546
Implementation issue for the #546 decision (option E / architecture item 5).
Siblings (do not implement here):
grid-keeper/(2× gas + tip→LUNC). Blocked on this issue’s message shapes and fee-pot query./v3product + indexer. Blocked on this issue’s deposit/withdraw/query shapes. No Claim / Reinvest / Rebalance buttons.Decision note: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/546#note_3722869567
mentioned in issue #650
marked as related to #650
mentioned in issue #717
marked as related to #717