Saturn Credit
Findings
This is the full scored assessment that the April run never produced — that pass halted at the deal-breaker gate. The three April access-control deal breakers are now resolved by a 5-day TimelockController migration (verified on-chain), and the implementations are byte-identical to April, so no code re-audit was triggered. Saturn is well-engineered — modern OpenZeppelin v5.x, comprehensive reentrancy protection, ERC4626 inflation defense, internal-accounting NAV, and three prior audits — with no Critical/High code vulnerability and no unprivileged drainable path. The score lands at the B band (68.4, a B+ at the bottom of the band) because of structural trust concentration rather than code defects: 92.5% of the $88.6M vault NAV is off-chain STRC credited by a non-timelocked single-key processor that can move the liquid balance within a ±20% tolerance, the 5-day timelock's sole proposer/canceller is still a single EOA, a slow 24h-heartbeat single oracle prices the off-chain NAV, and there is no bug bounty. The team is publicly doxxed.
- ▸ April's 3 access-control deal breakers resolved — upgrade authority now behind a 5-day TimelockController
- ▸ 92.5% of the $88.6M vault NAV is off-chain STRC credited by the processor — not provable on-chain
- ▸ Non-timelocked single-key processor can move the liquid USDat balance within a ±20% tolerance, uncapped
- ▸ The 5-day timelock's sole proposer/canceller is still a single EOA — delay without decentralization
- ▸ Slow single oracle (24h-heartbeat Chainlink STRC feed) prices 92.5% of NAV; no fallback
- ▸ No bug bounty / security contact; team is publicly doxxed (Kevin Li, Ellis Osborn, Seb)
Technical findings only — not financial advice.
Trust Surfaces
Who can move funds, and how fast| Surface | Controller | Type | Min Delay | Worst Case |
|---|---|---|---|---|
| USDat / sUSDat / Withdraw / StrcPriceOracle — Upgrade & DEFAULT_ADMIN | 0xfD5782E3BFF366601da3973aE30C583dE4F08A67 ↗ | Timelock | 120h | Upgrade any core contract or change the oracle/params — but only after a public 5-day delay (the canonical exit window) |
| TimelockController — PROPOSER & CANCELLER | 0x610182581C93687Ca03F4a8E7f124f8cEC616820 ↗ | EOA | Instant | Sole proposer and canceller: can queue a 5-day-delayed malicious upgrade, indefinitely cancel remediation, or freeze admin if the key is lost |
| sUSDat — PROCESSOR_ROLE (convertFromUsdat) | Processor key (single EOA, non-timelocked) | EOA | Instant | Move the liquid USDat balance via convertFromUsdat within a ±20% tolerance, uncapped and repeatable; off-chain STRC backing (92.5% of NAV) is not on-chain-provable |
| USDat — COMPLIANCE_ROLE | Compliance key (hot key) | EOA | Instant | Freeze accounts, arbitrary-recipient forceTransfer, manage the whitelist, and both pause and unpause USDat |
| Mint/Redeem (SwapFacility) — Proxy upgrade | 0x23CA665c8a73292Fc7AC2cC4493d2cE883BBA468 ↗ | Timelock | 72h | Mint/Redeem upgrade — 72h public delay before exposure |
| Chainlink STRC Feed — Owner | 0x21f73D42Eb58Ba49dDB685dc29D3bF5c0f0373CA ↗ | Multisig 4/9 | Instant | Replace the underlying Chainlink STRC price-feed implementation |
Deal Breaker Matrix
Access Control & Governance
| Item | Status | Evidence |
|---|---|---|
| EOA Upgrade Control | PASS | Was FAIL in April. Upgrade authority on all 4 core contracts now terminates at a 5-day TimelockController (0xfD57…8A67, minDelay 432000s). UUPS _authorizeUpgrade = onlyRole(DEFAULT_ADMIN_ROLE) = timelock; USDat ProxyAdmin owner = timelock. Old EOA fully revoked. |
| EOA Fund Control | PASS | Was FAIL in April. No single EOA can upgrade-to-drain; the upgrade path is 5-day-timelock-gated. The processor's convertFromUsdat is the intended conversion mechanism within ±20% tolerance with an off-chain backing obligation — scored as a Fund Control / economic weakness (§2.2, F-2), not an unrestricted EOA withdrawal. |
| >60% Governance Centralization | N/A | No governance token. |
| Governance Mechanism Bypass | N/A | No on-chain voting. |
| Timelock Backdoors | PASS | Was FAIL in April. Timelock executor = address(0) (open), admin = self; no fastTrack/emergencyExecute. 5d ≫ 48h mainnet minimum. Mint/Redeem under a separate 72h timelock. updateOracle now timelock-gated. |
| No Emergency Controls | PASS | Pause via COMPLIANCE_ROLE; unpause via DEFAULT_ADMIN_ROLE (timelock). |
Oracle & Price Integrity
| Item | Status | Evidence |
|---|---|---|
| Direct Pool Price Oracle | PASS | Chainlink STRC feed wrapped with staleness + bounds; not a DEX spot price. |
| Manual Price Control | PASS | No direct price setter; updateOracle / setPriceBounds are now 5-day-timelock-gated. |
Smart Contract Architecture
| Item | Status | Evidence |
|---|---|---|
| Known Compiler Bugs | PASS | Solidity 0.8.20 / 0.8.26, no applicable CVE. |
| No Reentrancy Protection | PASS | OZ ReentrancyGuard + CEI; SwapFacility transient-storage lock. |
| Unlimited Minting | PASS | USDat mints only by wrapping M 1:1 (onlySwapFacility). |
| Unsafe Delegatecall / Call | PASS | No delegatecall or unvalidated .call{} in Saturn contracts. |
| Uninitialized Implementation | PASS | All implementations call _disableInitializers(). |
| Unprotected Initializer | PASS | initializer-gated; already initialized on-chain. |
Audit & Verification
| Item | Status | Evidence |
|---|---|---|
| No Audit + High TVL | PASS | 3 audits (Three Sigma + 2× Certora); TVL ~$88.6M, audited. |
| Unverified Contracts | PASS | 10/10 in-scope contracts verified on Etherscan (100%). |
| Critical Unfixed Issues | Inconclusive | Audit PDFs not re-read this pass (per process). |
Economic & Liquidity
| Item | Status | Evidence |
|---|---|---|
| Zero Flash Loan Protection | PASS | Was Inconclusive in April. Internal-accounting NAV + async withdrawal + vesting break same-block manipulation. |
| Broken Tokenomics | PASS | Was Inconclusive in April. ~11% real yield from off-chain STRC, well below 100%; not emission-driven. |
| No Slippage Protection | PASS | depositWithMinShares, mintWithMaxAssets, requestRedeem(minUsdatReceived). |
Cross-Chain & Bridges
| Item | Status | Evidence |
|---|---|---|
| Centralized Bridge | N/A | Single chain (Ethereum only). |
| No Transfer Limits | N/A | Not a bridge. |
| No Token Verification | N/A | Not a bridge. |
Open Issues
- P1 Medium · Access Control Timeline: 1 monthTimelock's sole PROPOSER + CANCELLER is a single EOA (F-1)Impact: Delay without decentralization: key loss freezes admin (incl. unpause); key compromise can propose a malicious upgrade (5-day window) or indefinitely cancel remediationRecommendation: Move PROPOSER/CANCELLER to a ≥3/5 multisig with an independent canceller
- P1 Medium · Access Control Timeline: 1 monthNon-timelocked single-key processor can move the liquid USDat balance via convertFromUsdat (F-2)Impact: The processor can move the entire liquid USDat balance within a ±20% tolerance, uncapped and repeatable; off-chain STRC backing (92.5% of NAV) is not on-chain-provableRecommendation: Add on-chain caps/rate limits to convertFromUsdat; reduce reliance on off-chain-credited STRC backing
- P1 Medium · Economic Timeline: 1 month±20% toleranceBps compounds to a ~33% value-leak ceiling per processor conversion (F-7)Impact: toleranceBps bounds strcAmount and purchasePrice independently, compounding to roughly a 33% value-leak ceiling per conversion by the trusted processorRecommendation: Narrow toleranceBps to ≤5%
- P1 Medium · Operations Timeline: 1 monthNo public bug bounty program or security contactImpact: No incentive or channel for responsible vulnerability disclosure (counted as a red flag in the assessment)Recommendation: Establish an Immunefi program with a $100K+ max bounty and a public security contact
- P2 Medium · Economic Timeline: 3 monthsWithdrawal settlement allows a ≤20% haircut vs previewRedeem; minUsdatReceived can be 0 (F-8)Impact: A per-request minUsdatReceived of 0 still validates, allowing settlement up to 20% below previewRedeemRecommendation: Enforce a minimum minUsdatReceived; bound the settlement haircut vs previewRedeem
- P2 Low · Access Control Timeline: 3 monthsUSDat COMPLIANCE hot key holds freeze + forceTransfer + whitelist + pause and unpause (F-3)Impact: Broader than the sUSDat/Queue compliance roles: a single key can freeze, force-transfer to an arbitrary recipient, manage the whitelist, and both pause and unpauseRecommendation: Split the compliance hot key's powers; separate pause from unpause; constrain forceTransfer
- P2 Low · Smart Contract Timeline: 3 monthsUnbounded user-iterated loops (_claimAllFor over balanceOf) (F-4)Impact: Gas-griefing / self-DoS; funds remain escapable via per-NFT claimRecommendation: Bound the user-iterated loops or document per-NFT claim as the escape hatch
- P2 Low · Oracle Timeline: 3 monthsOracle has no fallback; a stale/out-of-bounds feed reverts all sUSDat accounting (F-5)Impact: A feed failure reverts deposit/preview/requestRedeem — a liveness DoS (fails closed)Recommendation: Add an oracle fallback / circuit breaker so a feed failure does not DoS vault accounting
- P2 Low · Oracle Timeline: 3 monthsgetPrice omits answeredInRound / round-completeness; loose staleness and static bounds (F-6)Impact: 36h staleness ceiling vs a 24h heartbeat, static $20–$150 bounds (~±50%), and no deviation breakerRecommendation: Add answeredInRound/round-completeness checks; tighten staleness to the 24h heartbeat; add a deviation breaker
Contract Inventory
Audit History
Protocol
Operations
All role assignments (8)
| Contract | Role | Holder | Powers |
|---|---|---|---|
| USDat | DEFAULT_ADMIN_ROLE | Timelock 0xfD57…8A67 | Grant/revoke roles, full admin — behind 5-day delay |
| USDat | COMPLIANCE_ROLE | Compliance key | Freeze, arbitrary-recipient forceTransfer, whitelist, pause + unpause |
| sUSDat | DEFAULT_ADMIN_ROLE | Timelock 0xfD57…8A67 | UUPS upgrade, vesting, fees, tolerance, params — behind 5-day delay |
| sUSDat | PROCESSOR_ROLE | Processor key (single EOA) | convertFromUsdat (±20% tolerance), distribute rewards, settle withdrawals |
| Withdraw | DEFAULT_ADMIN_ROLE | Timelock 0xfD57…8A67 | UUPS upgrade, unpause — behind 5-day delay |
| StrcPriceOracle | DEFAULT_ADMIN_ROLE | Timelock 0xfD57…8A67 | updateOracle, set staleness, set price bounds — behind 5-day delay |
| TimelockController | PROPOSER_ROLE / CANCELLER_ROLE | 0x6101…6820 (single EOA) | Queue (5-day-delayed) or cancel privileged operations |
| TimelockController | EXECUTOR_ROLE | address(0) (open) | Anyone may execute an operation after the 5-day delay |