Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bad01be
feat: SafeTimelockFactory + TimelockControllerImpl + ICallValidator
crypt0fairy Apr 23, 2026
3e3f728
feat: wire SafeTimelockFactory into AppController storage
crypt0fairy Apr 23, 2026
145659c
feat: timelocked gate for upgradeApp, terminateApp, terminateAppByAdmin
crypt0fairy Apr 23, 2026
a7a52f5
feat: wire SafeTimelockFactory into fresh-deploy path + regression tests
crypt0fairy Apr 23, 2026
22ecea3
feat: transferOwnership with timelocked-flag handoff
crypt0fairy Apr 23, 2026
104a290
feat: hardened ICallValidator integration (AppController + Timelock)
crypt0fairy Apr 23, 2026
2724c75
feat: cap _pendingIds to bound on-chain enumeration cost
crypt0fairy Apr 23, 2026
216b8a6
feat: v1.5.0-governance release scripts
crypt0fairy Apr 23, 2026
e4bffee
chore: lower MAX_PENDING_OPS from 128 to 32
crypt0fairy Apr 23, 2026
ed84cd9
feat: per-app team RBAC (ADMIN / PAUSER / DEVELOPER)
crypt0fairy Apr 23, 2026
3c09c08
refactor: extract AppAuthority for per-app ownership + RBAC
crypt0fairy Apr 24, 2026
527d72e
refactor: drop timelocked flag + SafeTimelockFactory coupling
crypt0fairy Apr 24, 2026
e915ec8
docs: replace internal "Option-2" references with descriptive text
crypt0fairy Apr 25, 2026
b1a22d6
docs: drop branch-local artifacts and stale references
crypt0fairy Apr 25, 2026
66bc593
merge origin/master into taras/app-governance
crypt0fairy Apr 30, 2026
8f6e0e4
review: fix self-transfer bug, PC leftover, migration gap
crypt0fairy Apr 30, 2026
c70f43e
feat: two-step transferOwnership + receiver quota check
crypt0fairy May 7, 2026
e1a284b
chore: regenerate Go bindings for AppController
crypt0fairy May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
696 changes: 695 additions & 1 deletion pkg/bindings/v1/AppController/binding.go

Large diffs are not rendered by default.

443 changes: 439 additions & 4 deletions pkg/bindings/v2/AppController/binding.go

Large diffs are not rendered by default.

48 changes: 44 additions & 4 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import {ComputeAVSRegistrar} from "../src/ComputeAVSRegistrar.sol";
import {ComputeOperator} from "../src/ComputeOperator.sol";
import {ImageAllowlist} from "../src/ImageAllowlist.sol";
import {IImageAllowlist} from "../src/interfaces/IImageAllowlist.sol";
import {SafeTimelockFactory} from "../src/factories/SafeTimelockFactory.sol";
import {TimelockControllerImpl} from "../src/governance/TimelockControllerImpl.sol";
import {IAppAuthority} from "../src/interfaces/IAppAuthority.sol";
import {AppAuthority} from "../src/governance/AppAuthority.sol";

