Community tax CW20 template: DEX-safe buy/sell/transfer tax, paid SKUs, CMM-only migrate #592
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#592
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?
Parent
Community tax / trading-tax CW20 for sovereign communities. Sibling issues: dApp create/manage and indexer catalog (link after open).
Payment (amendment)
Two on-chain invoices, both 50 UST1 (6 dp →
50000000) per unit, paid to CMM treasuryterra16j5u6…in UST1. Users may pay in any routable token via #595; the token/launcher still only accepts UST1Send.EnableFeature(except MintControl = instantiate-only)Batch (not per-field): one paid tx may update any number of already activated settings together (e.g. buy + sell + treasury + exempt list in one
Send). Invoice is always 50 UST1 for that tx, not50 × N keys.Already activated only: the batch may include:
buy_bps,sell_bps, singletreasurytransfer_bps(TransferTax), sinks/ratios (SplitRouter), exemptions (ExemptionDirectory),UpdateTaxBpsbeyond instantiate-fixed if VariableRates is on, AutoLP pair/threshold/LP recipient (AutoV2Lp), launch guards,UpdateMinter/RevokeMint(MintControl)Including a field for a SKU that is not unlocked → fail the whole batch (no partial apply, fee not kept). Do not combine
EnableFeature(SKU unlock) into a settings batch; that is a separate 50 UST1 invoice. Unlock first, then change in a later batch (or two msgs / two invoices in one Cosmos tx if the dApp sequences unlock then batch — still two 50 UST1 charges).Not a settings invoice: initial instantiate params;
EnableFeatureitself; permissionlessSkimToLp;Mint(issuing supply);RegisterListedPairif permissionless + factory-verified.No-op (every submitted value identical to on-chain) must fail closed and not take UST1. Invalid update reverts the fee. No refunds on a successful batch.
Free create profile remains 0 UST1 SKU cost (gas only) until a SKU is bought or a settings batch is submitted.
Problem statement
Communities on Terra Classic cannot launch a first-party buy / sell / transfer tax token that is safe to list on this DEX. Today the only listed CW20 templates are 1:1 mintable/standard (e.g. columbus-5 10184, 6036). Fee-on-transfer (FoT) templates are explicitly forbidden from the factory whitelist because the pair credits declared CW20 amounts, not balance deltas (invariants H-01, P2). There is no launcher, no paid-feature SKU, no manager-vs-upgrade split, and no Auto V2 LP helper.
This issue is the on-chain design and implementation: token template, optional sister contracts (tax splitter / AutoLP / hook), paid feature SKUs (50 UST1 each), CMM-only wasm upgrade, and listing compatibility without upgrading pair/router swap math.
Current codebase
DEX will not (and must not) grow FoT math
Transfer/Sendamounts, notbalanceOfdeltas. Inbound FoT (recipient credit < declared amount on transfers to the pair) desyncsRESERVES/PENDING_ESCROW(P2). Documented byadversarial_token::fee_on_transfer_creates_reserve_imbalanceand Layer Blayer_b_b1_fot_desyncs_reserves.docs/runbooks/cw20-whitelist-policy.md— never whitelist templates whose recipient credit can differ from the declared amount. Playbookskills/AGENTS_HOOK_CW20_OPS.mdH-01: do not add pair balance-delta / FoT swap math.IsCodeIdWhitelistedre-check, #582 / #584). Listing-time FoT remains an ops/harness gate (#589).What exists today (reuse, do not fork the pair)
cw20-mintable(git dep)whitelisted_code_ids/AddWhitelistedCodeIdSetPairHooksAfterSwaphookscl8y-dex-tax-hook/burn-hook%of ask to recipient / burntax_token; no sell-as-offer, no ratios, no AutoLPdex-common::hook_settlementGetConfig, splits askGetConfigor stay off the AfterSwap path#589harness +cw20_mutants.rs/mint+CreatePair/createUpgrade / admin split today
terra1zlmv2xydxcusurtr6rl78wsvytdc6mfex6hep7(docs/reference/governance-multisig.md).terra1xsecn4snv94ezcez0z3vq8an9j4h4kxxcydp8l(deployments/mainnet-ust1-wrap/REGISTRY.md).terra16j5u6ey7a84g40sr3gd94nzg5w5fm45046k9s2347qhfpwm5fr6sem3lr2— not a wasm admin.Anyone can
instantiatea stored code_id with an arbitrary--admin. F6 freezes listed pairs if the instance migrates off the pinned template; it does not stop an unlisted rug.Why needed
Sovereign communities want their own token with a trusted leader (manager) who can tune treasury routing, exemptions, and (optionally) mint — without the DEX becoming a general FoT AMM and without that leader being able to
MsgMigrateContractto a hidden-mint / honeypot wasm.This is a product (PinkSale / Pancake “liquidity generator” style) adapted to this DEX’s accounting: taxes that would desync reserves are implemented so every transfer that credits the pair, router, or limit escrow remains 1:1.
Research: features successful tax tokens actually used
Use this as a catalog to steal from or reject, not as a mandate to clone SafeMoon.
provide_liquidityTransfer/AfterSwapExtra ideas worth considering (not all v1)
RegisterListedPair { pair }that queries factorypairand only then treats that address as buy/sell.AfterSwaphook (economically a sell tax in UST1/cLUNC) — only if hook wiring is solved without pair math changes.Transfer.Constraints / guardrails
Transfer/Send/TransferFrom/SendFromto pair, router, limit-related escrow, AutoLP, tax splitter, and this token itself must credit the recipient exactlyamount. Layer A/B andfee_on_transfer_creates_reserve_imbalanceanalogue must stay green for those paths.fromis a registered listed pair, debitamountfrom pair, creditamount - buy_taxto the trader, creditbuy_taxto sinks. Pair reserves still decrement byamount→ P2 holds.amount + sell_tax(orceilequivalent), pair is credited exactlyamount. dApp must size max-sell. Fails closed if balance insufficient.amount * (1 - sell_bps)while pair accountsamount.--admin/MsgMigrateContract/UpdateAdmin= CMM governance onlyterra1xsecn4snv94ezcez0z3vq8an9j4h4kxxcydp8l(not DEXterra1zlmv2…, not CMM treasuryterra16j5u6…, not the token manager). Hardcode expected admin in launcher docs; dApp/indexer refuse to promote instances whose LCDContractInfo.admin≠ CMM.--admin. Mitigate: launcher path + catalog filter (admin == CMMandcreated_by_launcher). F6 still freezes listed pairs on migrate-off-template.Pause,Blacklist, orSetTax(100%)unbounded. Instantiate max_buy_bps / max_sell_bps / max_transfer_bps (recommend combined cap ≤ 2500 bps unless product sets otherwise). Raising above current bps may require the variable-rates SKU; lowering to 0 is always allowed if that SKU is on, or only at instantiate if not.SkimToLp. AutoLP contract is protocol-exempt.#589REPORT GO (Layer A 1:1 on protocol paths + Layer B P2 green). Update whitelist runbook with a narrow exception: this named template, not “tax tokens in general”.Sendcosts 50 UST1 flat and may mutate all already-activated settings in that batch (see Payment amendment). Implement as UST1Send+ hook (or launcherUpdateTokenSettings) so fee and mutations are atomic. Reject any key whose SKU is not unlocked. Manager-only; unpaid / underpaid / no-op → no state change. Do not charge this fee onMintor permissionlessSkimToLp.Transfer(pagination for queries;Maplookups in execute are O(1) per address).Sister-hook options (do not pick silently)
SetPairHooksSetPairHooksfor token manager + canonical hook code_idRelevant files
smartcontracts/contracts/pair/src/contract.rs,asset_code_id_guard.rs,lib.rs(P2 / F6 comments)smartcontracts/packages/dex-common/src/factory.rs,smartcontracts/contracts/factory/src/contract.rssmartcontracts/packages/dex-common/src/hook_settlement.rs,smartcontracts/contracts/hooks/tax-hook/,hooks/README.mdsmartcontracts/tests/src/adversarial_token.rs,cw20_mutants.rs,cw20_codeid_harness.rs,cw20-codeid-audits/docs/runbooks/cw20-whitelist-policy.md,docs/contracts-security-audit.md(H-01, P2, F6, I-02),skills/AGENTS_HOOK_CW20_OPS.md,skills/AGENTS_CW20_CODE_ID_PIN.md,skills/AGENTS_CW20_CODE_ID_AUDIT.mddocs/reference/governance-multisig.md,deployments/mainnet-ust1-wrap/REGISTRY.mdsmartcontracts/contracts/community-tax-token/(name TBD), optionalcommunity-tax-splitter//community-tax-autolp/, optionalcommunity-token-launcher/; workspacesmartcontracts/Cargo.tomlRecommended direction
1.
community-tax-tokenCW20 (new crate)Honest CW20-base surface (
transfer,send,increase_allowance, queries) plus:manager(execute config); no in-contract migrate. Wasm admin = CMM at instantiate (launcher sets it).name,symbol,decimals(cap 18, factory bootstrap),initial_supplyminted togenesis_recipient, fixedbuy_bps/sell_bpsto onetreasuryaddr, protocol exemption set, manager = chosen leader.RegisterListedPairvalidating factory), DEX router, this contract, splitter/AutoLP if instantiated. Manager cannot remove protocol entries.from ∈ pairsandtonot exempt → outbound split (guardrail 3).to ∈ pairs(guardrail 4).GetConfig,GetFeatures,GetExemptions,IsProtocolExempt,Minter,TaxPreview { from, to, amount }(dApp max-spend).2. Launcher (recommended over raw instantiate)
community-token-launcher(or factory-adjacent crate):token_code_id, optional splitter/AutoLP code ids,ust1addr,cmm_treasury,cmm_governance(wasm admin to stamp).CreateToken { …, features: Vec<Sku> }withfunds/ UST1Send=50 * len(paid_skus)UST1.--adminCMM (via submessage instantiate-with-admin if available; otherwise CosmWasm instantiate admin = launcher thenupdate-adminto CMM in the same tx — must not leave launcher as admin).community_token,sku,manager,code_idfor the indexer.EnableFeature { token, sku }later (except mint SKU).3. Paid SKU map (50 UST1 each; group methods)
Mint,UpdateMinter, one-wayRevokeMint. Instantiate-only purchase. Optionalcap.transfer_bpsvs buy/sell.SetPrimaryPair; permissionlessSkimToLp; LP to burn, manager, or CMM — config at enable.AddExempt/RemoveExempt(cannot remove protocol).UpdateTaxBpswithin instantiate max; 0 always allowed. Optional timelock on increases.max_wallet,cooldown_blocks,trading_enabled(default on unless set). No seller blacklist.Free remains usable with zero UST1 besides gas at create: fixed buy/sell to one wallet + initial supply. One later save of those free-profile settings costs 50 UST1 for the batch (even if several fields change). Unlocking VariableRates/ExemptionDirectory/etc. is a separate 50 UST1 SKU; after that SKU is on, those fields may be included in a settings batch (still 50 UST1 for the whole batch, not another per-field charge).
4. AutoV2LP sister
Not pair AfterSwap. Receives tax 1:1 (exempt). When
token_balance ≥ threshold,SkimToLp: swap half via router or pair (new tx / later tx),provide_liquidity, mint LP to configured sink. Tests: reentrancy during taxedSendmust not call pair; skim after swap succeeds.5. Listing
After harness GO: governance
AddWhitelistedCodeId. Runbook exception paragraph. Do not whitelist 8654 or mutants.#589catalogue rows for the new id.Acceptance criteria
Unauthorized.ContractInfo.adminon launcher-created tokens = CMM governance; manager cannot migrate orupdate-admin.TaxPreview.RevokeMintis irreversible.Transfer/Send/AfterSwap.#589REPORT template filled; factory whitelist is a separate ops step gated on GO.make verify-issue-NNN.Test plan (all paths)
Unit / multi-test (
smartcontracts):amount; user + treasury + sinks =amount.amountexactly; seller debit =amount + tax.RegisterListedPair: factory-listed pair OK; random addr rejected; spoof pair rejected.transfer_bpswhile TransferTax SKU is off → revert entire batch.Mintsucceeds without settings invoice (SKU only);RevokeMint/UpdateMinterrequire an activated MintControl SKU and a 50 UST1 settings batch.SkimToLpremains permissionless and free.SkimToLpadds LP; duringSendno nested swap.SendwithSwapmsg, not all pair sends). Must specify: prefer swap-direction detection viaSendmsg / skip tax iftois LP provider path. If detection is fragile, exempt all pair↔user transfers except those originating from swap receive — document chosen rule and test provide/withdraw/limit untaxed.Harness: new
cw20-codeid-audits/codeids/<id>/after store;LAYER_B_LT=1 make verify-issue-589.LocalTerra: launcher create, CreatePair, swap buy/sell, extra-debit max button, AutoLP skim.
Attack / abuse / hack test plan
to == pair— must be rejected by harness (P2 red). Our template must not match.MsgMigrateContractrejected (not wasm admin); CMM migrate to same family wasm only in ops (F6 refresh). Test manager cannotUpdateAdmin.GetLauncherOriginso dApp warns “unverified admin”.RegisterListedPair: cannot register a non-factory contract to trigger fake “buy” taxes on random sends.Sendto contract that calls backTransfer/SkimToLp/ pairswapin the same tx — fail or no double-tax / no reserve desync.amount; rounding cannot create extra tokens.UpdateTaxBpsvia plain execute without UST1Send→Unauthorized/FeeRequired; cannot pay in the tax token unless routed to UST1 via #595 (launcher/token invoice token is UST1 only).trading_enabled=false, both buy and sell blocked, or document; do not ship buy-only-on.Verification criteria
cd smartcontracts && cargo test -p <token-crate> -p <launcher-crate>and DEX suiteadversarial+cw20_codeid_harnessstill pass for mintable and fail known-bad FoT.make test-contracts; newmake verify-issue-NNN.Labels / owner / priority
contractsarchitectureenhancement· Owner: contracts · Priority: P2mentioned in issue #593
marked as related to #593
mentioned in issue #594
marked as related to #594
Siblings (product surface):
mentioned in issue #595
marked as related to #595
changed the description
Payment UX is not UST1-only. On-chain invoice remains 50 UST1/SKU; users pay in any routable token via https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/595 (launcher still only accepts UST1).
changed the description
Manager settings fee: after instantiate, changing any setting costs 50 UST1 per setting that actually changes (atomic UST1 invoice + mutation). Same 50 UST1 unit as SKU unlocks; pay in any token via #595. Instantiate defaults stay free;
Mintand permissionlessSkimToLpare not settings invoices. No-op updates must not take the fee.changed the description
Settings fee correction: 50 UST1 covers one batch of changes to already activated settings (not 50 per field). Unlocked SKUs only; free-profile buy/sell/treasury count as activated at instantiate. EnableFeature stays a separate SKU invoice.
mentioned in issue #597
marked as related to #597
Follow-up (book liquidity, not AutoLP): team maker+taker limit MM with Tier 0 fee waiver + monthly subscription — design/investigation: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/597
AutoV2Lp stays the on-chain pool skim SKU. #597 is off-chain book MM billed via #595.
mentioned in merge request !1117
mentioned in commit
093b78e43bmentioned in merge request !1119
mentioned in commit
ffe04d143ementioned in issue #601
marked as related to #601
!1119 merge review (no automerge, did not wait for CI)
Merged !1119 onto
main. Sourceissue-592-community-tax-cw20was already based on currentmain(4f245fc8);git merge-treereported no conflicts. Pair/router/factory swap-math files have zero diff vsmain(only new crates + docs).Acceptance vs this issue
make verify-issue-592on the MR SHA093b78e4— 27 crate tests × 2 + docs/REPORT NO-GO / whitelist exception (PASS).settings_*,batch_unactivated_sku_reverts,excess_invoice_rejected,non_manager_invoice_unauthorized).TaxPreviewmatch.RevokeMint; AutoLP never called from token Transfer/Send.Transfer(withdraw / limit refund) takes buy tax. Issue recommended exempting withdraw/LP; MR chose same CosmWasm primitive — not a merge blocker, but it is a product residual.make test-contractsfull workspace — not run this pass (MR test plan also left it open).adversarial+cw20_codeid_harness— not re-run this pass; pair wasm unchanged so FoT adversarial still lives onmain.CreatePair→ buy/sell — not run (out of !1119).CI
Pipeline 2782630926 jobs
test-contracts,cargo-audit-smartcontracts,gitleaksfailed withci_quota_exceeded, not a red suite. Project does not require a green pipeline to merge.Post-merge tracking
Store +
#589REPORT GO + factory whitelist + LocalTerra smoke + leftover DEX integration: new issue #601. Siblings #593 / #594 / #597 already exist.mentioned in issue #602
mentioned in issue #603
marked as related to #603
mentioned in issue #604
marked as related to #604
mentioned in issue #605
marked as related to #605
mentioned in issue #606
marked as related to #606
mentioned in issue #607
marked as related to #607
mentioned in issue #608
marked as related to #608
mentioned in issue #609
marked as related to #609
marked as related to #610
mentioned in issue #610
mentioned in issue #620
marked as related to #620
mentioned in issue #621
marked as related to #621
mentioned in issue #622
marked as related to #622
marked as related to #623
mentioned in issue #624
mentioned in issue #626
marked as related to #626
mentioned in issue #633
mentioned in issue #634
mentioned in issue #670