Audit readiness for the Seismic shielded SRC-20 branch#118
Merged
Conversation
…rPublicKey A zero private key bypassed the one-shot guard (which compares the stored key to zero), emitting ContractKeySet while leaving the contract key-less and a second call able to succeed. Reject it with ZeroPrivateKey, and reject any 33-byte public key whose first byte is not the compressed-point prefix 0x02/0x03 with InvalidPublicKeyPrefix in both entry points.
Compliance operators previously had no sanctioned way to learn how much to seize: forceTransfer takes an explicit amount but neither manager role was in the balanceOf gate. Allow FREEZE_MANAGER_ROLE holders to read any balance, and FORCED_TRANSFER_MANAGER_ROLE holders on the ForcedTransfer subclass via a balanceOf override.
…path The shielded approve stored the allowance in shielded space but emitted the standard Approval with the exact amount, making every allowance public. Mirror the Transfer treatment: refactor the encrypted-emit crypto core into a shared _encryptAmount helper, emit an encrypted-bytes Approval(address,address,bytes) overload to the spender's registered key on the suint256 approve path (empty-ciphertext fallback if unregistered), and keep the plaintext Approval on the native infra path where the amount is already public in plain calldata. The nonce counter is shared across both event kinds.
… fallback Before setContractKey, shielded transfers to unregistered recipients succeeded (empty-ciphertext fallback) while transfers to registered ones reverted -- inconsistent partial availability that leaked who is registered via reverts. Check the contract key first so ALL user-path shielded transfers/approves revert ContractKeyNotSet uniformly pre-key; wrap/unwrap/mint/burn/native-infra paths are unaffected (plaintext emits, no key needed). The key is deliberately not folded into initialize(): initializer calldata travels in plaintext inside the proxy deployment, which would leak the private key.
Add NOTE comments at the three ssolc 10311 sites (allowance check, sender-balance check, _revertIfInsufficientBalance) documenting the accepted 1-bit revert-vs-success leak inherent to ERC20 insufficient-balance/allowance semantics, and the missing unchecked justification in yield().
Comment-only sweep: one-line @dev + full @param lists on internal functions (restores _update's params), terse interface error/event docs, bespoke section dividers folded into the standard sections. Verified zero behavioral change (diff is comments-only; 151/151 yieldToOne unit tests unchanged).
Precompile failure paths (0x65/0x68/0x66), ciphertext fidelity and expectCall input pinning, nonce monotonicity across Transfer/Approval emits, zero-amount both branches, self-transfer and zero-recipient, native-path freeze matrix, de-listing re-blocks, residual-allowance pin, shielded transferFrom fuzz, unwrap insufficient-balance payload.
forceTransfer to a registered recipient stays plaintext-only with the encrypted-event nonce untouched; shielded-overload smoke tests; balanceOf gate for both compliance roles; seizure flow via production-visible interfaces; forceTransfer works while paused.
Gated balanceOf asserts moved to a harness getter, user transfers to the suint256 overloads, pause coverage via the shielded path, inherited gate/allowlist/native-revert tests, and a wrap dual-emit regression. 59/59 green under the seismic profile (suite was previously excluded with zero runnable tests).
Randomized op sequences over wrap/unwrap/transfers/claims/forceTransfer/ freeze/pause asserting sum-of-balances == totalSupply, M-backing solvency, and encrypted-event nonce monotonicity after every step.
suint256 overloads for user-path calls, gated balanceOf reads via the harness, contract-key install in setUp for the pre-key revert semantics, and infra-allowlisting of the swap adapter where it spends natively. Compile-verified; the suite still requires a Seismic-aware RPC to run.
…migration profile ?= seismic with a repo-local toolchain PATH prepend and fail-loud install guard; shared FORGE_BIN selector for build/sizes/coverage/ gas-report; integration fails loudly without a Seismic devnet RPC; slither replaced with a documented limitation; dead non-Seismic deploy/ upgrade/propose targets removed; force=true in the seismic profile kills the OZ upgrades-core partial-build-info flake; JMIExtension.t.sol re-admitted to the seismic test set; pre-commit fails fast with an install hint; package.json scripts pruned to match.
New test-seismic.yml installs the pinned toolchain and runs the seismic profile suite plus a sizes check; the three stock-forge workflows are disabled on this branch (they cannot parse suint256 and were permanently red).
set-contract-key.sh wraps scast send --seismic (TxSeismic 0x4A) so the contract encryption key never appears in plaintext calldata — a Foundry script cannot do a shielded broadcast and would leak it permanently. ConfigureSeismicExtension.s.sol approves the extension on SwapFacility and allowlists the infra addresses. .env.example gains the Seismic block.
deployments/5124.json with the live SwapFacility and USDS extension addresses, plus the recovered broadcast record so the re-verify target and ScriptBase address resolution work from a fresh clone.
The lock still pinned lib/common at v1.5.0 while the submodule points at a1fbf37 (feat/erc20-virtual, v1.5.1 + the 12-line virtual-marker delta); a forge-driven dependency materialization could have silently rolled it back.
README rewritten for this standalone branch (shielded MYieldToOne description, Seismic build instructions, 5124 deployment table); AUDIT-SCOPE.md with the in-scope list, system context, trust-model table, ERC-20 deviations, and accepted risks; TOOLCHAIN.md pins the Seismic toolchain the deployed bytecode was built with; RUNBOOK.md orders deploy -> verify -> configure -> set-contract-key; audits/README maps prior reports to their actual coverage; the Seismic design docs are un-ignored and tracked.
…nsion It is not yet deployed on Seismic testnet; the allowlist starts with Portal only and LOP is added via the same target once live.
SwapFacility.setAdminApprovedExtension(USDS, true) and setAllowlisted([Portal], true) executed on Seismic testnet; verified on-chain (isApprovedExtension and isAllowlisted both true).
…tnet checker sforge's EVM implements the real Seismic precompiles, so the 11-test suite pins the 0x65/0x66/0x67/0x68 vectors and proves real-key encrypt/decrypt round-trips with no mocks. run-sanvil-e2e.sh drives the full stack over RPC: setContractKey as TxSeismic 0x4A (private key absent from on-chain calldata), four-way signed-read gating, and off-chain decryption of a captured Transfer(bytes) event. check-live-testnet.sh is a read-only chain-5124 checklist. New make targets integration-seismic / e2e-sanvil / check-live-seismic-testnet; CI runs the in-process suite.
Pure-stdlib secp256k1 ECDH + double HKDF-SHA256 (libsecp256k1 shared secret with info 'aes-gcm key', then 'seismic_hkdf_105') + keccak-derived nonce + AES-256-GCM, matching the pinned precompile semantics; fast-paths through the cryptography package when available. --self-test asserts the cross-validated vectors.
Generated with the pinned toolchain: yieldToOne contracts at 100% line/ branch/function unit coverage (src total 94.13% lines); EIP-170 margins (JMIExtension tightest at 1,040 B); shielded-path gas medians.
Portal provenance + remaining suite-deployment addresses footnoted in AUDIT-SCOPE.md; RUNBOOK/AUDIT-READINESS reflect the executed configure step (extension approved, Portal allowlisted) with set-contract-key as the one remaining on-chain action.
The audit-readiness plan, scope, runbook, toolchain pins, and the docs/audit/ design notes were internal/generated material that should not be git-tracked. Remove them from tracking (kept locally) and ignore all of docs/. Fold the auditor-facing essentials those files were referenced for into the tracked tree so a fresh clone has no dead links: - README: add the pinned-toolchain table and an "Audit scope" section (in-scope files + ERC-20 deviations) - repoint audits/README, reports/README, the slither Makefile target, ConfigureSeismicExtension, and the seismic integration breadcrumbs to in-repo content / the real make targets
…tegration Unit (test/unit/projects/yieldToOne): +15 tests — encrypted-event fuzz (exactly-one-nonce-per-emit, ciphertext == precompile output, bytes-overload topic0 distinct from the uint256 overload), zero-amount shielded transferFrom to a registered recipient, allowance-boundary fuzz, and type(uint256).max boundaries; strengthened the simulation invariants (infra + unregistered holders, per-holder no-underflow, live balanceOf-gate check at each checkpoint). Integration: new test/integration/seismic/MExtensionSystemSeismic.t.sol — a non-forking, in-process system suite on the seismic EVM (real 0x65/0x66/0x68 precompiles, no mocks) covering wrap, cross-extension swap via SwapFacility, shielded transfer/transferFrom with real ciphertexts, yield accrue/claim, freeze + forceTransfer, and permissioned-extension gating. `make integration-seismic` now runs 22 tests (was 11). The mainnet-fork integration suites stay unrunnable under mercury (no usable eth_getFlaggedStorageAt fork source), so this restores live multi-contract SRC-20 coverage without forking.
…s find it sfoundryup installs `ssolc` to /usr/local/bin (install_ssolc ignores FOUNDRY_BIN_DIR), which is outside the cached .seismic-toolchain dir. The first (cache-miss) run compiles fine because /usr/local/bin is on PATH, but the cache only stores .seismic-toolchain — so every cache-hit run starts on a fresh runner with no ssolc and fails at compile time with "`ssolc` not found in PATH" (the Makefile only prepends .seismic-toolchain/bin to PATH). Fix: in the install step, export FOUNDRY_BIN_DIR and copy the installed ssolc into .seismic-toolchain/bin so it's cached and restored alongside sforge. Roll the cache key via TOOLCHAIN_CACHE_VERSION to evict the existing ssolc-less caches, and assert ssolc presence up front so a regression fails fast instead of deep in compilation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Brings the Seismic branch to an auditable state: freezes the contract diff with the pre-audit fixes, makes the test suite genuinely runnable and comprehensive, migrates the whole build/CI to the
sforge/ssolctoolchain, and assembles the deploy tooling + auditor-facing docs.Contract fixes & decisions (freeze the audit diff)
setContractKey/registerPublicKey(zero-key revert, 33-byte compressed-secp256k1 prefix check).ContractKeyNotSetchecked before the unregistered-recipient empty-ciphertext fallback.Approval(…,bytes)overload on the shielded approve path (native infra approve stays plaintext).balanceOfread-gate extended to compliance roles (FREEZE_MANAGER_ROLE, andFORCED_TRANSFER_MANAGER_ROLEon the forced-transfer subclass).// NOTE:annotations on the three accepted ssolc-10311 shielded-branching side channels.Tests
MYieldToOne/MYieldToOneForcedTransfer(encrypted-event routing, precompile-failure paths, gating matrix, dual-emit regressions, zero-amount branches).0x65/0x66/0x67/0x68precompiles, no mocks), a sanvil E2E (TxSeismic0x4Akey install, signed-read gating, on-chain → off-chain decryption), and a read-only live-testnet checker.Tooling / CI
[profile.seismic](mercury,optimizer_runs = 800,force = true) is the default;make build/tests/coverage/gas-reportrun undersforge.make integrationandmake slitherfail loudly with accurate explanations instead of passing vacuously.Scripts / deploy
set-contract-key.sh(usesscast send --seismic— never a Foundry script, which would leak the private key in plaintext calldata),ConfigureSeismicExtension.s.sol, off-chain decryptor,verify-seismic.py, and the 5124 deployment record.Docs
Test status
vm.skipanywhere.test/integration/*.t.sol) compile and are shielded-aware but are excluded from the default run: shielded reads needeth_getFlaggedStorageAt, which fork RPCs don't serve. They require a Seismic devnet (make integrationgates onSEISMIC_DEVNET_RPC_URLand fails loudly). Standing up a devnet RPC for CI is a tracked follow-up.Build & test
Known follow-ups (not blocking this PR)
SEISMIC_DEVNET_RPC_URLso the fork-integration tier executes in CI.set-contract-keyon the live 5124 proxy, the freeze tag, and mainnet role custody.