Skip to content

Enforce governance delays, etc.#9

Open
EndymionJkb wants to merge 13 commits into
mainfrom
admin-delay
Open

Enforce governance delays, etc.#9
EndymionJkb wants to merge 13 commits into
mainfrom
admin-delay

Conversation

@EndymionJkb

@EndymionJkb EndymionJkb commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Enforce governance delays, cap the fixed term, and split consequential roles

Motivation

Without execution delays, an admin can bypass the timelock those delays are meant to provide. The clearest case is the upgrader: with zero delay, an admin can replace an implementation instantly, and an upgrade can change the system entirely.

The same logic applies to any role that can make a consequential change. This PR assigns delays based on what each role can do, so no admin key can move funds, change code, or override pricing faster than the long delay. Immediate actions are limited to protective actions whose worst outcome is halting or excluding.

What this PR does

  1. Enforces governance delays across deployed roles, using execution delays, grant delays, and target admin delays.
  2. Caps the fixed term so committed users can exit before a governance change takes effect.
  3. Splits reinvestment into its own zero-delay keeper role.
  4. Splits blacklist control into three tiers: instant add, short-delayed removal, and long-delayed sanctions-source changes.
  5. Splits setConversionRate into its own long-delay role, while keeping oracle-source setters operational.
  6. Moves template registration to the long delay, bounding the factory's retained admin power.
  7. Removes dead role-config code, leaving DeployScript.getRoleConfig as the single source of truth.

Delay model

Two constants in Constants.sol are used by both the deploy script and the accountant:

  • SHORT_DELAY_SECONDS is two days. It applies to operational changes to live market configuration: kernel, accountant, protocol fee setter, oracle quoter, market ops, Balancer pool manager, unpauser, and blacklist removal.
  • LONG_DELAY_SECONDS is fifteen days. It applies to root admin execution, upgrader execution, grant delay on consequential roles, target admin delay on deployed proxies, sanctions-source changes, setConversionRate, and template registration.

The guiding rule is that emergency protection can be immediate, but reversal or consequential change must wait. The pauser has zero delay; the unpauser waits the short delay. Blacklist add is immediate; blacklist removal waits the short delay. The upgrader waits the long delay because an upgrade can replace implementations and change the system completely.

Fixed-term cap

MAX_FIXED_TERM_SECONDS is fourteen days. The deploy script asserts that the long delay is at least the fixed-term cap and at least the short delay. That preserves the core invariant: a committed user can exit before a governance change takes effect, and the admin path is never faster than a delay it can reconfigure.

The invariant is also protected at runtime. AccessManager applies delay changes through Time.Delay.withUpdate, so reducing a delay waits a setback equal to the reduction. Every path that can reduce a delay is itself bounded by the long delay: the governance admin has the long execution delay, and the factory's zero-delay admin can only be reached through a registered template, whose registration now also waits the long delay.

Role splits

The blacklist functions and conversion-rate override previously fell back to the admin role. They now have dedicated roles.

blacklistAccounts is assigned to ADMIN_BLACKLIST_ROLE with zero delay, since adding an address only excludes it and should be available immediately in an emergency.

unblacklistAccounts is assigned to ADMIN_UNBLACKLIST_ROLE with the short delay, since it removes protection for specific accounts but is not a system-wide security change.

setSanctionsList is assigned to ADMIN_SANCTIONS_ROLE with the long delay, since changing the sanctions source changes how every account is screened.

setConversionRate is assigned to ADMIN_CONVERSION_RATE_ROLE with the long delay. It is a direct price override and the most powerful function on the oracle quoter. The oracle-source setters, setChainlinkOracle, setBPTOracle, and setMaxReinvestmentSlippage, remain operational at the short delay.

Factory admin

The factory keeps the admin role at zero delay so it can wire roles during deployment. A registered template can reach that admin through executeAsFactory.

This PR bounds that path by moving template registration to the long delay. A malicious template therefore cannot be registered inside the reaction window, and the factory admin can only be driven by an already-registered trusted template during its deployment flow.

Removing the standing factory admin entirely is deferred as defense-in-depth against a template bug, not as part of the compromised-key path. The residual risk is documented at executeAsFactory, and the full admin-key-to-consequential-change path map is in docs/balancer-review/admin-delay-hardening.md.

Cleanup

RoleConfigUtils.getRoleConfig duplicated DeployScript.getRoleConfig and had only one consumer, UpgradeFactoryModule.

UpgradeFactoryModule no longer matches the current design. Its prepare path reads from the retired architecture, where the factory itself was the access manager, so it now reverts.

Both files are removed, leaving DeployScript.getRoleConfig as the single role-configuration source.

Open question

No outside party can cancel the root admin's scheduled operations, because the admin role's guardian is the admin role itself. An independent veto would require a second admin-role holder. Balancer's TimelockAuthorizer, for comparison, had separate executors and cancelers.

That is a key-management decision for the deployer, not a code change, and is left open deliberately.

Example Illustration

day-roles-and-delays

# Conflicts:
#	test/utils/RoycoDayTestBase.sol
# Conflicts:
#	script/Deploy.s.sol
#	src/libraries/Constants.sol
#	test/concrete/Factory/Test_DeployScriptConfig.t.sol
#	test/fork/kernels/Test_KernelSuiteBase.t.sol
# Conflicts:
#	src/factory/templates/Identical_ERC4626_ST_JT_SharePriceToChainlinkOracle_BalancerV3GyroECLP_LT_DeploymentTemplate.sol
# Conflicts:
#	src/accountant/RoycoDayAccountant.sol
#	src/factory/templates/Identical_ERC4626_ST_JT_SharePriceToChainlinkOracle_BalancerV3GyroECLP_LT_DeploymentTemplate.sol
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant