Conversation
One ERC-20 per subnet on an EVM chain (demo: fake Base on anvil), each backed 1:1 by staked alpha in a hub escrow on Bittensor. Buys lock USDC, mint tUSD, swap through a protocol-owned pool, and stake; balances rebase as emissions accrue; sells unwind back to USDC. Ships pallet-usd-psm, the USD precompile, Gateway/Portal/share-token contracts, a Hyperlane-based local rig (8 subnets with real mainnet identities), a MetaMask demo page, and an e2e suite. See swapdemo.md to run it. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| if (kind == SenderKind.UsdPortal && amount > 0) { | ||
| // Secure the backing first: canonical USD into the PSM escrow. | ||
| // Reverts here (e.g. mint window exhausted) leave funds locked at | ||
| // origin and let the relayer retry later. | ||
| canonicalUsd.mint(psmEscrow, amount); | ||
| } | ||
|
|
||
| IUsdRails(IUSD_RAILS_ADDRESS).gatewayExecute(amount, envelope); |
There was a problem hiding this comment.
[CRITICAL] Sender kind is not bound to the envelope action
A trusted UsdPortal may supply an arbitrary envelope through RailsPortal.deposit(), including SellShares. The gateway mints amount of canonical USD and then the runtime treats that same amount as already-burned shares, without proving any caller owned or burned them. An attacker can therefore reduce global outstanding shares, unstake other holders’ backing, and direct the USD release to themselves. Authenticate the sender kind alongside the decoded action and require sell envelopes to originate from the registered token for that exact domain/netuid.
| fn on_idle(now: BlockNumberFor<T>, _remaining_weight: Weight) -> Weight { | ||
| Self::flush_outbound().saturating_add(Self::heartbeat(now)) |
There was a problem hiding this comment.
[CRITICAL] Unbounded on_idle work can halt block production
This hook discards remaining_weight. OutboundQueue is unbounded, failed dispatches are retained indefinitely, and every queued item is retried in one block; heartbeat also iterates every route. Users can continually enqueue successful buys while a failing outbound route grows the retry set, eventually forcing work beyond the block budget. Bound the queue and process only as many entries/routes as the supplied weight permits, leaving a cursor for later blocks.
🛡️ AI Review — Skeptic (security review)VERDICT: VULNERABLE MEDIUM scrutiny: 5-month-old but highly active contributor with repository write permission; disclosed Cursor co-authorship, no Gittensor association found; swap-demo -> main. All three previously reported critical vulnerabilities remain present at the current head. Findings
Other findings
Prior-comment reconciliation
ConclusionThe unchanged settlement authorization permits reserve theft, while the unbounded runtime hook and unchanged runtime version threaten chain liveness and consensus safety. 📜 Previous run (superseded)
# 🔍 AI Review — Auditor (domain review) has not yet run on this PR. |
|
🔄 AI review updated — Skeptic: VULNERABLE |
…les. The .rails registry, agent databases, and CREATE2 deployments outlive a chain restart from genesis, leaving the rig half-wired. Hyperlane deploy now checks the mailbox has code before trusting the registry, the forge deploy scripts skip creation when code already sits at the CREATE2 address, and agents.sh wipes validator/relayer state when the mailbox set changes. Salt bumped to v2 for a clean local address space. Co-authored-by: Cursor <cursoragent@cursor.com>
| canonicalUsd.mint(psmEscrow, amount); | ||
| } | ||
|
|
||
| IUsdRails(IUSD_RAILS_ADDRESS).gatewayExecute(amount, envelope); |
There was a problem hiding this comment.
[CRITICAL] Sender kind is not bound to the envelope action
kind controls only whether canonical USD is minted; the opaque envelope is then executed without verifying that UsdPortal carries a deposit action or RemoteToken carries SellShares. A trusted portal exposes arbitrary envelope construction through deposit, so a user can submit a SellShares envelope without burning shares and make the hub release escrow. Decode and validate the action against kind before calling the precompile.
| /// push the live share index to every routed spoke on a fixed block | ||
| /// cadence, so rebasing balances tick without any user action. | ||
| fn on_idle(now: BlockNumberFor<T>, _remaining_weight: Weight) -> Weight { | ||
| Self::flush_outbound().saturating_add(Self::heartbeat(now)) |
There was a problem hiding this comment.
[CRITICAL] Unbounded on_idle work can halt block production
The hook ignores remaining_weight. flush_outbound drains the entire unbounded queue, and heartbeat iterates every route while performing EVM mailbox dispatches. The returned weight is only retrospective; execution can already exceed the block budget and prevent block production. Bound both workloads, stop before exhausting remaining_weight, and persist cursors/retries for later blocks.
|
🔄 AI review updated — Skeptic: VULNERABLE |
Summary
sell()unwinds back to USDC.pallet-usd-psm(PSM assets, protocol-owned tUSD/TAO pool, escrow staking, share-index accounting, sequential envelope nonces, outbound queue + index heartbeat), the0x814USD precompile, and rails runtime API/RPC.Gateway(trusted-sender envelope door),RailsPortal(buy(), USD release, shared nonce counter), rebasing share token, andEnvelopeLib(SCALE encoding, golden-vector tested against Rust and TypeScript).just rails-up|rails-demo|rails-ping|rails-down. Creates 8 demo subnets carrying real mainnet identities (name, description, logo) read back from the chain.demo/index.html): subnet dropdown, pool-quoted prices via view precompiles, wallet-free faucet, and self-healing for MetaMask's stale-nonce-after-localnet-reset failure mode.swapdemo.mddocuments how to run it.Test plan
forge test— 18 contract tests (rebasing, minter windows, nonce guard, golden envelopes)cargo test -p pallet-usd-psm— pallet unit tests (nonce ordering, buy/sell pipelines, fallbacks, heartbeat)pnpm exec moonwall test rails— 12 e2e tests on the live rig (buy → mint, index accrual, attestation consistency, sell → USDC release, nonce sync, failure drills)Made with Cursor