NimbusProtocol
Trust and security

Robinhood governance safeguards

Proposal delays, state bindings, zone lifecycle controls and native refunds in the Robinhood contracts.

The Robinhood contracts use seconds for governance waits. These safeguards describe the contract source; applying them to an existing deployment requires the deployment changes described below.

Proposals and authorities

MinimumDelayTimelock preserves OpenZeppelin operation identifiers, predecessor dependencies and executor roles. Its scheduling floor is one second. A proposal executes from its maturity until 12 hours after maturity, including both boundaries. Execution after that window fails.

Governance can assign a proposer and delay to each contract address and function selector through setOperationPolicy. The assigned proposer must also hold PROPOSER_ROLE; an unassigned function accepts any holder of that role. The global minimum delay still applies, so the effective wait is the larger of that minimum and the function's configured delay.

Changing a function's authority or increasing its delay advances its queue epoch and invalidates pending calls under the previous epoch. A reduction under the same authority preserves pending calls and their original maturity. Every policy replacement has a separate state revision, so an older queued policy change cannot overwrite a newer one. Revoking a proposer invalidates that account's pending proposals, even if the role is granted again. Increasing the global minimum invalidates older pending proposals. Reducing a function's delay must first wait the longer delay it replaces. Cancellation retains OpenZeppelin's canceller-role requirement.

The timelock keeps its current proposal, call receipt and batch position in transient storage, which lasts only for the current transaction. Scheduled proposals, policies, roles and delays remain in persistent storage. Nested execution saves and restores the outer execution context; every dispatched call still rechecks its proposal and reviewed state.

Reviewed state

Targets can expose governanceState to bind a queued call to the state it was reviewed against. The timelock checks that commitment before execution and again before each call in a batch. If an earlier batch call invalidates a later one, the entire batch reverts.

Node keys and registration

A node rotation binds the current signer as well as the proposed replacement in the scheduled calldata. If another rotation changes that signer first, the older rotation fails. Revoking a node advances a shared registration epoch and invalidates earlier applications and registrations for both screener and data-endorser committees. A fresh proposal can register after the revocation.

Deposit asset grants

Revoking a deposit asset advances the pool's shared grant epoch. Every asset grant queued before that revocation becomes invalid, including grants for another asset. Deposits can be reopened by a proposal reviewed after the revocation.

Zone changes

Key rotation, authority transfer, freeze, unfreeze and delay-table replacement each require at least one second. Execution also applies this floor to legacy zones that stored zero ordinary delays. The recovery delay is the exception: a zone may choose any value, zero included, and a recovery waits exactly the delay the zone holds when the recovery opens. The registry sets no bound on it, so no governance change can shorten a recovery. A zone that chooses zero accepts a recovery that executes in the block it opens, before its authority can veto it.

Ordinary changes and recoveries have an inclusive 12-hour execution window after maturity. The zone authority or bootstrap can cancel a pending change; anyone can clear one after it expires.

Signer-leaf cooldown

Governance sets the cooldown through setLeafCooldown, naming the current value it replaces. Key rotation and unfreeze can change the signer leaf only after that cooldown has elapsed since the leaf last changed. An unchanged leaf needs no extra wait. Freeze and recovery can change a leaf during cooldown; the cooldown then starts from that change. Existing registries default to zero cooldown until governance configures one.

Shortening a delay

A replacement delay table waits the current delay-change wait and every old delay being reduced, whichever is longest. For example, suppose recovery waits one day and changing the delay table normally waits one minute. A proposal to reduce recovery to one minute still waits one day before installation. Once installed, a newly opened recovery waits the new one minute. An already opened recovery keeps its original maturity.

Fee-enclave switches

Activation, deactivation, freeze and unfreeze retain their distinct proposal roles. A role holder queues the switch through proposeSwitch; anyone can execute it after its wait. The proposal uses the proxy owner's operation policy and global minimum, with the one-second floor when the owner provides no timelock policy. The execution window is 12 hours after maturity.

Activation binds the reviewed controller and enclave key. Deactivation invalidates earlier activation proposals, and freeze invalidates earlier unfreeze proposals. Role revocation, operation-authority reassignment, an operation-delay increase, a global-delay increase or a change of governance owner can also invalidate a proposal. Restoring a role or lowering the global delay again does not revive it.

A governance owner holding the corresponding switch role can execute its own timelock proposal without a second waiting period. The registry accepts this route only while that owner is dispatching the exact target and calldata. A different role holder cannot forge this receipt. Each of the four timelock switch calls also binds the registry epoch of its corresponding role. Revocation invalidates those queued calls before execution, including later calls in the same batch. Revoking a different switch role leaves the proposal valid.

For example, suppose a freeze is queued through the timelock, then its FREEZE_ROLE is revoked and granted again. The older freeze still cannot execute. Governance must review and queue a fresh freeze after the role is restored.

The proposer or governance admin can cancel a local switch proposal. Anyone can clear an expired or invalidated one. A consumed proposal cannot execute twice.

Native refunds and deployment

PoolProxy pins the implementation's WETH address when it is constructed. An empty native refund from that WETH takes a receipt path that reads no proxy storage and does not delegate, allowing WETH implementations that refund using the transfer stipend. Other senders still follow the pool implementation's receive checks. An implementation upgrade must preserve the pinned WETH.

The proxy and timelock runtime code is not upgradeable. Existing deployments need a replacement timelock and a fresh proxy to obtain those runtime safeguards. The registry and pool state added for the remaining guards is appended to existing storage. Deployment scripts pin the recognized timelock runtime hash and reject a different implementation. The transient execution-context change also requires a replacement timelock: it cannot be applied to an existing instance in place. Its policies and roles must be configured and governance authority transferred under the existing timelock's rules; queued operations remain with the old instance and do not migrate automatically.

The pool uses interface version 3 for its 20-slot-only fee-collection proof support. The guardian registry uses interface version 2: the pool reads its threshold, active committee size and current status together for transaction processing and registration. The existing full cluster and status reads remain available, and the combined read preserves their status rules. Current signer authorisation is still checked at execution, after token inflows for a value operation. Upgrade the guardian registry and pool together in one timelock batch, with the registry first.

Cluster activation binds the same configuration digest. Its domain, field widths, byte order and list order are preserved; the contract constructs the encoded configuration in one destination buffer instead of repeatedly copying a growing prefix. Existing activation approvals therefore keep the same configuration binding.

The other five upgradeable Robinhood contracts remain at interface version 1. Rust services compile their expectations from interface-versions.json, the CLI imports that file directly, and the SDK bundles it into its package during the build. Rebuild the services, CLI and SDK from the deployment's source revision. Wallet clients must also match the current contract ABI; matching version numbers alone does not establish that their calldata is compatible. Later incompatible upgrades must advance the affected entries and update their dependants together. Indexers must apply the archive migration for the new governance events before archiving them.

This page does not prescribe a live pool migration. A fresh proxy requires a separately reviewed plan for its custody and state.

Last updated

On this page