Unauthorized execution is the most direct threat. I blocked it by making the treasury accept calls only from the timelock, which only executes after confirming the proposal is queued, the ETA has passed, and the grace period hasn't expired. There is no alternative entry point.
Signature replay was the second thing I locked down. I used EIP-712 with a DOMAIN_SEPARATOR encoding chainId, verifyingContract, and a protocol salt. Each proposal carries a nonce in the council contract — once approved, the nonce is consumed and those signatures are permanently dead.
Signature malleability is subtle but real. ecrecover can return a valid signer for two different s values. I enforce low-s canonical form and require v to be 27 or 28. Anything outside those constraints is rejected before recovery even runs.
Reentrancy is covered by a nonReentrant guard on the treasury and checks-effects-interactions ordering throughout. The proposal book marks a proposal EXECUTED in storage before any external call fires — a malicious target calling back finds the state already finalized.
Double claims in the merkle distributor are blocked by a per-epoch bitmap. When eric, joseph, emmanuel, or edna claims a reward for a given index, that bit flips. Any repeat attempt with the same epoch and index reverts immediately. Flash-loan governance manipulation doesn't apply structurally. ARES uses an allowlisted council, not token-weighted votes. There are no tokens to borrow that grant approval power, so the vector simply doesn't exist in this model.
Large treasury drains are bounded by the guard rail's per-asset daily spend limits. Even a fully approved proposal can't extract more than the configured cap in a 24-hour window.
Proposal griefing is made expensive by the proposer bond. Spam proposals get slashed on guardian cancellation. Proposals that expire unused also forfeit the bond.
Merkle root manipulation is prevented by restricting setEpochRoot to the timelock. Changing distribution data requires council approval and a full timelock delay.
Every path to treasury funds follows the same chain: council approval → timelock queue → ETA wait → guard rail check → treasury execute. I didn't build any shortcut that skips a step. For signatures, domain separation plus per-proposal nonces plus low-s enforcement means a captured signature is useful exactly once on one chain. For economic attacks, the bond and daily caps mean both submitting and executing malicious proposals carry real cost.
Council key compromise is the biggest gap. If threshold keys are stolen, an attacker can approve anything the guard rail allows. The mitigation is operational — hardware wallets, distributed signers — not something enforceable on-chain.
Guardian centralization is a deliberate trade-off. A compromised guardian can censor proposals indefinitely. I accepted this because no cancellation mechanism on a $500M treasury felt worse.
Guard rail misconfiguration can't be fully prevented on-chain. A badly configured allowlist degrades protection regardless of everything else working correctly.
Timestamp dependence on daily limits is low risk on mainnet but worth noting — significant drift could allow two spending windows inside less than 24 real hours.