contract Deploy is Parser {
struct Proxies {
Expand All @@ -39,6 +43,7 @@ contract Deploy is Parser {
ComputeOperator computeOperator;
AppController appController;
ImageAllowlist imageAllowlist;
AppAuthority appAuthority;
}

function run(string memory environment) public {
Expand Down Expand Up @@ -90,6 +95,35 @@ contract Deploy is Parser {
UpgradeableBeacon appBeacon =
new UpgradeableBeacon(address(new App(params.version, IPermissionController(params.permissionController))));

// SafeTimelockFactory for tests and local deploys that need to
// exercise the deployTimelock / deploySafe paths. Safe infra
// addresses (singleton / proxy factory / fallback handler) are zero
// here because local environments don't have canonical Safe
// deployments; deploySafe is not callable in that configuration but
// deployTimelock remains functional. Production deploys set real
// Safe addresses via zeus env in the release scripts.
TimelockControllerImpl timelockImpl = new TimelockControllerImpl();
SafeTimelockFactory safeTimelockFactoryImpl = new SafeTimelockFactory({
_safeSingleton: address(0),
_safeProxyFactory: address(0),
_safeFallbackHandler: address(0),
_timelockImplementation: address(timelockImpl)
});
new TransparentUpgradeableProxy(
address(safeTimelockFactoryImpl),
address(params.proxyAdmin),
abi.encodeCall(SafeTimelockFactory.initialize, ())
);

// Deploy AppAuthority (owns per-app RBAC state consumed by
// AppController). The consumer (AppController proxy) is already
// known — it's the proxy we just created above. The impl holds
// the consumer immutable; the proxy just fronts it.
AppAuthority appAuthorityImpl = new AppAuthority(address(proxies.appController));
TransparentUpgradeableProxy appAuthorityProxy = new TransparentUpgradeableProxy(
address(appAuthorityImpl), address(params.proxyAdmin), abi.encodeCall(AppAuthority.initialize, ())
);

// Deploy implementation contracts
Implementations memory impls = Implementations({
app: App(appBeacon.implementation()),
Expand All @@ -114,9 +148,11 @@ contract Deploy is Parser {
_releaseManager: params.releaseManager,
_computeAVSRegistrar: IComputeAVSRegistrar(address(proxies.computeAVSRegistrar)),
_computeOperator: IComputeOperator(address(proxies.computeOperator)),
_appBeacon: appBeacon
_appBeacon: appBeacon,
_appAuthority: IAppAuthority(address(appAuthorityProxy))
}),
imageAllowlist: new ImageAllowlist()
imageAllowlist: new ImageAllowlist(),
appAuthority: appAuthorityImpl
});

// Upgrade proxies using ProxyAdmin
Expand Down Expand Up @@ -171,7 +207,9 @@ contract Deploy is Parser {
computeOperator: IComputeOperator(address(proxies.computeOperator)),
computeOperatorImpl: impls.computeOperator,
imageAllowlist: IImageAllowlist(address(proxies.imageAllowlist)),
imageAllowlistImpl: impls.imageAllowlist
imageAllowlistImpl: impls.imageAllowlist,
appAuthority: IAppAuthority(address(appAuthorityProxy)),
appAuthorityImpl: impls.appAuthority
});
}

Expand All @@ -194,7 +232,9 @@ contract Deploy is Parser {
vm.serializeAddress(addresses, "computeOperator", address(deployedContracts.computeOperator));
vm.serializeAddress(addresses, "computeOperatorImpl", address(deployedContracts.computeOperatorImpl));
vm.serializeAddress(addresses, "imageAllowlist", address(deployedContracts.imageAllowlist));
addresses = vm.serializeAddress(addresses, "imageAllowlistImpl", address(deployedContracts.imageAllowlistImpl));
vm.serializeAddress(addresses, "imageAllowlistImpl", address(deployedContracts.imageAllowlistImpl));
vm.serializeAddress(addresses, "appAuthority", address(deployedContracts.appAuthority));
addresses = vm.serializeAddress(addresses, "appAuthorityImpl", address(deployedContracts.appAuthorityImpl));

// Add the chainInfo object
string memory chainInfo = "chainInfo";
Expand Down
8 changes: 7 additions & 1 deletion script/Parser.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {ComputeAVSRegistrar} from "../src/ComputeAVSRegistrar.sol";
import {ComputeOperator} from "../src/ComputeOperator.sol";
import {ImageAllowlist} from "../src/ImageAllowlist.sol";
import {IImageAllowlist} from "../src/interfaces/IImageAllowlist.sol";
import {IAppAuthority} from "../src/interfaces/IAppAuthority.sol";
import {AppAuthority} from "../src/governance/AppAuthority.sol";

contract Parser is Script {
struct DeployParams {
Expand Down Expand Up @@ -52,6 +54,8 @@ contract Parser is Script {
ComputeOperator computeOperatorImpl;
IImageAllowlist imageAllowlist;
ImageAllowlist imageAllowlistImpl;
IAppAuthority appAuthority;
AppAuthority appAuthorityImpl;
}

function parseDeployParams(string memory environment) public view returns (DeployParams memory) {
Expand Down Expand Up @@ -93,7 +97,9 @@ contract Parser is Script {
computeOperator: IComputeOperator(vm.parseJsonAddress(json, ".addresses.computeOperator")),
computeOperatorImpl: ComputeOperator(vm.parseJsonAddress(json, ".addresses.computeOperatorImpl")),
imageAllowlist: IImageAllowlist(vm.parseJsonAddress(json, ".addresses.imageAllowlist")),
imageAllowlistImpl: ImageAllowlist(vm.parseJsonAddress(json, ".addresses.imageAllowlistImpl"))
imageAllowlistImpl: ImageAllowlist(vm.parseJsonAddress(json, ".addresses.imageAllowlistImpl")),
appAuthority: IAppAuthority(vm.parseJsonAddress(json, ".addresses.appAuthority")),
appAuthorityImpl: AppAuthority(vm.parseJsonAddress(json, ".addresses.appAuthorityImpl"))
});

return deployedContracts;
Expand Down
41 changes: 41 additions & 0 deletions script/releases/Env.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import {ComputeAVSRegistrar} from "../../src/ComputeAVSRegistrar.sol";
import {ComputeOperator} from "../../src/ComputeOperator.sol";
import {ImageAllowlist} from "../../src/ImageAllowlist.sol";
import {USDCCredits} from "../../src/USDCCredits.sol";
import {SafeTimelockFactory} from "../../src/factories/SafeTimelockFactory.sol";
import {TimelockControllerImpl} from "../../src/governance/TimelockControllerImpl.sol";
import {AppAuthority} from "../../src/governance/AppAuthority.sol";

library Env {
using ZEnvHelpers for *;
Expand Down Expand Up @@ -110,6 +113,14 @@ library Env {
return USDCCredits(_deployedProxy(type(USDCCredits).name));
}

function safeTimelockFactory(DeployedProxy) internal view returns (SafeTimelockFactory) {
return SafeTimelockFactory(_deployedProxy(type(SafeTimelockFactory).name));
}

function appAuthority(DeployedProxy) internal view returns (AppAuthority) {
return AppAuthority(_deployedProxy(type(AppAuthority).name));
}

function appBeacon(DeployedBeacon) internal view returns (UpgradeableBeacon) {
return UpgradeableBeacon(_deployedBeacon(type(App).name));
}
Expand Down Expand Up @@ -141,6 +152,20 @@ library Env {
return USDCCredits(_deployedImpl(type(USDCCredits).name));
}

function safeTimelockFactory(DeployedImpl) internal view returns (SafeTimelockFactory) {
return SafeTimelockFactory(_deployedImpl(type(SafeTimelockFactory).name));
}

function appAuthority(DeployedImpl) internal view returns (AppAuthority) {
return AppAuthority(_deployedImpl(type(AppAuthority).name));
}

/// @notice TimelockControllerImpl — the clone-master for Timelocks created
/// by SafeTimelockFactory. Not behind a proxy; deployed directly.
function timelockControllerImpl() internal view returns (TimelockControllerImpl) {
return TimelockControllerImpl(payable(_deployedContract(type(TimelockControllerImpl).name)));
}

/**
* governance contracts
*/
Expand Down Expand Up @@ -202,6 +227,22 @@ library Env {
return _envU256("USDC_MINIMUM_PURCHASE");
}

/**
* Safe infrastructure — canonical Gnosis Safe singletons per chain.
* Consumed by the SafeTimelockFactory constructor.
*/
function safeSingleton() internal view returns (address) {
return _envAddress("safeSingleton");
}

function safeProxyFactory() internal view returns (address) {
return _envAddress("safeProxyFactory");
}

function safeFallbackHandler() internal view returns (address) {
return _envAddress("safeFallbackHandler");
}

/**
* Helpers
*/
Expand Down
6 changes: 5 additions & 1 deletion script/releases/v1.0.4-init/1-deployContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {ComputeOperator} from "../../../src/ComputeOperator.sol";
import {IAppController} from "../../../src/interfaces/IAppController.sol";
import {IComputeAVSRegistrar} from "../../../src/interfaces/IComputeAVSRegistrar.sol";
import {IComputeOperator} from "../../../src/interfaces/IComputeOperator.sol";
import {IAppAuthority} from "../../../src/interfaces/IAppAuthority.sol";

/**
* Purpose: use an EOA to deploy all compute contracts.
Expand Down Expand Up @@ -82,7 +83,10 @@ contract Deploy is EOADeployer {
_releaseManager: Env.releaseManager(),
_computeAVSRegistrar: IComputeAVSRegistrar(address(computeAVSRegistrarProxy)),
_computeOperator: IComputeOperator(address(computeOperatorProxy)),
_appBeacon: appBeacon
_appBeacon: appBeacon,
// v1.0.4 predates AppAuthority. Historical script kept compilable
// against the current constructor; this path never runs.
_appAuthority: IAppAuthority(address(0))
});

// Upgrade proxies using ProxyAdmin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "../Env.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";

import {AppController} from "../../../src/AppController.sol";
import {IAppAuthority} from "../../../src/interfaces/IAppAuthority.sol";

/**
* Purpose: deploy new AppController implementation with suspension functionality
Expand All @@ -26,7 +27,9 @@ contract DeployAppControllerImpl is EOADeployer {
_releaseManager: Env.releaseManager(),
_computeAVSRegistrar: Env.proxy.computeAVSRegistrar(),
_computeOperator: Env.proxy.computeOperator(),
_appBeacon: Env.beacon.appBeacon()
_appBeacon: Env.beacon.appBeacon(),
// v1.1.1 predates AppAuthority. Historical script; never runs again.
_appAuthority: IAppAuthority(address(0))
});

// Register new implementation in Env system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "../Env.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";

import {AppController} from "../../../src/AppController.sol";
import {IAppAuthority} from "../../../src/interfaces/IAppAuthority.sol";

/**
* Purpose: deploy new AppController implementation with isolated billing functionality
Expand All @@ -26,7 +27,9 @@ contract DeployAppControllerImpl is EOADeployer {
_releaseManager: Env.releaseManager(),
_computeAVSRegistrar: Env.proxy.computeAVSRegistrar(),
_computeOperator: Env.proxy.computeOperator(),
_appBeacon: Env.beacon.appBeacon()
_appBeacon: Env.beacon.appBeacon(),
// v1.4.0 predates AppAuthority. Historical script; never runs again.
_appAuthority: IAppAuthority(address(0))
});

// Register new implementation in Env system
Expand Down
Loading
Loading