Security hardening: recordCompany auth, CEI, Ownable2Step+timelock, EAS membership, namehash, bounded loops - #57
Conversation
…AS membership, namehash, bounded loops Applies the AxoloDAO best-of-4 hardening patterns to StartupChain (zero code coupling; patterns only). Sepolia only. Contracts: #1 recordCompany requires Safe-owner caller (squat-revert); #2 Ownable2Step + 2-day timelock on setFeeRecipient; #3 CEI + nonReentrant in recordCompany (reverting-fee test); #4 real onlyCompanyMember (isFounder) + owner-gated schema setters; #5 correct .eth namehash in transferENS/create/revokeSubdomain; #6 MAX_FOUNDERS cap + founder->companyId index. Off-chain: #7 bind payment to a founder wallet (+ commitment hook); #8 separate treasury from the hot signer key. forge test: 61 passing. See SECURITY-HARDENING.md; item #9 (Arweave cap-table) left as a separate PR.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| const paymentStatus = await checkPaymentStatusAction({ | ||
| paymentTxHash, | ||
| minValueWei: totalWei, | ||
| allowedFrom: founderStructs.map((f) => f.wallet), | ||
| }) |
There was a problem hiding this comment.
Registration Payment Remains Reusable
This call enables founder binding but never supplies the new expectedCommitment, so the check in payment-actions.ts is skipped for the live registration path. A founder can reuse one successful treasury tx hash for another ENS registration with the same founder set, because the server verifies from, to, status, and value but never ties the tx to this specific ensName or marks it consumed.
| console.warn( | ||
| '[startupchain] STARTUPCHAIN_TREASURY_ADDRESS is not set — falling back to the hot signer address ' + | ||
| 'as treasury. Set a distinct treasury (ideally a Safe) before production (#8).' | ||
| ) | ||
| return account.address |
There was a problem hiding this comment.
When STARTUPCHAIN_TREASURY_ADDRESS is unset or invalid, this branch accepts user prepayments into the hot relayer account and only logs a warning. The payment verifier then treats that signer address as the treasury, so a misconfigured deployment keeps fee custody and gas signing on the same key.
Applies the transferable hardening patterns from the AxoloDAO best-of-4 contract review to StartupChain. The two products stay fully decoupled — no shared code, only the patterns transfer. Sepolia only, no mainnet moves.
forge test→ 61 passing (13 newStartupChain.t.sol, 2 newAttestationModule.t.sol, existing suites unchanged). TS typecheck clean.Contract fixes (each with a proving test)
recordCompanycaller auth — caller must be an owner of the target Safe (ISafe.isOwner) and the Safe must be deployed. Closes theensNameToCompanyIdsquat/front-run vector.test_recordCompany_squatByNonOwnerRevertsOwnable2Step+ timelock — 2-step ownership;setFeeRecipientbehind a 2-day propose→execute timelock. Deploy the owner as a 2/3 Safe.test_ownershipIsTwoStep,test_setFeeRecipientIsTimelockedrecordCompany— all state written before the fee.call;nonReentrant.test_recordCompany_revertingFeeRecipientRevertsWholeTxAttestationModule— realonlyCompanyMember(registryisFounder); schema setters owner-gated (were permissionless one-time).testOnlyCompanyMemberCanAttest,testSchemaSetterIsOwnerGated.ethnamehash — labels parented undernamehash("eth"), not the ENS root, intransferENS/createSubdomain/revokeSubdomain. Unblocks Create subdomains for company members #43/Manage domains and subdomains from dashboard #44/Forward and reverse name resolution for ENS domains on Startupchain UI #33.test_namehash_isEthParentedNotRootMAX_FOUNDERS = 50;founder → companyId[]index replaces the off-chain O(n) scan;isFounderbacks Revert "Feat/branding on home" #4.test_maxFoundersEnforced,test_founderReverseIndexOff-chain fixes
checkPaymentStatusActionrequires the payment to come from a founder wallet (allowedFrom) + optional per-registration commitment hook. Residual: full single-use needs a consumed-tx store or on-chainmsg.valuefee (flagged).STARTUPCHAIN_TREASURY_ADDRESSseparates the treasury from the gas-paying signer (falls back with a warning).Tradeoffs (documented, not enforced)
isFounder).Not in this PR
Item #9 — moving the cap table off contract storage to a permanent Arweave snapshot + EAS anchor — is a separate, larger PR.
See
SECURITY-HARDENING.md. Constructors changed:StartupChain(ens, resolver, feeRecipient, initialOwner),AttestationModule(eas, registry, initialOwner).Greptile Summary
This PR hardens the StartupChain registration and attestation flow. The main changes are:
recordCompany..ethnamehashing for ENS operations.Confidence Score: 4/5
The payment hardening path needs fixes before merging.
src/app/(app)/dashboard/setup/actions.ts, src/lib/blockchain/startupchain-client.ts
Security Review
The contract hardening reduces several on-chain risks, but the off-chain payment path still allows same-founder tx replay because the commitment is unused. Treasury misconfiguration can also route user prepayments to the hot signer.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Founder participant Setup as commitEnsRegistrationAction participant Payment as checkPaymentStatusAction participant RPC as Sepolia RPC participant Treasury Founder->>Treasury: Send ETH payment Founder->>Setup: Submit registration and tx hash Setup->>Payment: tx hash, min value, allowed founders Payment->>RPC: Read receipt and transaction RPC-->>Payment: status, to, from, value, input Payment-->>Setup: confirmed when status/to/value/from pass Note over Payment: No commitment is supplied, so the tx is not tied to this ENS registration Setup-->>Founder: Continue registration%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Founder participant Setup as commitEnsRegistrationAction participant Payment as checkPaymentStatusAction participant RPC as Sepolia RPC participant Treasury Founder->>Treasury: Send ETH payment Founder->>Setup: Submit registration and tx hash Setup->>Payment: tx hash, min value, allowed founders Payment->>RPC: Read receipt and transaction RPC-->>Payment: status, to, from, value, input Payment-->>Setup: confirmed when status/to/value/from pass Note over Payment: No commitment is supplied, so the tx is not tied to this ENS registration Setup-->>Founder: Continue registrationReviews (1): Last reviewed commit: "Security hardening: recordCompany auth, ..." | Re-trigger Greptile
Context used: