Audience: protocol operators, multisig signers. Cadence: deployment (once), plus AP onboarding and settlement (recurring).
The contracts assume a disciplined multisig. Every scenario in the Threat Model that isn't mitigated on-chain is mitigated here. If this doc isn't followed, the audit findings don't matter.
| Multisig | Suggested threshold | Signers | Key storage |
|---|---|---|---|
Owner (cold) — owns ARU, rarely used |
2-of-3 | Founding team + legal counsel | Air-gapped hardware (Ledger/Trezor), recovery phrases in geographically-separated sealed envelopes |
| Manager Admin (operational) — settles epochs, pauses, approves per-request settlements | 3-of-5 (4 humans + AutoSigner) | Ops + eng + eng backup + counsel + AutoSigner service | Hardware wallets for humans; HSM-backed cloud key for AutoSigner |
| Oracle Admin — sets safety params | 2-of-3 | Same as Manager Admin OR separate if org permits | Hardware |
| Treasury — holds USDC | 2-of-3 | Finance + ops + backup | Hardware, Gnosis Safe |
| Redstone Price Updater | Single key (hot) | Automated relayer | Cloud HSM or equivalent; rotated quarterly |
Never share signers between Owner and Manager Admin multisigs. Any single compromise should not bridge both.
AutoSigner participation (recommended, see Auto-Signer for full spec):
foundry.toml pins solc = "0.8.24" (current)lib/openzeppelin-contracts submodule pinned to a specific tag, not tracking main. Verify with git submodule status; record the commit hash in the deployment log.forge test -vvvforge test --match-path 'test/invariant/*.t.sol' --invariant-runs 1024slither . --filter-paths lib/forge inspect <contract> bytecode and diff against the audited commit 1153227 (Nethermind NM-0902; see Audit History)forge snapshotscript/Deploy.s.sol reviewed by at least 2 engineers against the constructor argument plan belowThe ARUPriceOracle.initialPrice bypasses the rate-of-change guard (constructor-set). Getting it right is non-negotiable.
Process:
int256 scaled).Who can be signer #1 and #2: any two members of the Owner or Manager Admin multisig, but not the same human using two keys. Real separation.
Deploy ARU with the cold owner multisig as initialOwner. Initial minter = owner (self).
Deploy ARUMintRedeemManager with:
usdc = canonical chain USDC (e.g., 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 on Arbitrum)aru = deployed ARU addresstreasury = treasury Safe addressmanagerAdmin = operational multisigminimumMintAmount = 10,000 USDC (6-dec)minimumRedeemAmount = 10,000 ARU (18-dec)Deploy ARUPriceOracle with:
admin = oracle admin multisigpriceUpdater = Redstone relayer hot keyinitialPrice = output of the price ceremonymaxPriceChangeBps = 300 (3%)minUpdateInterval = 3,600 (1h)stalenessThreshold = 90,000 (25h)feedId = Redstone feed identifierPost-deploy verification (same tx batch or immediately after):
DEFAULT_ADMIN_ROLE is the intended multisig on Manager and OraclePRICE_UPDATER is set to relayer only (no extras)ARU uses a multi-minter design (v2). Both the v1 epoch-batched manager and the v2 per-request manager need mint/burn rights. Each addition is subject to a 48h owner timelock.
For each manager to grant minter role:
ARU.proposeAddMinter(managerAddress).cancelMinterChange(managerAddress) if anything looks wrong.ARU.executeMinterChange(managerAddress) — public by design, provides transparency after timelock elapses.ARU.isMinter(managerAddress) read.Repeat for both v1 and v2 manager addresses.
After both managers have mint rights, remove the bootstrap owner-as-minter:
ARU.proposeRemoveMinter(ownerAddress).ARU.executeMinterChange(ownerAddress).ARU.isMinter(ownerAddress) returning false.Do not skip the wait. Do not combine propose + execute. Do not skip the bootstrap-minter removal — leaving the owner as a minter preserves a centralization vector.
For each AP:
Manager.addAP(apAddress).Manager.isAP(apAddress) read.Record every AP onboarding in a standing registry doc (address, date, legal entity, deactivation date if applicable).
Per the idempotent batching in Settlement Correctness:
Before completeMints / completeRedemptions:
epochTotalMintCollateral[epoch] and epochTotalRedeemARU[epoch].MAX_BATCH_SIZE (start at 50, tune based on gas).forge script Simulate --fork-url $RPC --sender $ADMIN. Attach output to the signing request.During drain:
SettlementBatchCompleted.epochTotalMintCollateral[epoch] drain to zero.Close:
epochTotalMintCollateral[epoch] == 0, call closeEpochMints(epoch).Exception path: if an AP is missed mid-drain (e.g., hardware wallet issue), that's fine under the new design — schedule another drain batch. The AP's request is still claimable because epochMintSettled is not yet flipped.
Relayer configuration:
stalenessThreshold / 2 (12.5h under defaults), even if price unchanged.setPrice with the same value is allowed (rate-of-change = 0 bps < 300).stalenessThreshold * 0.5.| Event | Action |
|---|---|
| Signer leaves the org | Rotate that role's multisig within 48h. Cold multisig uses proposeOwnershipTransfer → 48h wait → execute. Manager/Oracle multisigs can swap signers via Safe UI. |
| Hardware wallet lost | Revoke signer, add replacement |
| Suspected compromise | Immediately pause (pauseSubscriptions + pauseRedemptions), rotate keys, investigate |
| Relayer key every 90 days | Generate new key, grant PRICE_UPDATER to new key, revoke old, update relayer config |
Compromised MANAGER_ADMIN:
pauseSubscriptions + pauseRedemptions.ARU.owner calls proposeMinterChange(newManager) — 48h window lets you deploy a fresh Manager and migrate.Compromised oracle:
PRICE_UPDATER via ADMIN_ROLE.revokeRole + grantRole.Compromised treasury Safe:
Manager.setTreasuryAddress(newSafe).safeTransferFrom.For every deployment, archive (git-committed or stored with legal):
aru-evm deployedlib/openzeppelin-contracts submodule