Repository navigation
fix(cards): harden spend refunds, token and destination checks - #63
Conversation
👁️ Cyclops Security Review✅ Reviewed 250b1dd (latest) · Coverage: partial ·
|
There was a problem hiding this comment.
I reviewed the latest push and didn't find new bugs. Commit 250b1dd fixes both earlier virtual-destination comments: the "TS" handler now authorizes only the literal destination, which again matches BridgeProvider.isAuthorizedDestination and preflight. The PR description's "Virtual destinations" bullet still describes the removed master-based rule, so it needs updating. This changes settlement and refund logic in both spenders and moves their deterministic addresses, so a human look would still be worthwhile.
What was reviewed:
- The measured-share refund in
CardSpender._redeemPublicandSpenderTempoSpend. Over-burning reverts against the starting balance. A vault that over-refunds makesrequired - unusedunderflow, so the spend reverts. - The new
_refundexact-credit check, theisTIP20gate in both_checkpaths, and the zero-vault rejection in "UV". - The docs on literal-only destinations and the threat-model risks, checked against the code.
Extended reasoning...
The change touches token-settlement code in cards/src/core/CardSpender.sol and cards/src/providers/bridge/SpenderTempoSpend.sol, plus tests and docs. It changes how EarnShare refunds are calculated and checked, which token types are accepted, and vault allowlisting. That is payment- and authorization-sensitive surface. The new commit reverts the master-based destination authorization flagged earlier, and no new issue was found. It is still left for a human because it is security-sensitive settlement logic whose deterministic deployment addresses change.
| // Only TIP-20s settle: no transfer callbacks, fees or rebases to upset the exact-credit checks. A vault's | ||
| // asset must equal `s.token`, so this covers redemptions too. | ||
| if (!StdPrecompiles.TIP20_FACTORY.isTIP20(s.token)) revert UnsupportedToken(s.token); |
There was a problem hiding this comment.
Informational: CardSpender's TIP-20 settlement gate has no test that fails when removed
The change adds an UnsupportedToken check to CardSpender._check, but no test asserts it: every core and Bridge program test that uses a non-TIP-20 token mocks the factory's isTIP20 to true. With the check deleted, the full cards suite still reports 675 passed, 0 failed, 50 skipped. First reported at 8270b86; recheck at this head: still present.
Valid? React with 👍 / 👎.
Hardens card spend settlement in
CardSpenderand Bridge's "TS" handler (SpenderTempoSpend). Existing programs and handlers are unchanged; this applies to new deployments.CardSpenderand the "TS" handler refuse a settlement token that is not a TIP-20 (UnsupportedToken), so no transfer callback, fee or rebase can upset the exact-credit checks. A vault's asset must equal the token, so this covers redemptions too.CardSpenderdirect public spends and in the "TS" handler. A vault that under-reports can no longer make either refund shares it held before.RefundNotCredited), asCardSpenderalready does, so a receive policy cannot redirect a direct caller's refund to a guard.InvalidVault).CardSpend.fromVault(only public spends mix stablecoin and Earn), states that a Zone key'sapproveandrequestWithdrawalgo in one transaction for the exact amount, and lists the known ways an approved private spend can fail to settle under the threat model's accepted risks.Changing
SpenderTempoSpendandCardSpendermoves their deterministic addresses (docs/cards/bridge/deploy.md).Tests
New: direct clone refunds blocked by a receive policy (default and payer authority) revert atomically and succeed without one; refunds measured against a vault that under-reports, for the program and the clone; non-TIP-20 tokens refused; a virtual address of an allowed master refused; "UV" with the zero address. Mock tokens in existing hostile-token tests now stand in for TIP-20s so they still exercise the contracts' own checks.
forge fmt --check src test script,forge lint --deny warnings ...,forge build --sizes srcFOUNDRY_PROFILE=ci forge test: 674 passed, 0 failed, 50 skippedscript/rain/build-fixture.sh --checkpnpm --filter @tempoxyz/contracts test --run docs,vp fmt --check 'docs/**',git diff --check