Multisig and timelock
Some properties cannot be enforced in a contract. These are operational requirements, and they are stated here because the alternative is pretending the code covers them.
Requirement 1 — admin roles on a multisig
DEFAULT_ADMIN_ROLE on all four AccessControl contracts, and owner on the registry, must be held by a multisig behind a timelock. Never a hot deployer wallet.
What a compromised admin could otherwise do:
- Pause staking indefinitely.
- Widen
maxPriceImpactBpsto 10,000, removing the buyback price bound. - Grant
KEEPER_ROLEto itself and drain the buyback budget through bad executions. - Grant
GOVERNOR_ROLEon the reserve to itself and, after the timelock, withdraw.
What it still could not do: change the split, take staked principal, change the registered token, or mint.
Requirement 2 — governor and guardian must differ
bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");The guardian's veto is the only check on a reserve withdrawal during its 2-day window. If one entity holds both, the check is decorative.
The contract cannot enforce this, because it cannot know that two addresses are controlled by the same person.
Requirement 3 — keeper on a dedicated hot wallet
The keeper key will be used frequently and automatically, so treat it as compromised-by-default. It should hold only gas and nothing else. Its powers are already bounded — see keeper constraints.
Requirement 4 — verify the transfer on chain
After granting roles to the multisig, the deployer must renounce, and this must be verified, not assumed:
cast call <contract> 'hasRole(bytes32,address)(bool)' \ 0x0000000000000000000000000000000000000000000000000000000000000000 <deployer> \ --rpc-url https://rpc.mainnet.chain.robinhood.comEvery contract must return false for the deployer, for every role.
The registry uses Ownable2Step, so ownership transfer requires the multisig to call acceptOwnership() — a mistyped address cannot orphan the contract.
Requirement 5 — publish the holders
Users cannot evaluate trust assumptions they cannot see. Role holders belong in contract roles and in the deployment record once assigned.
Suggested configuration
| Role | Holder | Threshold |
|---|---|---|
Registry owner | Multisig | 3-of-5 |
DEFAULT_ADMIN_ROLE | Same multisig | 3-of-5 |
PAUSER_ROLE | Multisig, or a faster incident multisig | 2-of-5 |
EMERGENCY_ROLE | Multisig | 3-of-5 |
KEEPER_ROLE | Dedicated hot wallet | — |
GOVERNOR_ROLE | Multisig | 3-of-5 |
GUARDIAN_ROLE | A different set of signers | 2-of-3 |
A faster threshold on PAUSER_ROLE is defensible: pausing is a safe action that traps nothing, so optimising for speed there costs little.
Until this is done
Plouto should not be described as production-ready, and this documentation does not describe it that way. See known limitations.