diff --git a/pkg/bindings/v1/AppController/binding.go b/pkg/bindings/v1/AppController/binding.go index 33e7007..6ee07a4 100644 --- a/pkg/bindings/v1/AppController/binding.go +++ b/pkg/bindings/v1/AppController/binding.go @@ -75,7 +75,7 @@ type IReleaseManagerTypesRelease struct { // AppControllerMetaData contains all meta data concerning the AppController contract. var AppControllerMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"_permissionController\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"},{\"name\":\"_releaseManager\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"},{\"name\":\"_computeAVSRegistrar\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"},{\"name\":\"_computeOperator\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"},{\"name\":\"_appBeacon\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"API_PERMISSION_TYPEHASH\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"appBeacon\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateApiPermissionDigestHash\",\"inputs\":[{\"name\":\"permission\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"expiry\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateAppId\",\"inputs\":[{\"name\":\"deployer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"computeAVSRegistrar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"computeOperator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"confirmUpgrade\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createApp\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createAppWithIsolatedBilling\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"domainSeparator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getActiveAppCount\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppCreator\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppLatestReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppOperatorSetId\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppPendingReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppStatus\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getApps\",\"inputs\":[{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByBillingAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByCreator\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByDeveloper\",\"inputs\":[{\"name\":\"developer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingAccount\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingType\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.BillingType\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"globalActiveAppCount\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"admin\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"maxGlobalActiveApps\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"permissionController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"releaseManager\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMaxGlobalActiveApps\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"startApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"stopApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"suspend\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateAppByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateAppMetadataURI\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"AppCreated\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppMetadataURIUpdated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStarted\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStopped\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppSuspended\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminatedByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppUpgraded\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"rmsReleaseId\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"release\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"GlobalMaxActiveAppsSet\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MaxActiveAppsSet\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpgradeConfirmed\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccountHasActiveApps\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppAlreadyExists\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppDoesNotExist\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"GlobalMaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAppStatus\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidPermissions\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReleaseMetadataURI\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidShortString\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MoreThanOneArtifact\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoPendingUpgrade\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SignatureExpired\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"StringTooLong\",\"inputs\":[{\"name\":\"str\",\"type\":\"string\",\"internalType\":\"string\"}]}]", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"_permissionController\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"},{\"name\":\"_releaseManager\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"},{\"name\":\"_computeAVSRegistrar\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"},{\"name\":\"_computeOperator\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"},{\"name\":\"_appBeacon\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"},{\"name\":\"_appAuthority\",\"type\":\"address\",\"internalType\":\"contractIAppAuthority\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"API_PERMISSION_TYPEHASH\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"appAuthority\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIAppAuthority\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"appBeacon\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateApiPermissionDigestHash\",\"inputs\":[{\"name\":\"permission\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"expiry\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateAppId\",\"inputs\":[{\"name\":\"deployer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"canCall\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"cancelOwnershipTransfer\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"computeAVSRegistrar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"computeOperator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"confirmUpgrade\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createApp\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createAppWithIsolatedBilling\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"domainSeparator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getActiveAppCount\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppCreator\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppLatestReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppOperatorSetId\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppPendingReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppStatus\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getApps\",\"inputs\":[{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByBillingAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByCreator\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByDeveloper\",\"inputs\":[{\"name\":\"developer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingAccount\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingType\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.BillingType\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getPendingOwner\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"globalActiveAppCount\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"admin\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"maxGlobalActiveApps\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"migrateAppsToAppAuthority\",\"inputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"permissionController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"releaseManager\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMaxGlobalActiveApps\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"startApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"stopApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"suspend\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateAppByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateAppMetadataURI\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"AppCreated\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppMetadataURIUpdated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppOwnershipTransferred\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStarted\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStopped\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppSuspended\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminatedByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppUpgraded\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"rmsReleaseId\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"release\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"GlobalMaxActiveAppsSet\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MaxActiveAppsSet\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferCancelled\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"currentOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"cancelledOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferProposed\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"currentOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"proposedOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpgradeConfirmed\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccountHasActiveApps\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppAlreadyExists\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppDoesNotExist\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"GlobalMaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAppStatus\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidPermissions\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReleaseMetadataURI\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidShortString\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidTeamRole\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MoreThanOneArtifact\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoPendingUpgrade\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotCreator\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotPendingOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SignatureExpired\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"StringTooLong\",\"inputs\":[{\"name\":\"str\",\"type\":\"string\",\"internalType\":\"string\"}]}]", } // AppControllerABI is the input ABI used to generate the binding from. @@ -255,6 +255,37 @@ func (_AppController *AppControllerCallerSession) APIPERMISSIONTYPEHASH() ([32]b return _AppController.Contract.APIPERMISSIONTYPEHASH(&_AppController.CallOpts) } +// AppAuthority is a free data retrieval call binding the contract method 0x8029bbca. +// +// Solidity: function appAuthority() view returns(address) +func (_AppController *AppControllerCaller) AppAuthority(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _AppController.contract.Call(opts, &out, "appAuthority") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// AppAuthority is a free data retrieval call binding the contract method 0x8029bbca. +// +// Solidity: function appAuthority() view returns(address) +func (_AppController *AppControllerSession) AppAuthority() (common.Address, error) { + return _AppController.Contract.AppAuthority(&_AppController.CallOpts) +} + +// AppAuthority is a free data retrieval call binding the contract method 0x8029bbca. +// +// Solidity: function appAuthority() view returns(address) +func (_AppController *AppControllerCallerSession) AppAuthority() (common.Address, error) { + return _AppController.Contract.AppAuthority(&_AppController.CallOpts) +} + // AppBeacon is a free data retrieval call binding the contract method 0x8a52d0b5. // // Solidity: function appBeacon() view returns(address) @@ -348,6 +379,37 @@ func (_AppController *AppControllerCallerSession) CalculateAppId(deployer common return _AppController.Contract.CalculateAppId(&_AppController.CallOpts, deployer, salt) } +// CanCall is a free data retrieval call binding the contract method 0x9614801b. +// +// Solidity: function canCall(address caller, bytes data) view returns(bool) +func (_AppController *AppControllerCaller) CanCall(opts *bind.CallOpts, caller common.Address, data []byte) (bool, error) { + var out []interface{} + err := _AppController.contract.Call(opts, &out, "canCall", caller, data) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// CanCall is a free data retrieval call binding the contract method 0x9614801b. +// +// Solidity: function canCall(address caller, bytes data) view returns(bool) +func (_AppController *AppControllerSession) CanCall(caller common.Address, data []byte) (bool, error) { + return _AppController.Contract.CanCall(&_AppController.CallOpts, caller, data) +} + +// CanCall is a free data retrieval call binding the contract method 0x9614801b. +// +// Solidity: function canCall(address caller, bytes data) view returns(bool) +func (_AppController *AppControllerCallerSession) CanCall(caller common.Address, data []byte) (bool, error) { + return _AppController.Contract.CanCall(&_AppController.CallOpts, caller, data) +} + // ComputeAVSRegistrar is a free data retrieval call binding the contract method 0xef6d92c6. // // Solidity: function computeAVSRegistrar() view returns(address) @@ -900,6 +962,37 @@ func (_AppController *AppControllerCallerSession) GetMaxActiveAppsPerUser(user c return _AppController.Contract.GetMaxActiveAppsPerUser(&_AppController.CallOpts, user) } +// GetPendingOwner is a free data retrieval call binding the contract method 0x66f6a5ed. +// +// Solidity: function getPendingOwner(address app) view returns(address) +func (_AppController *AppControllerCaller) GetPendingOwner(opts *bind.CallOpts, app common.Address) (common.Address, error) { + var out []interface{} + err := _AppController.contract.Call(opts, &out, "getPendingOwner", app) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetPendingOwner is a free data retrieval call binding the contract method 0x66f6a5ed. +// +// Solidity: function getPendingOwner(address app) view returns(address) +func (_AppController *AppControllerSession) GetPendingOwner(app common.Address) (common.Address, error) { + return _AppController.Contract.GetPendingOwner(&_AppController.CallOpts, app) +} + +// GetPendingOwner is a free data retrieval call binding the contract method 0x66f6a5ed. +// +// Solidity: function getPendingOwner(address app) view returns(address) +func (_AppController *AppControllerCallerSession) GetPendingOwner(app common.Address) (common.Address, error) { + return _AppController.Contract.GetPendingOwner(&_AppController.CallOpts, app) +} + // GlobalActiveAppCount is a free data retrieval call binding the contract method 0xa8aa2bd3. // // Solidity: function globalActiveAppCount() view returns(uint32) @@ -1024,6 +1117,37 @@ func (_AppController *AppControllerCallerSession) ReleaseManager() (common.Addre return _AppController.Contract.ReleaseManager(&_AppController.CallOpts) } +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) pure returns(bool) +func (_AppController *AppControllerCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _AppController.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) pure returns(bool) +func (_AppController *AppControllerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _AppController.Contract.SupportsInterface(&_AppController.CallOpts, interfaceId) +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) pure returns(bool) +func (_AppController *AppControllerCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _AppController.Contract.SupportsInterface(&_AppController.CallOpts, interfaceId) +} + // Version is a free data retrieval call binding the contract method 0x54fd4d50. // // Solidity: function version() view returns(string) @@ -1055,6 +1179,48 @@ func (_AppController *AppControllerCallerSession) Version() (string, error) { return _AppController.Contract.Version(&_AppController.CallOpts) } +// AcceptOwnership is a paid mutator transaction binding the contract method 0x51710e45. +// +// Solidity: function acceptOwnership(address app) returns() +func (_AppController *AppControllerTransactor) AcceptOwnership(opts *bind.TransactOpts, app common.Address) (*types.Transaction, error) { + return _AppController.contract.Transact(opts, "acceptOwnership", app) +} + +// AcceptOwnership is a paid mutator transaction binding the contract method 0x51710e45. +// +// Solidity: function acceptOwnership(address app) returns() +func (_AppController *AppControllerSession) AcceptOwnership(app common.Address) (*types.Transaction, error) { + return _AppController.Contract.AcceptOwnership(&_AppController.TransactOpts, app) +} + +// AcceptOwnership is a paid mutator transaction binding the contract method 0x51710e45. +// +// Solidity: function acceptOwnership(address app) returns() +func (_AppController *AppControllerTransactorSession) AcceptOwnership(app common.Address) (*types.Transaction, error) { + return _AppController.Contract.AcceptOwnership(&_AppController.TransactOpts, app) +} + +// CancelOwnershipTransfer is a paid mutator transaction binding the contract method 0x7b37e561. +// +// Solidity: function cancelOwnershipTransfer(address app) returns() +func (_AppController *AppControllerTransactor) CancelOwnershipTransfer(opts *bind.TransactOpts, app common.Address) (*types.Transaction, error) { + return _AppController.contract.Transact(opts, "cancelOwnershipTransfer", app) +} + +// CancelOwnershipTransfer is a paid mutator transaction binding the contract method 0x7b37e561. +// +// Solidity: function cancelOwnershipTransfer(address app) returns() +func (_AppController *AppControllerSession) CancelOwnershipTransfer(app common.Address) (*types.Transaction, error) { + return _AppController.Contract.CancelOwnershipTransfer(&_AppController.TransactOpts, app) +} + +// CancelOwnershipTransfer is a paid mutator transaction binding the contract method 0x7b37e561. +// +// Solidity: function cancelOwnershipTransfer(address app) returns() +func (_AppController *AppControllerTransactorSession) CancelOwnershipTransfer(app common.Address) (*types.Transaction, error) { + return _AppController.Contract.CancelOwnershipTransfer(&_AppController.TransactOpts, app) +} + // ConfirmUpgrade is a paid mutator transaction binding the contract method 0xbbc1c204. // // Solidity: function confirmUpgrade(address app) returns() @@ -1139,6 +1305,27 @@ func (_AppController *AppControllerTransactorSession) Initialize(admin common.Ad return _AppController.Contract.Initialize(&_AppController.TransactOpts, admin) } +// MigrateAppsToAppAuthority is a paid mutator transaction binding the contract method 0xa6a062cd. +// +// Solidity: function migrateAppsToAppAuthority(address[] apps) returns() +func (_AppController *AppControllerTransactor) MigrateAppsToAppAuthority(opts *bind.TransactOpts, apps []common.Address) (*types.Transaction, error) { + return _AppController.contract.Transact(opts, "migrateAppsToAppAuthority", apps) +} + +// MigrateAppsToAppAuthority is a paid mutator transaction binding the contract method 0xa6a062cd. +// +// Solidity: function migrateAppsToAppAuthority(address[] apps) returns() +func (_AppController *AppControllerSession) MigrateAppsToAppAuthority(apps []common.Address) (*types.Transaction, error) { + return _AppController.Contract.MigrateAppsToAppAuthority(&_AppController.TransactOpts, apps) +} + +// MigrateAppsToAppAuthority is a paid mutator transaction binding the contract method 0xa6a062cd. +// +// Solidity: function migrateAppsToAppAuthority(address[] apps) returns() +func (_AppController *AppControllerTransactorSession) MigrateAppsToAppAuthority(apps []common.Address) (*types.Transaction, error) { + return _AppController.Contract.MigrateAppsToAppAuthority(&_AppController.TransactOpts, apps) +} + // SetMaxActiveAppsPerUser is a paid mutator transaction binding the contract method 0xd49fec2b. // // Solidity: function setMaxActiveAppsPerUser(address user, uint32 limit) returns() @@ -1286,6 +1473,27 @@ func (_AppController *AppControllerTransactorSession) TerminateAppByAdmin(app co return _AppController.Contract.TerminateAppByAdmin(&_AppController.TransactOpts, app) } +// TransferOwnership is a paid mutator transaction binding the contract method 0x6d435421. +// +// Solidity: function transferOwnership(address app, address newOwner) returns() +func (_AppController *AppControllerTransactor) TransferOwnership(opts *bind.TransactOpts, app common.Address, newOwner common.Address) (*types.Transaction, error) { + return _AppController.contract.Transact(opts, "transferOwnership", app, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0x6d435421. +// +// Solidity: function transferOwnership(address app, address newOwner) returns() +func (_AppController *AppControllerSession) TransferOwnership(app common.Address, newOwner common.Address) (*types.Transaction, error) { + return _AppController.Contract.TransferOwnership(&_AppController.TransactOpts, app, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0x6d435421. +// +// Solidity: function transferOwnership(address app, address newOwner) returns() +func (_AppController *AppControllerTransactorSession) TransferOwnership(app common.Address, newOwner common.Address) (*types.Transaction, error) { + return _AppController.Contract.TransferOwnership(&_AppController.TransactOpts, app, newOwner) +} + // UpdateAppMetadataURI is a paid mutator transaction binding the contract method 0x65aa9a65. // // Solidity: function updateAppMetadataURI(address app, string metadataURI) returns() @@ -1627,6 +1835,168 @@ func (_AppController *AppControllerFilterer) ParseAppMetadataURIUpdated(log type return event, nil } +// AppControllerAppOwnershipTransferredIterator is returned from FilterAppOwnershipTransferred and is used to iterate over the raw logs and unpacked data for AppOwnershipTransferred events raised by the AppController contract. +type AppControllerAppOwnershipTransferredIterator struct { + Event *AppControllerAppOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppControllerAppOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppControllerAppOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppControllerAppOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppControllerAppOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppControllerAppOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppControllerAppOwnershipTransferred represents a AppOwnershipTransferred event raised by the AppController contract. +type AppControllerAppOwnershipTransferred struct { + App common.Address + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAppOwnershipTransferred is a free log retrieval operation binding the contract event 0x3fa03516e5ee455b2d2779f21b254735e2c1f82cf338619c1b96816df2a467a4. +// +// Solidity: event AppOwnershipTransferred(address indexed app, address indexed previousOwner, address indexed newOwner) +func (_AppController *AppControllerFilterer) FilterAppOwnershipTransferred(opts *bind.FilterOpts, app []common.Address, previousOwner []common.Address, newOwner []common.Address) (*AppControllerAppOwnershipTransferredIterator, error) { + + var appRule []interface{} + for _, appItem := range app { + appRule = append(appRule, appItem) + } + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _AppController.contract.FilterLogs(opts, "AppOwnershipTransferred", appRule, previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &AppControllerAppOwnershipTransferredIterator{contract: _AppController.contract, event: "AppOwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchAppOwnershipTransferred is a free log subscription operation binding the contract event 0x3fa03516e5ee455b2d2779f21b254735e2c1f82cf338619c1b96816df2a467a4. +// +// Solidity: event AppOwnershipTransferred(address indexed app, address indexed previousOwner, address indexed newOwner) +func (_AppController *AppControllerFilterer) WatchAppOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *AppControllerAppOwnershipTransferred, app []common.Address, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var appRule []interface{} + for _, appItem := range app { + appRule = append(appRule, appItem) + } + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _AppController.contract.WatchLogs(opts, "AppOwnershipTransferred", appRule, previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppControllerAppOwnershipTransferred) + if err := _AppController.contract.UnpackLog(event, "AppOwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAppOwnershipTransferred is a log parse operation binding the contract event 0x3fa03516e5ee455b2d2779f21b254735e2c1f82cf338619c1b96816df2a467a4. +// +// Solidity: event AppOwnershipTransferred(address indexed app, address indexed previousOwner, address indexed newOwner) +func (_AppController *AppControllerFilterer) ParseAppOwnershipTransferred(log types.Log) (*AppControllerAppOwnershipTransferred, error) { + event := new(AppControllerAppOwnershipTransferred) + if err := _AppController.contract.UnpackLog(event, "AppOwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // AppControllerAppStartedIterator is returned from FilterAppStarted and is used to iterate over the raw logs and unpacked data for AppStarted events raised by the AppController contract. type AppControllerAppStartedIterator struct { Event *AppControllerAppStarted // Event containing the contract specifics and raw log @@ -2906,6 +3276,330 @@ func (_AppController *AppControllerFilterer) ParseMaxActiveAppsSet(log types.Log return event, nil } +// AppControllerOwnershipTransferCancelledIterator is returned from FilterOwnershipTransferCancelled and is used to iterate over the raw logs and unpacked data for OwnershipTransferCancelled events raised by the AppController contract. +type AppControllerOwnershipTransferCancelledIterator struct { + Event *AppControllerOwnershipTransferCancelled // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppControllerOwnershipTransferCancelledIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppControllerOwnershipTransferCancelled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppControllerOwnershipTransferCancelled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppControllerOwnershipTransferCancelledIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppControllerOwnershipTransferCancelledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppControllerOwnershipTransferCancelled represents a OwnershipTransferCancelled event raised by the AppController contract. +type AppControllerOwnershipTransferCancelled struct { + App common.Address + CurrentOwner common.Address + CancelledOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferCancelled is a free log retrieval operation binding the contract event 0xcad70233aeb5045d6c0748ae334980c91d2ebb929df025f4e53f236e3365cdae. +// +// Solidity: event OwnershipTransferCancelled(address indexed app, address indexed currentOwner, address indexed cancelledOwner) +func (_AppController *AppControllerFilterer) FilterOwnershipTransferCancelled(opts *bind.FilterOpts, app []common.Address, currentOwner []common.Address, cancelledOwner []common.Address) (*AppControllerOwnershipTransferCancelledIterator, error) { + + var appRule []interface{} + for _, appItem := range app { + appRule = append(appRule, appItem) + } + var currentOwnerRule []interface{} + for _, currentOwnerItem := range currentOwner { + currentOwnerRule = append(currentOwnerRule, currentOwnerItem) + } + var cancelledOwnerRule []interface{} + for _, cancelledOwnerItem := range cancelledOwner { + cancelledOwnerRule = append(cancelledOwnerRule, cancelledOwnerItem) + } + + logs, sub, err := _AppController.contract.FilterLogs(opts, "OwnershipTransferCancelled", appRule, currentOwnerRule, cancelledOwnerRule) + if err != nil { + return nil, err + } + return &AppControllerOwnershipTransferCancelledIterator{contract: _AppController.contract, event: "OwnershipTransferCancelled", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferCancelled is a free log subscription operation binding the contract event 0xcad70233aeb5045d6c0748ae334980c91d2ebb929df025f4e53f236e3365cdae. +// +// Solidity: event OwnershipTransferCancelled(address indexed app, address indexed currentOwner, address indexed cancelledOwner) +func (_AppController *AppControllerFilterer) WatchOwnershipTransferCancelled(opts *bind.WatchOpts, sink chan<- *AppControllerOwnershipTransferCancelled, app []common.Address, currentOwner []common.Address, cancelledOwner []common.Address) (event.Subscription, error) { + + var appRule []interface{} + for _, appItem := range app { + appRule = append(appRule, appItem) + } + var currentOwnerRule []interface{} + for _, currentOwnerItem := range currentOwner { + currentOwnerRule = append(currentOwnerRule, currentOwnerItem) + } + var cancelledOwnerRule []interface{} + for _, cancelledOwnerItem := range cancelledOwner { + cancelledOwnerRule = append(cancelledOwnerRule, cancelledOwnerItem) + } + + logs, sub, err := _AppController.contract.WatchLogs(opts, "OwnershipTransferCancelled", appRule, currentOwnerRule, cancelledOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppControllerOwnershipTransferCancelled) + if err := _AppController.contract.UnpackLog(event, "OwnershipTransferCancelled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferCancelled is a log parse operation binding the contract event 0xcad70233aeb5045d6c0748ae334980c91d2ebb929df025f4e53f236e3365cdae. +// +// Solidity: event OwnershipTransferCancelled(address indexed app, address indexed currentOwner, address indexed cancelledOwner) +func (_AppController *AppControllerFilterer) ParseOwnershipTransferCancelled(log types.Log) (*AppControllerOwnershipTransferCancelled, error) { + event := new(AppControllerOwnershipTransferCancelled) + if err := _AppController.contract.UnpackLog(event, "OwnershipTransferCancelled", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppControllerOwnershipTransferProposedIterator is returned from FilterOwnershipTransferProposed and is used to iterate over the raw logs and unpacked data for OwnershipTransferProposed events raised by the AppController contract. +type AppControllerOwnershipTransferProposedIterator struct { + Event *AppControllerOwnershipTransferProposed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppControllerOwnershipTransferProposedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppControllerOwnershipTransferProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppControllerOwnershipTransferProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppControllerOwnershipTransferProposedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppControllerOwnershipTransferProposedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppControllerOwnershipTransferProposed represents a OwnershipTransferProposed event raised by the AppController contract. +type AppControllerOwnershipTransferProposed struct { + App common.Address + CurrentOwner common.Address + ProposedOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferProposed is a free log retrieval operation binding the contract event 0x8fed7b39cda3ba71166209a9aabe8392412ec261885b32365ee6c870f2cc6197. +// +// Solidity: event OwnershipTransferProposed(address indexed app, address indexed currentOwner, address indexed proposedOwner) +func (_AppController *AppControllerFilterer) FilterOwnershipTransferProposed(opts *bind.FilterOpts, app []common.Address, currentOwner []common.Address, proposedOwner []common.Address) (*AppControllerOwnershipTransferProposedIterator, error) { + + var appRule []interface{} + for _, appItem := range app { + appRule = append(appRule, appItem) + } + var currentOwnerRule []interface{} + for _, currentOwnerItem := range currentOwner { + currentOwnerRule = append(currentOwnerRule, currentOwnerItem) + } + var proposedOwnerRule []interface{} + for _, proposedOwnerItem := range proposedOwner { + proposedOwnerRule = append(proposedOwnerRule, proposedOwnerItem) + } + + logs, sub, err := _AppController.contract.FilterLogs(opts, "OwnershipTransferProposed", appRule, currentOwnerRule, proposedOwnerRule) + if err != nil { + return nil, err + } + return &AppControllerOwnershipTransferProposedIterator{contract: _AppController.contract, event: "OwnershipTransferProposed", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferProposed is a free log subscription operation binding the contract event 0x8fed7b39cda3ba71166209a9aabe8392412ec261885b32365ee6c870f2cc6197. +// +// Solidity: event OwnershipTransferProposed(address indexed app, address indexed currentOwner, address indexed proposedOwner) +func (_AppController *AppControllerFilterer) WatchOwnershipTransferProposed(opts *bind.WatchOpts, sink chan<- *AppControllerOwnershipTransferProposed, app []common.Address, currentOwner []common.Address, proposedOwner []common.Address) (event.Subscription, error) { + + var appRule []interface{} + for _, appItem := range app { + appRule = append(appRule, appItem) + } + var currentOwnerRule []interface{} + for _, currentOwnerItem := range currentOwner { + currentOwnerRule = append(currentOwnerRule, currentOwnerItem) + } + var proposedOwnerRule []interface{} + for _, proposedOwnerItem := range proposedOwner { + proposedOwnerRule = append(proposedOwnerRule, proposedOwnerItem) + } + + logs, sub, err := _AppController.contract.WatchLogs(opts, "OwnershipTransferProposed", appRule, currentOwnerRule, proposedOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppControllerOwnershipTransferProposed) + if err := _AppController.contract.UnpackLog(event, "OwnershipTransferProposed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferProposed is a log parse operation binding the contract event 0x8fed7b39cda3ba71166209a9aabe8392412ec261885b32365ee6c870f2cc6197. +// +// Solidity: event OwnershipTransferProposed(address indexed app, address indexed currentOwner, address indexed proposedOwner) +func (_AppController *AppControllerFilterer) ParseOwnershipTransferProposed(log types.Log) (*AppControllerOwnershipTransferProposed, error) { + event := new(AppControllerOwnershipTransferProposed) + if err := _AppController.contract.UnpackLog(event, "OwnershipTransferProposed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // AppControllerUpgradeConfirmedIterator is returned from FilterUpgradeConfirmed and is used to iterate over the raw logs and unpacked data for UpgradeConfirmed events raised by the AppController contract. type AppControllerUpgradeConfirmedIterator struct { Event *AppControllerUpgradeConfirmed // Event containing the contract specifics and raw log diff --git a/pkg/bindings/v2/AppController/binding.go b/pkg/bindings/v2/AppController/binding.go index 4547471..8c68989 100644 --- a/pkg/bindings/v2/AppController/binding.go +++ b/pkg/bindings/v2/AppController/binding.go @@ -70,7 +70,7 @@ type IReleaseManagerTypesRelease struct { // AppControllerMetaData contains all meta data concerning the AppController contract. var AppControllerMetaData = bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"_permissionController\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"},{\"name\":\"_releaseManager\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"},{\"name\":\"_computeAVSRegistrar\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"},{\"name\":\"_computeOperator\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"},{\"name\":\"_appBeacon\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"API_PERMISSION_TYPEHASH\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"appBeacon\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateApiPermissionDigestHash\",\"inputs\":[{\"name\":\"permission\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"expiry\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateAppId\",\"inputs\":[{\"name\":\"deployer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"computeAVSRegistrar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"computeOperator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"confirmUpgrade\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createApp\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createAppWithIsolatedBilling\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"domainSeparator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getActiveAppCount\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppCreator\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppLatestReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppOperatorSetId\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppPendingReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppStatus\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getApps\",\"inputs\":[{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByBillingAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByCreator\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByDeveloper\",\"inputs\":[{\"name\":\"developer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingAccount\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingType\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.BillingType\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"globalActiveAppCount\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"admin\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"maxGlobalActiveApps\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"permissionController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"releaseManager\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMaxGlobalActiveApps\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"startApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"stopApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"suspend\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateAppByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateAppMetadataURI\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"AppCreated\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppMetadataURIUpdated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStarted\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStopped\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppSuspended\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminatedByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppUpgraded\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"rmsReleaseId\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"release\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"GlobalMaxActiveAppsSet\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MaxActiveAppsSet\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpgradeConfirmed\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccountHasActiveApps\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppAlreadyExists\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppDoesNotExist\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"GlobalMaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAppStatus\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidPermissions\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReleaseMetadataURI\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidShortString\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MoreThanOneArtifact\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoPendingUpgrade\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SignatureExpired\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"StringTooLong\",\"inputs\":[{\"name\":\"str\",\"type\":\"string\",\"internalType\":\"string\"}]}]", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"_permissionController\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"},{\"name\":\"_releaseManager\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"},{\"name\":\"_computeAVSRegistrar\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"},{\"name\":\"_computeOperator\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"},{\"name\":\"_appBeacon\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"},{\"name\":\"_appAuthority\",\"type\":\"address\",\"internalType\":\"contractIAppAuthority\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"API_PERMISSION_TYPEHASH\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"appAuthority\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIAppAuthority\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"appBeacon\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIBeacon\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateApiPermissionDigestHash\",\"inputs\":[{\"name\":\"permission\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"expiry\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"calculateAppId\",\"inputs\":[{\"name\":\"deployer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"canCall\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"cancelOwnershipTransfer\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"computeAVSRegistrar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeAVSRegistrar\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"computeOperator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIComputeOperator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"confirmUpgrade\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createApp\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createAppWithIsolatedBilling\",\"inputs\":[{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"domainSeparator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getActiveAppCount\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppCreator\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppLatestReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppOperatorSetId\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppPendingReleaseBlockNumber\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppStatus\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getApps\",\"inputs\":[{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByBillingAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByCreator\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAppsByDeveloper\",\"inputs\":[{\"name\":\"developer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"offset\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"limit\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"},{\"name\":\"appConfigsMem\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.AppConfig[]\",\"components\":[{\"name\":\"creator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"latestReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.AppStatus\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingAccount\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBillingType\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumIAppController.BillingType\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getPendingOwner\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"globalActiveAppCount\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"admin\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"maxGlobalActiveApps\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"migrateAppsToAppAuthority\",\"inputs\":[{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"permissionController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIPermissionController\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"releaseManager\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIReleaseManager\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setMaxActiveAppsPerUser\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMaxGlobalActiveApps\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"startApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"stopApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"suspend\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"apps\",\"type\":\"address[]\",\"internalType\":\"contractIApp[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"terminateAppByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateAppMetadataURI\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeApp\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"contractIApp\"},{\"name\":\"release\",\"type\":\"tuple\",\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"AppCreated\",\"inputs\":[{\"name\":\"creator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"operatorSetId\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppMetadataURIUpdated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"metadataURI\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppOwnershipTransferred\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStarted\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppStopped\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppSuspended\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminated\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppTerminatedByAdmin\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AppUpgraded\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"rmsReleaseId\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"release\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIAppController.Release\",\"components\":[{\"name\":\"rmsRelease\",\"type\":\"tuple\",\"internalType\":\"structIReleaseManagerTypes.Release\",\"components\":[{\"name\":\"artifacts\",\"type\":\"tuple[]\",\"internalType\":\"structIReleaseManagerTypes.Artifact[]\",\"components\":[{\"name\":\"digest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"registry\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"upgradeByTime\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"publicEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"encryptedEnv\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"containerPolicy\",\"type\":\"tuple\",\"internalType\":\"structIAppController.ContainerPolicy\",\"components\":[{\"name\":\"args\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"cmdOverride\",\"type\":\"string[]\",\"internalType\":\"string[]\"},{\"name\":\"env\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"envOverride\",\"type\":\"tuple[]\",\"internalType\":\"structIAppController.EnvVar[]\",\"components\":[{\"name\":\"key\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"value\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"restartPolicy\",\"type\":\"string\",\"internalType\":\"string\"}]}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"GlobalMaxActiveAppsSet\",\"inputs\":[{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MaxActiveAppsSet\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"limit\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferCancelled\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"currentOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"cancelledOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferProposed\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"currentOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"proposedOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpgradeConfirmed\",\"inputs\":[{\"name\":\"app\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractIApp\"},{\"name\":\"pendingReleaseBlockNumber\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccountHasActiveApps\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppAlreadyExists\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AppDoesNotExist\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"GlobalMaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAppStatus\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidPermissions\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReleaseMetadataURI\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidShortString\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidTeamRole\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MaxActiveAppsExceeded\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MoreThanOneArtifact\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoPendingUpgrade\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotCreator\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotPendingOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SignatureExpired\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"StringTooLong\",\"inputs\":[{\"name\":\"str\",\"type\":\"string\",\"internalType\":\"string\"}]}]", ID: "AppController", } @@ -97,9 +97,9 @@ func (c *AppController) Instance(backend bind.ContractBackend, addr common.Addre // PackConstructor is the Go binding used to pack the parameters required for // contract deployment. // -// Solidity: constructor(string _version, address _permissionController, address _releaseManager, address _computeAVSRegistrar, address _computeOperator, address _appBeacon) returns() -func (appController *AppController) PackConstructor(_version string, _permissionController common.Address, _releaseManager common.Address, _computeAVSRegistrar common.Address, _computeOperator common.Address, _appBeacon common.Address) []byte { - enc, err := appController.abi.Pack("", _version, _permissionController, _releaseManager, _computeAVSRegistrar, _computeOperator, _appBeacon) +// Solidity: constructor(string _version, address _permissionController, address _releaseManager, address _computeAVSRegistrar, address _computeOperator, address _appBeacon, address _appAuthority) returns() +func (appController *AppController) PackConstructor(_version string, _permissionController common.Address, _releaseManager common.Address, _computeAVSRegistrar common.Address, _computeOperator common.Address, _appBeacon common.Address, _appAuthority common.Address) []byte { + enc, err := appController.abi.Pack("", _version, _permissionController, _releaseManager, _computeAVSRegistrar, _computeOperator, _appBeacon, _appAuthority) if err != nil { panic(err) } @@ -141,6 +141,63 @@ func (appController *AppController) UnpackAPIPERMISSIONTYPEHASH(data []byte) ([3 return out0, nil } +// PackAcceptOwnership is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x51710e45. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function acceptOwnership(address app) returns() +func (appController *AppController) PackAcceptOwnership(app common.Address) []byte { + enc, err := appController.abi.Pack("acceptOwnership", app) + if err != nil { + panic(err) + } + return enc +} + +// TryPackAcceptOwnership is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x51710e45. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function acceptOwnership(address app) returns() +func (appController *AppController) TryPackAcceptOwnership(app common.Address) ([]byte, error) { + return appController.abi.Pack("acceptOwnership", app) +} + +// PackAppAuthority is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x8029bbca. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function appAuthority() view returns(address) +func (appController *AppController) PackAppAuthority() []byte { + enc, err := appController.abi.Pack("appAuthority") + if err != nil { + panic(err) + } + return enc +} + +// TryPackAppAuthority is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x8029bbca. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function appAuthority() view returns(address) +func (appController *AppController) TryPackAppAuthority() ([]byte, error) { + return appController.abi.Pack("appAuthority") +} + +// UnpackAppAuthority is the Go binding that unpacks the parameters returned +// from invoking the contract method with ID 0x8029bbca. +// +// Solidity: function appAuthority() view returns(address) +func (appController *AppController) UnpackAppAuthority(data []byte) (common.Address, error) { + out, err := appController.abi.Unpack("appAuthority", data) + if err != nil { + return *new(common.Address), err + } + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + return out0, nil +} + // PackAppBeacon is the Go binding used to pack the parameters required for calling // the contract method with ID 0x8a52d0b5. This method will panic if any // invalid/nil inputs are passed. @@ -246,6 +303,63 @@ func (appController *AppController) UnpackCalculateAppId(data []byte) (common.Ad return out0, nil } +// PackCanCall is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x9614801b. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function canCall(address caller, bytes data) view returns(bool) +func (appController *AppController) PackCanCall(caller common.Address, data []byte) []byte { + enc, err := appController.abi.Pack("canCall", caller, data) + if err != nil { + panic(err) + } + return enc +} + +// TryPackCanCall is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x9614801b. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function canCall(address caller, bytes data) view returns(bool) +func (appController *AppController) TryPackCanCall(caller common.Address, data []byte) ([]byte, error) { + return appController.abi.Pack("canCall", caller, data) +} + +// UnpackCanCall is the Go binding that unpacks the parameters returned +// from invoking the contract method with ID 0x9614801b. +// +// Solidity: function canCall(address caller, bytes data) view returns(bool) +func (appController *AppController) UnpackCanCall(data []byte) (bool, error) { + out, err := appController.abi.Unpack("canCall", data) + if err != nil { + return *new(bool), err + } + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + return out0, nil +} + +// PackCancelOwnershipTransfer is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x7b37e561. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function cancelOwnershipTransfer(address app) returns() +func (appController *AppController) PackCancelOwnershipTransfer(app common.Address) []byte { + enc, err := appController.abi.Pack("cancelOwnershipTransfer", app) + if err != nil { + panic(err) + } + return enc +} + +// TryPackCancelOwnershipTransfer is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x7b37e561. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function cancelOwnershipTransfer(address app) returns() +func (appController *AppController) TryPackCancelOwnershipTransfer(app common.Address) ([]byte, error) { + return appController.abi.Pack("cancelOwnershipTransfer", app) +} + // PackComputeAVSRegistrar is the Go binding used to pack the parameters required for calling // the contract method with ID 0xef6d92c6. This method will panic if any // invalid/nil inputs are passed. @@ -934,6 +1048,41 @@ func (appController *AppController) UnpackGetMaxActiveAppsPerUser(data []byte) ( return out0, nil } +// PackGetPendingOwner is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x66f6a5ed. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function getPendingOwner(address app) view returns(address) +func (appController *AppController) PackGetPendingOwner(app common.Address) []byte { + enc, err := appController.abi.Pack("getPendingOwner", app) + if err != nil { + panic(err) + } + return enc +} + +// TryPackGetPendingOwner is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x66f6a5ed. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function getPendingOwner(address app) view returns(address) +func (appController *AppController) TryPackGetPendingOwner(app common.Address) ([]byte, error) { + return appController.abi.Pack("getPendingOwner", app) +} + +// UnpackGetPendingOwner is the Go binding that unpacks the parameters returned +// from invoking the contract method with ID 0x66f6a5ed. +// +// Solidity: function getPendingOwner(address app) view returns(address) +func (appController *AppController) UnpackGetPendingOwner(data []byte) (common.Address, error) { + out, err := appController.abi.Unpack("getPendingOwner", data) + if err != nil { + return *new(common.Address), err + } + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + return out0, nil +} + // PackGlobalActiveAppCount is the Go binding used to pack the parameters required for calling // the contract method with ID 0xa8aa2bd3. This method will panic if any // invalid/nil inputs are passed. @@ -1026,6 +1175,28 @@ func (appController *AppController) UnpackMaxGlobalActiveApps(data []byte) (uint return out0, nil } +// PackMigrateAppsToAppAuthority is the Go binding used to pack the parameters required for calling +// the contract method with ID 0xa6a062cd. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function migrateAppsToAppAuthority(address[] apps) returns() +func (appController *AppController) PackMigrateAppsToAppAuthority(apps []common.Address) []byte { + enc, err := appController.abi.Pack("migrateAppsToAppAuthority", apps) + if err != nil { + panic(err) + } + return enc +} + +// TryPackMigrateAppsToAppAuthority is the Go binding used to pack the parameters required for calling +// the contract method with ID 0xa6a062cd. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function migrateAppsToAppAuthority(address[] apps) returns() +func (appController *AppController) TryPackMigrateAppsToAppAuthority(apps []common.Address) ([]byte, error) { + return appController.abi.Pack("migrateAppsToAppAuthority", apps) +} + // PackPermissionController is the Go binding used to pack the parameters required for calling // the contract method with ID 0x4657e26a. This method will panic if any // invalid/nil inputs are passed. @@ -1184,6 +1355,41 @@ func (appController *AppController) TryPackStopApp(app common.Address) ([]byte, return appController.abi.Pack("stopApp", app) } +// PackSupportsInterface is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x01ffc9a7. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function supportsInterface(bytes4 interfaceId) pure returns(bool) +func (appController *AppController) PackSupportsInterface(interfaceId [4]byte) []byte { + enc, err := appController.abi.Pack("supportsInterface", interfaceId) + if err != nil { + panic(err) + } + return enc +} + +// TryPackSupportsInterface is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x01ffc9a7. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function supportsInterface(bytes4 interfaceId) pure returns(bool) +func (appController *AppController) TryPackSupportsInterface(interfaceId [4]byte) ([]byte, error) { + return appController.abi.Pack("supportsInterface", interfaceId) +} + +// UnpackSupportsInterface is the Go binding that unpacks the parameters returned +// from invoking the contract method with ID 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) pure returns(bool) +func (appController *AppController) UnpackSupportsInterface(data []byte) (bool, error) { + out, err := appController.abi.Unpack("supportsInterface", data) + if err != nil { + return *new(bool), err + } + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + return out0, nil +} + // PackSuspend is the Go binding used to pack the parameters required for calling // the contract method with ID 0xcb1e6ff7. This method will panic if any // invalid/nil inputs are passed. @@ -1250,6 +1456,28 @@ func (appController *AppController) TryPackTerminateAppByAdmin(app common.Addres return appController.abi.Pack("terminateAppByAdmin", app) } +// PackTransferOwnership is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x6d435421. This method will panic if any +// invalid/nil inputs are passed. +// +// Solidity: function transferOwnership(address app, address newOwner) returns() +func (appController *AppController) PackTransferOwnership(app common.Address, newOwner common.Address) []byte { + enc, err := appController.abi.Pack("transferOwnership", app, newOwner) + if err != nil { + panic(err) + } + return enc +} + +// TryPackTransferOwnership is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x6d435421. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function transferOwnership(address app, address newOwner) returns() +func (appController *AppController) TryPackTransferOwnership(app common.Address, newOwner common.Address) ([]byte, error) { + return appController.abi.Pack("transferOwnership", app, newOwner) +} + // PackUpdateAppMetadataURI is the Go binding used to pack the parameters required for calling // the contract method with ID 0x65aa9a65. This method will panic if any // invalid/nil inputs are passed. @@ -1427,6 +1655,49 @@ func (appController *AppController) UnpackAppMetadataURIUpdatedEvent(log *types. return out, nil } +// AppControllerAppOwnershipTransferred represents a AppOwnershipTransferred event raised by the AppController contract. +type AppControllerAppOwnershipTransferred struct { + App common.Address + PreviousOwner common.Address + NewOwner common.Address + Raw *types.Log // Blockchain specific contextual infos +} + +const AppControllerAppOwnershipTransferredEventName = "AppOwnershipTransferred" + +// ContractEventName returns the user-defined event name. +func (AppControllerAppOwnershipTransferred) ContractEventName() string { + return AppControllerAppOwnershipTransferredEventName +} + +// UnpackAppOwnershipTransferredEvent is the Go binding that unpacks the event data emitted +// by contract. +// +// Solidity: event AppOwnershipTransferred(address indexed app, address indexed previousOwner, address indexed newOwner) +func (appController *AppController) UnpackAppOwnershipTransferredEvent(log *types.Log) (*AppControllerAppOwnershipTransferred, error) { + event := "AppOwnershipTransferred" + if len(log.Topics) == 0 || log.Topics[0] != appController.abi.Events[event].ID { + return nil, errors.New("event signature mismatch") + } + out := new(AppControllerAppOwnershipTransferred) + if len(log.Data) > 0 { + if err := appController.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return nil, err + } + } + var indexed abi.Arguments + for _, arg := range appController.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + if err := abi.ParseTopics(out, indexed, log.Topics[1:]); err != nil { + return nil, err + } + out.Raw = log + return out, nil +} + // AppControllerAppStarted represents a AppStarted event raised by the AppController contract. type AppControllerAppStarted struct { App common.Address @@ -1799,6 +2070,92 @@ func (appController *AppController) UnpackMaxActiveAppsSetEvent(log *types.Log) return out, nil } +// AppControllerOwnershipTransferCancelled represents a OwnershipTransferCancelled event raised by the AppController contract. +type AppControllerOwnershipTransferCancelled struct { + App common.Address + CurrentOwner common.Address + CancelledOwner common.Address + Raw *types.Log // Blockchain specific contextual infos +} + +const AppControllerOwnershipTransferCancelledEventName = "OwnershipTransferCancelled" + +// ContractEventName returns the user-defined event name. +func (AppControllerOwnershipTransferCancelled) ContractEventName() string { + return AppControllerOwnershipTransferCancelledEventName +} + +// UnpackOwnershipTransferCancelledEvent is the Go binding that unpacks the event data emitted +// by contract. +// +// Solidity: event OwnershipTransferCancelled(address indexed app, address indexed currentOwner, address indexed cancelledOwner) +func (appController *AppController) UnpackOwnershipTransferCancelledEvent(log *types.Log) (*AppControllerOwnershipTransferCancelled, error) { + event := "OwnershipTransferCancelled" + if len(log.Topics) == 0 || log.Topics[0] != appController.abi.Events[event].ID { + return nil, errors.New("event signature mismatch") + } + out := new(AppControllerOwnershipTransferCancelled) + if len(log.Data) > 0 { + if err := appController.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return nil, err + } + } + var indexed abi.Arguments + for _, arg := range appController.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + if err := abi.ParseTopics(out, indexed, log.Topics[1:]); err != nil { + return nil, err + } + out.Raw = log + return out, nil +} + +// AppControllerOwnershipTransferProposed represents a OwnershipTransferProposed event raised by the AppController contract. +type AppControllerOwnershipTransferProposed struct { + App common.Address + CurrentOwner common.Address + ProposedOwner common.Address + Raw *types.Log // Blockchain specific contextual infos +} + +const AppControllerOwnershipTransferProposedEventName = "OwnershipTransferProposed" + +// ContractEventName returns the user-defined event name. +func (AppControllerOwnershipTransferProposed) ContractEventName() string { + return AppControllerOwnershipTransferProposedEventName +} + +// UnpackOwnershipTransferProposedEvent is the Go binding that unpacks the event data emitted +// by contract. +// +// Solidity: event OwnershipTransferProposed(address indexed app, address indexed currentOwner, address indexed proposedOwner) +func (appController *AppController) UnpackOwnershipTransferProposedEvent(log *types.Log) (*AppControllerOwnershipTransferProposed, error) { + event := "OwnershipTransferProposed" + if len(log.Topics) == 0 || log.Topics[0] != appController.abi.Events[event].ID { + return nil, errors.New("event signature mismatch") + } + out := new(AppControllerOwnershipTransferProposed) + if len(log.Data) > 0 { + if err := appController.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return nil, err + } + } + var indexed abi.Arguments + for _, arg := range appController.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + if err := abi.ParseTopics(out, indexed, log.Topics[1:]); err != nil { + return nil, err + } + out.Raw = log + return out, nil +} + // AppControllerUpgradeConfirmed represents a UpgradeConfirmed event raised by the AppController contract. type AppControllerUpgradeConfirmed struct { App common.Address @@ -1871,6 +2228,9 @@ func (appController *AppController) UnpackError(raw []byte) (any, error) { if bytes.Equal(raw[:4], appController.abi.Errors["InvalidSignature"].ID.Bytes()[:4]) { return appController.UnpackInvalidSignatureError(raw[4:]) } + if bytes.Equal(raw[:4], appController.abi.Errors["InvalidTeamRole"].ID.Bytes()[:4]) { + return appController.UnpackInvalidTeamRoleError(raw[4:]) + } if bytes.Equal(raw[:4], appController.abi.Errors["MaxActiveAppsExceeded"].ID.Bytes()[:4]) { return appController.UnpackMaxActiveAppsExceededError(raw[4:]) } @@ -1880,6 +2240,12 @@ func (appController *AppController) UnpackError(raw []byte) (any, error) { if bytes.Equal(raw[:4], appController.abi.Errors["NoPendingUpgrade"].ID.Bytes()[:4]) { return appController.UnpackNoPendingUpgradeError(raw[4:]) } + if bytes.Equal(raw[:4], appController.abi.Errors["NotCreator"].ID.Bytes()[:4]) { + return appController.UnpackNotCreatorError(raw[4:]) + } + if bytes.Equal(raw[:4], appController.abi.Errors["NotPendingOwner"].ID.Bytes()[:4]) { + return appController.UnpackNotPendingOwnerError(raw[4:]) + } if bytes.Equal(raw[:4], appController.abi.Errors["SignatureExpired"].ID.Bytes()[:4]) { return appController.UnpackSignatureExpiredError(raw[4:]) } @@ -2096,6 +2462,29 @@ func (appController *AppController) UnpackInvalidSignatureError(raw []byte) (*Ap return out, nil } +// AppControllerInvalidTeamRole represents a InvalidTeamRole error raised by the AppController contract. +type AppControllerInvalidTeamRole struct { +} + +// ErrorID returns the hash of canonical representation of the error's signature. +// +// Solidity: error InvalidTeamRole() +func AppControllerInvalidTeamRoleErrorID() common.Hash { + return common.HexToHash("0x8a008e174a035e142dfc4a2bb0f04b943d719f4126df368adee849db4d07a5ac") +} + +// UnpackInvalidTeamRoleError is the Go binding used to decode the provided +// error data into the corresponding Go error struct. +// +// Solidity: error InvalidTeamRole() +func (appController *AppController) UnpackInvalidTeamRoleError(raw []byte) (*AppControllerInvalidTeamRole, error) { + out := new(AppControllerInvalidTeamRole) + if err := appController.abi.UnpackIntoInterface(out, "InvalidTeamRole", raw); err != nil { + return nil, err + } + return out, nil +} + // AppControllerMaxActiveAppsExceeded represents a MaxActiveAppsExceeded error raised by the AppController contract. type AppControllerMaxActiveAppsExceeded struct { } @@ -2165,6 +2554,52 @@ func (appController *AppController) UnpackNoPendingUpgradeError(raw []byte) (*Ap return out, nil } +// AppControllerNotCreator represents a NotCreator error raised by the AppController contract. +type AppControllerNotCreator struct { +} + +// ErrorID returns the hash of canonical representation of the error's signature. +// +// Solidity: error NotCreator() +func AppControllerNotCreatorErrorID() common.Hash { + return common.HexToHash("0x93687c0be1f4d8f5b3c5ab55b81e360246e6c249b742b9b16b45d7e97dfe5645") +} + +// UnpackNotCreatorError is the Go binding used to decode the provided +// error data into the corresponding Go error struct. +// +// Solidity: error NotCreator() +func (appController *AppController) UnpackNotCreatorError(raw []byte) (*AppControllerNotCreator, error) { + out := new(AppControllerNotCreator) + if err := appController.abi.UnpackIntoInterface(out, "NotCreator", raw); err != nil { + return nil, err + } + return out, nil +} + +// AppControllerNotPendingOwner represents a NotPendingOwner error raised by the AppController contract. +type AppControllerNotPendingOwner struct { +} + +// ErrorID returns the hash of canonical representation of the error's signature. +// +// Solidity: error NotPendingOwner() +func AppControllerNotPendingOwnerErrorID() common.Hash { + return common.HexToHash("0x1853971cac2844d8fedc34d0d65cff78483e8a606ad8448df68a3af766d8a29d") +} + +// UnpackNotPendingOwnerError is the Go binding used to decode the provided +// error data into the corresponding Go error struct. +// +// Solidity: error NotPendingOwner() +func (appController *AppController) UnpackNotPendingOwnerError(raw []byte) (*AppControllerNotPendingOwner, error) { + out := new(AppControllerNotPendingOwner) + if err := appController.abi.UnpackIntoInterface(out, "NotPendingOwner", raw); err != nil { + return nil, err + } + return out, nil +} + // AppControllerSignatureExpired represents a SignatureExpired error raised by the AppController contract. type AppControllerSignatureExpired struct { } diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 8cb680e..01faa6b 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -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 { @@ -39,6 +43,7 @@ contract Deploy is Parser { ComputeOperator computeOperator; AppController appController; ImageAllowlist imageAllowlist; + AppAuthority appAuthority; } function run(string memory environment) public { @@ -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()), @@ -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 @@ -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 }); } @@ -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"; diff --git a/script/Parser.s.sol b/script/Parser.s.sol index 982a879..4f774e0 100644 --- a/script/Parser.s.sol +++ b/script/Parser.s.sol @@ -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 { @@ -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) { @@ -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; diff --git a/script/releases/Env.sol b/script/releases/Env.sol index 1607e51..5f80986 100644 --- a/script/releases/Env.sol +++ b/script/releases/Env.sol @@ -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 *; @@ -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)); } @@ -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 */ @@ -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 */ diff --git a/script/releases/v1.0.4-init/1-deployContracts.s.sol b/script/releases/v1.0.4-init/1-deployContracts.s.sol index 88ac49e..b76af2e 100644 --- a/script/releases/v1.0.4-init/1-deployContracts.s.sol +++ b/script/releases/v1.0.4-init/1-deployContracts.s.sol @@ -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. @@ -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 diff --git a/script/releases/v1.1.1-app-suspension/1-deployAppControllerImpl.s.sol b/script/releases/v1.1.1-app-suspension/1-deployAppControllerImpl.s.sol index b231135..3108426 100644 --- a/script/releases/v1.1.1-app-suspension/1-deployAppControllerImpl.s.sol +++ b/script/releases/v1.1.1-app-suspension/1-deployAppControllerImpl.s.sol @@ -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 @@ -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 diff --git a/script/releases/v1.4.0-isolated-billing/1-deployAppControllerImpl.s.sol b/script/releases/v1.4.0-isolated-billing/1-deployAppControllerImpl.s.sol index 8fd0fe1..67c3292 100644 --- a/script/releases/v1.4.0-isolated-billing/1-deployAppControllerImpl.s.sol +++ b/script/releases/v1.4.0-isolated-billing/1-deployAppControllerImpl.s.sol @@ -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 @@ -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 diff --git a/script/releases/v1.5.0-governance/1-deployGovernanceContracts.s.sol b/script/releases/v1.5.0-governance/1-deployGovernanceContracts.s.sol new file mode 100644 index 0000000..8b7b21c --- /dev/null +++ b/script/releases/v1.5.0-governance/1-deployGovernanceContracts.s.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {EOADeployer} from "zeus-templates/templates/EOADeployer.sol"; +import "../Env.sol"; + +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {AppController} from "../../../src/AppController.sol"; +import {SafeTimelockFactory} from "../../../src/factories/SafeTimelockFactory.sol"; +import {TimelockControllerImpl} from "../../../src/governance/TimelockControllerImpl.sol"; +import {AppAuthority} from "../../../src/governance/AppAuthority.sol"; +import {IAppAuthority} from "../../../src/interfaces/IAppAuthority.sol"; + +/** + * @title DeployGovernanceContracts + * @notice First step of the v1.5.0 release. Run by an EOA; deploys all new + * impls and non-upgrade proxies. The AppController proxy itself is + * NOT upgraded here — that's `2-upgradeAppController.s.sol`, run by + * the ops multisig. + * + * Deploys, in order: + * 1. `TimelockControllerImpl` — clone master for Timelocks created by the + * factory. Deployed directly, no proxy. + * 2. `SafeTimelockFactory` impl — references the Timelock impl above and + * the canonical Gnosis Safe infrastructure (pulled from zeus env). + * 3. `SafeTimelockFactory` proxy — `TransparentUpgradeableProxy` behind + * the existing protocol `ProxyAdmin`. + * 4. `AppAuthority` impl — consumer-bound to the existing AppController + * proxy address. The consumer immutable is how AppAuthority + * authenticates mutation calls from the upgraded AppController. + * 5. `AppAuthority` proxy — `TransparentUpgradeableProxy` behind the same + * `ProxyAdmin`. + * 6. New `AppController` impl — wired to the AppAuthority proxy. Critical + * ops delegate to AppAuthority for owner and role checks. + */ +contract DeployGovernanceContracts is EOADeployer { + using Env for *; + + function _runAsEOA() internal override { + vm.startBroadcast(); + + // 1. TimelockControllerImpl — no constructor args, immutable. + TimelockControllerImpl timelockImpl = new TimelockControllerImpl(); + deployContract({name: type(TimelockControllerImpl).name, deployedTo: address(timelockImpl)}); + + // 2. SafeTimelockFactory implementation. + SafeTimelockFactory safeTimelockFactoryImpl = new SafeTimelockFactory({ + _safeSingleton: Env.safeSingleton(), + _safeProxyFactory: Env.safeProxyFactory(), + _safeFallbackHandler: Env.safeFallbackHandler(), + _timelockImplementation: address(timelockImpl) + }); + deployImpl({name: type(SafeTimelockFactory).name, deployedTo: address(safeTimelockFactoryImpl)}); + + // 3. SafeTimelockFactory proxy (TransparentUpgradeableProxy). + TransparentUpgradeableProxy safeTimelockFactoryProxy = new TransparentUpgradeableProxy( + address(safeTimelockFactoryImpl), + address(Env.proxyAdmin()), + abi.encodeCall(SafeTimelockFactory.initialize, ()) + ); + deployProxy({name: type(SafeTimelockFactory).name, deployedTo: address(safeTimelockFactoryProxy)}); + + // 4. AppAuthority implementation — consumer-bound to the existing + // AppController proxy. AppAuthority's `consumer` immutable is the + // AppController proxy address; AppController at the proxy already + // exists (it's the v1.4.0 AppController we're about to upgrade). + AppAuthority appAuthorityImpl = new AppAuthority(address(Env.proxy.appController())); + deployImpl({name: type(AppAuthority).name, deployedTo: address(appAuthorityImpl)}); + + // 5. AppAuthority proxy. + TransparentUpgradeableProxy appAuthorityProxy = new TransparentUpgradeableProxy( + address(appAuthorityImpl), address(Env.proxyAdmin()), abi.encodeCall(AppAuthority.initialize, ()) + ); + deployProxy({name: type(AppAuthority).name, deployedTo: address(appAuthorityProxy)}); + + // 6. New AppController implementation — wired to the AppAuthority + // proxy. AppController no longer references SafeTimelockFactory; + // the factory is still deployed (steps 2–3) because users / + // tooling can use it to deploy attested Safes and Timelocks, but + // AppController's correctness no longer depends on it. + AppController newAppControllerImpl = new AppController({ + _version: Env.deployVersion(), + _permissionController: Env.permissionController(), + _releaseManager: Env.releaseManager(), + _computeAVSRegistrar: Env.proxy.computeAVSRegistrar(), + _computeOperator: Env.proxy.computeOperator(), + _appBeacon: Env.beacon.appBeacon(), + _appAuthority: IAppAuthority(address(appAuthorityProxy)) + }); + deployImpl({name: type(AppController).name, deployedTo: address(newAppControllerImpl)}); + + vm.stopBroadcast(); + } + + function testScript() public virtual { + runAsEOA(); + + _validateNewAddresses({afterUpgrade: false}); + _validateConstructors(); + } + + /// @dev Validate all phase-1 deployments are non-zero and wired correctly. + function _validateNewAddresses(bool afterUpgrade) internal view { + assertTrue(address(Env.timelockControllerImpl()) != address(0), "TimelockControllerImpl zero"); + assertTrue(address(Env.impl.safeTimelockFactory()) != address(0), "SafeTimelockFactory impl zero"); + assertTrue(address(Env.proxy.safeTimelockFactory()) != address(0), "SafeTimelockFactory proxy zero"); + assertTrue(address(Env.impl.appController()) != address(0), "AppController impl zero"); + + // Proxy points at the fresh impl. + assertEq( + _getProxyImpl(address(Env.proxy.safeTimelockFactory())), + address(Env.impl.safeTimelockFactory()), + "SafeTimelockFactory proxy -> impl mismatch" + ); + + // Only after phase 2 does the AppController proxy point at the new impl. + if (afterUpgrade) { + assertEq( + _getProxyImpl(address(Env.proxy.appController())), + address(Env.impl.appController()), + "AppController proxy -> impl mismatch" + ); + } + } + + /// @dev Cross-check immutables on the freshly deployed implementations. + function _validateConstructors() internal view { + SafeTimelockFactory factoryImpl = Env.impl.safeTimelockFactory(); + assertEq(factoryImpl.safeSingleton(), Env.safeSingleton(), "factory safeSingleton mismatch"); + assertEq(factoryImpl.safeProxyFactory(), Env.safeProxyFactory(), "factory safeProxyFactory mismatch"); + assertEq(factoryImpl.safeFallbackHandler(), Env.safeFallbackHandler(), "factory safeFallbackHandler mismatch"); + assertEq( + factoryImpl.timelockImplementation(), + address(Env.timelockControllerImpl()), + "factory timelockImplementation mismatch" + ); + + AppController appImpl = Env.impl.appController(); + assertEq(appImpl.version(), Env.deployVersion(), "AppController version mismatch"); + assertEq( + address(appImpl.permissionController()), + address(Env.permissionController()), + "permissionController mismatch" + ); + assertEq(address(appImpl.releaseManager()), address(Env.releaseManager()), "releaseManager mismatch"); + assertEq( + address(appImpl.computeAVSRegistrar()), + address(Env.proxy.computeAVSRegistrar()), + "computeAVSRegistrar mismatch" + ); + assertEq(address(appImpl.computeOperator()), address(Env.proxy.computeOperator()), "computeOperator mismatch"); + assertEq(address(appImpl.appBeacon()), address(Env.beacon.appBeacon()), "appBeacon mismatch"); + assertEq(address(appImpl.appAuthority()), address(Env.proxy.appAuthority()), "appAuthority mismatch"); + + AppAuthority authorityImpl = Env.impl.appAuthority(); + assertEq(authorityImpl.consumer(), address(Env.proxy.appController()), "AppAuthority consumer mismatch"); + } + + function _getProxyImpl(address proxy) internal view returns (address) { + return ProxyAdmin(Env.proxyAdmin()).getProxyImplementation(ITransparentUpgradeableProxy(proxy)); + } +} diff --git a/script/releases/v1.5.0-governance/2-upgradeAppController.s.sol b/script/releases/v1.5.0-governance/2-upgradeAppController.s.sol new file mode 100644 index 0000000..d608ff3 --- /dev/null +++ b/script/releases/v1.5.0-governance/2-upgradeAppController.s.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol"; +import "./1-deployGovernanceContracts.s.sol"; +import "../Env.sol"; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {IApp} from "../../../src/interfaces/IApp.sol"; +import {IAppController} from "../../../src/interfaces/IAppController.sol"; + +/** + * @title UpgradeAppController + * @notice Second step of the v1.5.0 release. Run by the ops multisig after + * the EOA-deployed impls from `1-deployGovernanceContracts.s.sol` + * are on-chain. + * + * Performs two actions atomically in a single multisig transaction: + * 1. `ProxyAdmin.upgrade` points AppController at the new impl. From + * this call on, AppController delegates auth to AppAuthority. + * 2. `migrateAppsToAppAuthority` seeds AppAuthority state for every + * pre-existing app: initializes each scope from the cached + * `creator`, seeds ADMIN from the app's legacy PermissionController + * admins. Paginated in batches to stay under the block gas limit. + * + * Atomicity matters: between steps 1 and 2, pre-existing apps have + * `appAuthority.scopeOwner(app) == address(0)`, so every owner-gated + * critical op would revert. Running both in one multisig tx closes + * that window entirely. + * + * The storage layout is append-only vs. v1.4.0 plus the + * `pendingReleaseBlockNumber` field added in v1.4.1 — existing apps + * keep their prior state (creator, operatorSetId, status, + * billingType, latestReleaseBlockNumber) with no rewrites required. + */ +contract UpgradeAppController is MultisigBuilder, DeployGovernanceContracts { + using Env for *; + + /// @notice Page size for the post-upgrade migration loop. Conservative + /// default that keeps the outer multisig tx well under block + /// gas limits even with dozens of admins per app. + uint256 internal constant MIGRATION_PAGE_SIZE = 50; + + function _runAsMultisig() internal override prank(Env.computeOpsMultisig()) { + // Step 1 — upgrade the AppController proxy to the new impl. + Env.proxyAdmin() + .upgrade( + ITransparentUpgradeableProxy(address(Env.proxy.appController())), address(Env.impl.appController()) + ); + + // Step 2 — seed AppAuthority state for every existing app. + // `getApps` is an unchanged v1.4.0 view; it enumerates the full + // `_allApps` set which the upgrade does not touch. Paginate to + // avoid one giant calldata/gas spike. + IAppController controller = IAppController(address(Env.proxy.appController())); + uint256 offset = 0; + while (true) { + (IApp[] memory apps,) = controller.getApps(offset, MIGRATION_PAGE_SIZE); + if (apps.length == 0) break; + controller.migrateAppsToAppAuthority(apps); + offset += apps.length; + if (apps.length < MIGRATION_PAGE_SIZE) break; + } + } + + function testScript() public virtual override { + runAsEOA(); + execute(); + _validateNewAddresses({afterUpgrade: true}); + _validateConstructors(); + } +} diff --git a/script/releases/v1.5.0-governance/upgrade.json b/script/releases/v1.5.0-governance/upgrade.json new file mode 100644 index 0000000..d738e45 --- /dev/null +++ b/script/releases/v1.5.0-governance/upgrade.json @@ -0,0 +1,9 @@ +{ + "name": "governance", + "from": "1.4.0", + "to": "1.5.0", + "phases": [ + { "type": "eoa", "filename": "1-deployGovernanceContracts.s.sol" }, + { "type": "multisig", "filename": "2-upgradeAppController.s.sol" } + ] +} diff --git a/src/AppController.sol b/src/AppController.sol index 89c53b6..2bc22d7 100644 --- a/src/AppController.sol +++ b/src/AppController.sol @@ -2,9 +2,8 @@ pragma solidity ^0.8.27; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import {SignatureUtilsMixin} from "@eigenlayer-contracts/src/contracts/mixins/SignatureUtilsMixin.sol"; import {IPermissionController} from "@eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; import {PermissionControllerMixin} from "@eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol"; @@ -17,15 +16,46 @@ import {IComputeAVSRegistrar} from "./interfaces/IComputeAVSRegistrar.sol"; import {IComputeOperator} from "./interfaces/IComputeOperator.sol"; import {AppControllerStorage} from "./storage/AppControllerStorage.sol"; import {IAppController} from "./interfaces/IAppController.sol"; -import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +import {IAppAuthority} from "./interfaces/IAppAuthority.sol"; import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; import {IApp} from "./interfaces/IApp.sol"; - -contract AppController is Initializable, SignatureUtilsMixin, PermissionControllerMixin, AppControllerStorage { +import {ICallValidator} from "./interfaces/ICallValidator.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +contract AppController is + Initializable, + SignatureUtilsMixin, + PermissionControllerMixin, + AppControllerStorage, + ICallValidator +{ using EnumerableSet for EnumerableSet.AddressSet; /// MODIFIERS + /// @notice Modifier to require the caller hold `role` on `app` (or ADMIN, + /// which is a superset). Reverts with InvalidTeamRole otherwise. + /// @dev Role state lives in AppAuthority; AppController just queries it. + modifier onlyRoleOrAdmin(IApp app, IAppAuthority.Role role) { + if (!appAuthority.hasRoleOrAdmin(app, role, msg.sender)) revert InvalidTeamRole(); + _; + } + + /// @notice Modifier to require the caller is an ADMIN on `app`. + modifier onlyAdmin(IApp app) { + if (!appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, msg.sender)) revert InvalidTeamRole(); + _; + } + + /// @notice Modifier to require the caller is the current owner of `app`. + /// Used on critical ops — ADMIN alone is not enough. Ownership + /// lives in AppAuthority, mirrored into `_appConfigs[app].creator` + /// for billing, events, and ABI stability. + modifier onlyCreator(IApp app) { + if (!appAuthority.isScopeOwner(app, msg.sender)) revert NotCreator(); + _; + } + /// @notice Modifier to ensure app exists modifier appExists(IApp app) { require(_exists(_appConfigs[app].status), InvalidAppStatus()); @@ -51,11 +81,12 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll IReleaseManager _releaseManager, IComputeAVSRegistrar _computeAVSRegistrar, IComputeOperator _computeOperator, - IBeacon _appBeacon + IBeacon _appBeacon, + IAppAuthority _appAuthority ) SignatureUtilsMixin(_version) PermissionControllerMixin(_permissionController) - AppControllerStorage(_releaseManager, _computeOperator, _computeAVSRegistrar, _appBeacon) + AppControllerStorage(_releaseManager, _computeOperator, _computeAVSRegistrar, _appBeacon, _appAuthority) { _disableInitializers(); } @@ -113,13 +144,76 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll /// @inheritdoc IAppController function upgradeApp(IApp app, Release calldata release) external - checkCanCall(address(app)) + onlyCreator(app) appIsActive(app) returns (uint256) { + // Critical op: only the current owner may call. If the owner is a + // Timelock, this forces the call through schedule → execute; if it's + // a Safe, through the multisig; if it's an EOA, directly. The + // governance mechanism is whatever the owner contract is — AppController + // doesn't need to classify it. return _upgradeApp(app, release); } + /// @inheritdoc IAppController + function transferOwnership(IApp app, address newOwner) external onlyCreator(app) appExists(app) { + require(newOwner != address(0), InvalidPermissions()); + + AppConfigStorage storage config = _appConfigs[app]; + address previousPending = config.pendingOwner; + config.pendingOwner = newOwner; + + // Surface the supersession explicitly so off-chain consumers can + // invalidate any UI state tied to the previous proposal. + if (previousPending != address(0) && previousPending != newOwner) { + emit OwnershipTransferCancelled(app, msg.sender, previousPending); + } + emit OwnershipTransferProposed(app, msg.sender, newOwner); + } + + /// @inheritdoc IAppController + function acceptOwnership(IApp app) external appExists(app) { + AppConfigStorage storage config = _appConfigs[app]; + if (msg.sender != config.pendingOwner) revert NotPendingOwner(); + + address previousOwner = config.creator; + + // For DEFAULT-billed active apps, the counter is about to migrate + // to the receiver — they must have capacity. Matches `createApp` + // semantics exactly: strictly-less-than check against both the + // per-user cap and the global cap (global is already at capacity + // in this scenario means we're transferring from one user to + // another, net zero, so it can never fail — but we still enforce + // the user-level cap). + if (config.billingType == BillingType.DEFAULT && _isActive(config.status)) { + UserConfig storage receiverCfg = _userConfigs[msg.sender]; + require(receiverCfg.activeAppCount < receiverCfg.maxActiveApps, MaxActiveAppsExceeded()); + } + + // Rotate ownership + ADMIN atomically in AppAuthority. + appAuthority.transferScopeOwnership(app, msg.sender); + + config.creator = msg.sender; + delete config.pendingOwner; + + if (config.billingType == BillingType.DEFAULT && _isActive(config.status)) { + _userConfigs[previousOwner].activeAppCount--; + _userConfigs[msg.sender].activeAppCount++; + } + + emit AppOwnershipTransferred(app, previousOwner, msg.sender); + } + + /// @inheritdoc IAppController + function cancelOwnershipTransfer(IApp app) external onlyCreator(app) appExists(app) { + AppConfigStorage storage config = _appConfigs[app]; + address pending = config.pendingOwner; + if (pending == address(0)) return; + delete config.pendingOwner; + emit OwnershipTransferCancelled(app, msg.sender, pending); + } + /// @inheritdoc IAppController function confirmUpgrade(IApp app) external checkCanCall(address(this)) appIsActive(app) { AppConfigStorage storage config = _appConfigs[app]; @@ -133,19 +227,19 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll /// @inheritdoc IAppController function updateAppMetadataURI(IApp app, string calldata metadataURI) external - checkCanCall(address(app)) + onlyRoleOrAdmin(app, IAppAuthority.Role.DEVELOPER) appExists(app) { emit AppMetadataURIUpdated(app, metadataURI); } /// @inheritdoc IAppController - function startApp(IApp app) external checkCanCall(address(app)) appExists(app) { + function startApp(IApp app) external onlyAdmin(app) appExists(app) { _startApp(app); } /// @inheritdoc IAppController - function stopApp(IApp app) external checkCanCall(address(app)) { + function stopApp(IApp app) external onlyRoleOrAdmin(app, IAppAuthority.Role.PAUSER) { AppConfigStorage storage config = _appConfigs[app]; require(config.status == AppStatus.STARTED, InvalidAppStatus()); config.status = AppStatus.STOPPED; @@ -154,12 +248,21 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll } /// @inheritdoc IAppController - function terminateApp(IApp app) external checkCanCall(address(app)) appIsActive(app) { + function terminateApp(IApp app) external onlyCreator(app) appIsActive(app) { + // Critical op: only the current owner (`creator`) may terminate. + // Termination is irreversible; a co-ADMIN cannot trigger it. _terminateApp(app); } /// @inheritdoc IAppController function terminateAppByAdmin(IApp app) external checkCanCall(address(this)) appIsActive(app) { + // Protocol admin (UAM-gated) may terminate any app. This is protocol + // policy that sits above app-level governance — abuse, legal, or + // platform-level concerns require a uniform lever regardless of what + // the user chose as the app's owner. If the protocol wants its own + // termination actions to be delay-gated, the protocol's UAM admin + // multisig should itself be behind a Timelock — that's an operational + // decision, not an AppController concern. _terminateApp(app); emit AppTerminatedByAdmin(app); } @@ -191,6 +294,47 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll _setMaxActiveAppsPerUser(account, 0); } + /// TEAM ROLE MANAGEMENT — delegated to AppAuthority + /// + /// Role management (grant / revoke / renounce / hasRole) lives in + /// AppAuthority directly. Clients call `appAuthority.grantRole(app, ...)` + /// etc. AppController does not re-expose those entry points; doing so + /// would add an extra hop with no auth delta and double the audit + /// surface. See IAppAuthority for the role API. + + /// @inheritdoc IAppController + function migrateAppsToAppAuthority(IApp[] calldata apps) external checkCanCall(address(this)) { + // For every pre-v1.5.0 app: + // (1) If AppAuthority has no owner recorded, initialize the scope + // with AppController's cached `creator` field. + // (2) Seed AppAuthority's ADMIN role with the app's + // PermissionController admins. ADMIN is an operational-only + // role in this model — it does NOT confer upgrade / transfer / + // terminate power, so migrated admins carry no critical-op + // exposure; the owner remains the only critical-op authority. + // Idempotent: re-running is safe because initializeScope reverts on + // reinit (handled), and grantRole is set-semantics. + uint256 n = apps.length; + IApp[] memory scopes = new IApp[](n); + address[][] memory allAdmins = new address[][](n); + + for (uint256 i = 0; i < n; i++) { + IApp app = apps[i]; + address cachedOwner = _appConfigs[app].creator; + + // Initialize the scope if not already initialized. scopeOwner + // returns address(0) for uninitialized scopes. + if (appAuthority.scopeOwner(app) == address(0) && cachedOwner != address(0)) { + appAuthority.initializeScope(app, cachedOwner); + } + + scopes[i] = app; + allAdmins[i] = permissionController.getAdmins(address(app)); + } + + appAuthority.migrateAdmins(scopes, allAdmins); + } + /// INTERNAL FUNCTIONS /** @@ -213,6 +357,10 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll _appConfigs[app].billingType = _billingType; _allApps.add(address(app)); + // Register the scope + seed creator as ADMIN in AppAuthority. All + // subsequent auth checks consult AppAuthority directly. + appAuthority.initializeScope(app, msg.sender); + emit AppCreated(msg.sender, app, operatorSetId); // Upgrade the app with the initial release and auto-confirm it @@ -411,13 +559,18 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll } /** - * @notice Check if address is developer of app - * @param app The app to check - * @param developer The developer to check - * @return True if the developer is the developer of the app + * @notice Check whether `developer` has app-level authority on `app`. + * @dev Used as a predicate by `getAppsByDeveloper` to enumerate apps a + * given address can operate on. After the v1.5.0 migration, app- + * level authority is AppAuthority's ADMIN role (operational + * superset, held by the scope owner and anyone they've granted). + * Pre-migration fallback via PermissionController is intentionally + * NOT consulted here — addresses that existed only as + * PermissionController admins get reflected in AppAuthority's ADMIN + * set via `migrateAppsToAppAuthority`. */ function _isDeveloper(IApp app, address developer) private view returns (bool) { - return permissionController.isAdmin(address(app), developer); + return appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, developer); } /** @@ -472,6 +625,11 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll return _appConfigs[app].creator; } + /// @inheritdoc IAppController + function getPendingOwner(IApp app) external view returns (address) { + return _appConfigs[app].pendingOwner; + } + /** * @notice Resolves the billing account for an app * @param app The app instance to resolve billing for @@ -487,6 +645,42 @@ contract AppController is Initializable, SignatureUtilsMixin, PermissionControll return _appConfigs[app].billingType; } + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(ICallValidator).interfaceId || interfaceId == type(IERC165).interfaceId; + } + + /// @inheritdoc ICallValidator + /// @dev Schedule-time validation hook consumed by TimelockControllerImpl. + /// AppController is a common Timelock target; a scheduled critical + /// op from a non-owner is doomed at execute time. Reject at + /// schedule time so the delay window isn't burned on a doomed op. + /// + /// Role-management selectors are NOT on this contract anymore — + /// they live on AppAuthority. Scheduling role ops targets + /// AppAuthority directly, which has its own ICallValidator surface + /// (if wired) to validate there. + function canCall(address caller, bytes calldata data) external view returns (bool) { + if (data.length < 36) return true; + bytes4 selector = bytes4(data[:4]); + + // Owner-gated critical ops. transferOwnership is a proposal under + // the two-step model — still owner-gated at the proposer side. + // cancelOwnershipTransfer is also owner-gated. acceptOwnership is + // intentionally NOT listed: it's gated on the pending-owner field, + // which is per-app dynamic state; we let the runtime check handle + // it rather than duplicate the lookup here. + if ( + selector == this.upgradeApp.selector || selector == this.terminateApp.selector + || selector == this.transferOwnership.selector || selector == this.cancelOwnershipTransfer.selector + ) { + IApp app = abi.decode(data[4:36], (IApp)); + if (!appAuthority.isScopeOwner(app, caller)) return false; + } + + return true; + } + /// @inheritdoc IAppController function getAppOperatorSetId(IApp app) external view returns (uint32) { return _appConfigs[app].operatorSetId; diff --git a/src/factories/SafeTimelockFactory.sol b/src/factories/SafeTimelockFactory.sol new file mode 100644 index 0000000..d92ce95 --- /dev/null +++ b/src/factories/SafeTimelockFactory.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {SafeTimelockFactoryStorage} from "../storage/SafeTimelockFactoryStorage.sol"; +import {ISafeTimelockFactory} from "../interfaces/ISafeTimelockFactory.sol"; +import {ISafe} from "../interfaces/ISafe.sol"; +import {ISafeProxyFactory} from "../interfaces/ISafeProxyFactory.sol"; +import {TimelockControllerImpl} from "../governance/TimelockControllerImpl.sol"; + +/** + * @title SafeTimelockFactory + * @notice Factory for deploying verified Gnosis Safes and TimelockControllers + * @dev Deployed entities can be verified as "official" via isSafe() and isTimelock() + */ +contract SafeTimelockFactory is Initializable, SafeTimelockFactoryStorage { + using EnumerableSet for EnumerableSet.AddressSet; + + constructor( + address _safeSingleton, + address _safeProxyFactory, + address _safeFallbackHandler, + address _timelockImplementation + ) SafeTimelockFactoryStorage(_safeSingleton, _safeProxyFactory, _safeFallbackHandler, _timelockImplementation) { + _disableInitializers(); + } + + function initialize() external initializer {} + + /// EXTERNAL FUNCTIONS + + /// @inheritdoc ISafeTimelockFactory + function deploySafe(SafeConfig calldata config, bytes32 salt) external returns (address safe) { + safe = _deploySafe(config, salt); + _safes.add(safe); + _safesByDeployer[msg.sender].add(safe); + emit SafeDeployed(msg.sender, safe, config.owners, config.threshold, salt); + } + + /// @inheritdoc ISafeTimelockFactory + function deployTimelock(TimelockConfig calldata config, bytes32 salt) external returns (address timelock) { + _validateTimelockConfig(config); + timelock = _deployTimelock(config, salt); + _timelocks.add(timelock); + _timelocksByDeployer[msg.sender].add(timelock); + emit TimelockDeployed(msg.sender, timelock, config.minDelay, config.proposers, config.executors, salt); + } + + /// VIEW FUNCTIONS + + /// @inheritdoc ISafeTimelockFactory + function isSafe(address safe) external view returns (bool) { + return _safes.contains(safe); + } + + /// @inheritdoc ISafeTimelockFactory + function isTimelock(address timelock) external view returns (bool) { + return _timelocks.contains(timelock); + } + + /// @inheritdoc ISafeTimelockFactory + function getTimelocksByDeployer(address deployer) external view returns (address[] memory) { + return _timelocksByDeployer[deployer].values(); + } + + /// @inheritdoc ISafeTimelockFactory + function getSafesByDeployer(address deployer) external view returns (address[] memory) { + return _safesByDeployer[deployer].values(); + } + + /// @inheritdoc ISafeTimelockFactory + function calculateSafeAddress(address deployer, SafeConfig calldata config, bytes32 salt) + external + view + returns (address) + { + bytes memory initializer = _encodeSafeInitializer(config); + uint256 saltNonce = uint256(_deriveSalt(deployer, salt)); + bytes32 creationSalt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); + bytes memory proxyCreationCode = ISafeProxyFactory(safeProxyFactory).proxyCreationCode(); + bytes memory deploymentData = abi.encodePacked(proxyCreationCode, uint256(uint160(safeSingleton))); + return Create2.computeAddress(creationSalt, keccak256(deploymentData), safeProxyFactory); + } + + /// @inheritdoc ISafeTimelockFactory + function calculateTimelockAddress(address deployer, bytes32 salt) external view returns (address) { + bytes32 mixedSalt = _deriveSalt(deployer, salt); + return Clones.predictDeterministicAddress(timelockImplementation, mixedSalt); + } + + /// INTERNAL FUNCTIONS + + function _validateTimelockConfig(TimelockConfig calldata config) internal pure { + require(config.proposers.length > 0, NoProposers()); + require(config.executors.length > 0, NoExecutors()); + for (uint256 i = 0; i < config.proposers.length; i++) { + require(config.proposers[i] != address(0), ZeroAddressProposer()); + } + for (uint256 i = 0; i < config.executors.length; i++) { + require(config.executors[i] != address(0), ZeroAddressExecutor()); + } + } + + function _deploySafe(SafeConfig calldata config, bytes32 salt) internal returns (address safe) { + bytes memory initializer = _encodeSafeInitializer(config); + uint256 saltNonce = uint256(_deriveSalt(msg.sender, salt)); + safe = ISafeProxyFactory(safeProxyFactory).createProxyWithNonce(safeSingleton, initializer, saltNonce); + } + + function _encodeSafeInitializer(SafeConfig calldata config) internal view returns (bytes memory) { + return abi.encodeWithSelector( + ISafe.setup.selector, + config.owners, + config.threshold, + address(0), + "", + safeFallbackHandler, + address(0), + 0, + payable(address(0)) + ); + } + + function _deployTimelock(TimelockConfig calldata config, bytes32 salt) internal returns (address timelock) { + bytes32 mixedSalt = _deriveSalt(msg.sender, salt); + timelock = Clones.cloneDeterministic(timelockImplementation, mixedSalt); + + // forgefmt: disable-next-item + TimelockControllerImpl(payable(timelock)).initialize(config.minDelay, config.proposers, config.executors, address(0)); + } + + function _deriveSalt(address deployer, bytes32 salt) internal view returns (bytes32) { + return keccak256(abi.encodePacked(address(this), deployer, salt)); + } +} diff --git a/src/governance/AppAuthority.sol b/src/governance/AppAuthority.sol new file mode 100644 index 0000000..c98abc6 --- /dev/null +++ b/src/governance/AppAuthority.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IAppAuthority} from "../interfaces/IAppAuthority.sol"; +import {IApp} from "../interfaces/IApp.sol"; + +/** + * @title AppAuthority + * @notice Per-app ownership + RBAC state, extracted from AppController. + * AppController is the sole consumer — it authenticates callers of + * app-lifecycle operations and delegates the auth state here. + * + * @dev The contract enforces the owner-gated RBAC model by construction: + * - Only the scope owner may grant / revoke ADMIN. ADMIN itself is an + * operational-only role — it does NOT confer critical-op power on + * the consumer (upgrade / transfer / terminate). Those are gated on + * `isScopeOwner` at the consumer layer. + * - The owner is always ADMIN on their scope; they cannot renounce or + * self-revoke ADMIN. `transferScopeOwnership` is the only path that + * rotates the owner, and it adds the new owner + removes the previous + * owner from ADMIN atomically. + * - Non-ADMIN roles (PAUSER, DEVELOPER) are bounded, revocable + * operational powers any ADMIN may grant. They carry no critical-op + * authority. + * + * @dev Why extract: the critical-op gate on AppController + * (`msg.sender == scopeOwner`) binds iff the owner identity the gate + * reads is mutated only through paths this contract controls. Owning + * the state in a small dedicated contract with a narrow mutation + * surface makes that trust boundary explicit and shrinks the audit + * surface of AppController accordingly. + */ +contract AppAuthority is Initializable, IAppAuthority { + using EnumerableSet for EnumerableSet.AddressSet; + + /// @notice The consumer contract authorized to call mutation methods that + /// pass through app-lifecycle events (scope initialization, + /// ownership transfer, admin migration). + /// @dev Set at construction. Role-level grants/revokes/renounces are NOT + /// consumer-gated — users interact with them directly. + address public immutable consumer; + + /// @notice Per-scope owner. Zero means uninitialized. + mapping(IApp => address) internal _scopeOwner; + + /// @notice Per-scope, per-role set of holders. + mapping(IApp => mapping(Role => EnumerableSet.AddressSet)) internal _roles; + + /** + * @dev Reserved storage for future variables. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[48] private __gap; + + modifier onlyConsumer() { + if (msg.sender != consumer) revert OnlyConsumer(); + _; + } + + constructor(address _consumer) { + if (_consumer == address(0)) revert ZeroAddress(); + consumer = _consumer; + _disableInitializers(); + } + + /// @notice No-op initializer. All state is derived at construction + /// (consumer immutable) or via `initializeScope`. Exists so the + /// proxy can be deployed via upgradeAndCall. + function initialize() external initializer {} + + /// @inheritdoc IAppAuthority + function initializeScope(IApp scope, address owner) external onlyConsumer { + if (owner == address(0)) revert ZeroAddress(); + if (_scopeOwner[scope] != address(0)) revert ScopeAlreadyInitialized(); + + _scopeOwner[scope] = owner; + _roles[scope][Role.ADMIN].add(owner); + + emit ScopeInitialized(scope, owner); + emit RoleGranted(scope, Role.ADMIN, owner); + } + + /// @inheritdoc IAppAuthority + function transferScopeOwnership(IApp scope, address newOwner) external onlyConsumer { + if (newOwner == address(0)) revert ZeroAddress(); + + address previousOwner = _scopeOwner[scope]; + if (previousOwner == address(0)) revert ScopeNotInitialized(); + // Self-transfer would collapse the ADMIN add+remove pair into a net + // remove — the owner would lose their ADMIN role despite the owner + // pointer not changing. Reject explicitly. + if (newOwner == previousOwner) revert SameOwnerTransfer(); + + _scopeOwner[scope] = newOwner; + + // Add new owner first so the ADMIN set never empties between the + // two writes (preserves the last-admin invariant during the swap). + if (_roles[scope][Role.ADMIN].add(newOwner)) { + emit RoleGranted(scope, Role.ADMIN, newOwner); + } + if (_roles[scope][Role.ADMIN].remove(previousOwner)) { + emit RoleRevoked(scope, Role.ADMIN, previousOwner); + } + + emit ScopeOwnershipTransferred(scope, previousOwner, newOwner); + } + + /// @inheritdoc IAppAuthority + function grantRole(IApp scope, Role role, address account) external { + if (account == address(0)) revert ZeroAddress(); + + if (role == Role.ADMIN) { + if (msg.sender != _scopeOwner[scope]) revert NotScopeOwner(); + } else { + if (!_roles[scope][Role.ADMIN].contains(msg.sender)) revert InvalidRole(); + } + + if (_roles[scope][role].add(account)) { + emit RoleGranted(scope, role, account); + } + } + + /// @inheritdoc IAppAuthority + function revokeRole(IApp scope, Role role, address account) external { + if (role == Role.ADMIN) { + if (msg.sender != _scopeOwner[scope]) revert NotScopeOwner(); + if (account == _scopeOwner[scope]) revert CannotRemoveOwnerAdmin(); + } else { + if (!_roles[scope][Role.ADMIN].contains(msg.sender)) revert InvalidRole(); + } + + if (role == Role.ADMIN && _roles[scope][Role.ADMIN].length() == 1) { + revert CannotRemoveLastAdmin(); + } + + if (_roles[scope][role].remove(account)) { + emit RoleRevoked(scope, role, account); + } + } + + /// @inheritdoc IAppAuthority + function renounceRole(IApp scope, Role role) external { + if (role == Role.ADMIN) { + if (msg.sender == _scopeOwner[scope]) revert CannotRemoveOwnerAdmin(); + if (_roles[scope][Role.ADMIN].length() == 1) revert CannotRemoveLastAdmin(); + } + + if (_roles[scope][role].remove(msg.sender)) { + emit RoleRevoked(scope, role, msg.sender); + } + } + + /// @inheritdoc IAppAuthority + function migrateAdmins(IApp[] calldata scopes, address[][] calldata admins) external onlyConsumer { + require(scopes.length == admins.length, InvalidRole()); + for (uint256 i = 0; i < scopes.length; i++) { + IApp scope = scopes[i]; + address[] calldata scopeAdmins = admins[i]; + for (uint256 j = 0; j < scopeAdmins.length; j++) { + address adm = scopeAdmins[j]; + if (adm == address(0)) continue; + if (_roles[scope][Role.ADMIN].add(adm)) { + emit RoleGranted(scope, Role.ADMIN, adm); + } + } + } + } + + /// @inheritdoc IAppAuthority + function scopeOwner(IApp scope) external view returns (address) { + return _scopeOwner[scope]; + } + + /// @inheritdoc IAppAuthority + function isScopeOwner(IApp scope, address account) external view returns (bool) { + return _scopeOwner[scope] == account; + } + + /// @inheritdoc IAppAuthority + function hasRole(IApp scope, Role role, address account) external view returns (bool) { + return _roles[scope][role].contains(account); + } + + /// @inheritdoc IAppAuthority + function hasRoleOrAdmin(IApp scope, Role role, address account) external view returns (bool) { + return _roles[scope][role].contains(account) || _roles[scope][Role.ADMIN].contains(account); + } +} diff --git a/src/governance/TimelockControllerImpl.sol b/src/governance/TimelockControllerImpl.sol new file mode 100644 index 0000000..30143dc --- /dev/null +++ b/src/governance/TimelockControllerImpl.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import { + TimelockControllerUpgradeable +} from "@openzeppelin-upgrades/contracts/governance/TimelockControllerUpgradeable.sol"; +import {ICallValidator} from "../interfaces/ICallValidator.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/** + * @title TimelockControllerImpl + * @notice Implementation contract for TimelockController minimal proxies + * @dev Wraps TimelockControllerUpgradeable and adds on-chain pending operation enumeration. + * Overrides schedule/scheduleBatch/execute/executeBatch/cancel to maintain a set of + * pending operation IDs, enabling clients to enumerate queued operations without + * requiring event log scanning. + * + * Also validates targets at schedule-time: if a target implements ICallValidator, + * canCall(address(this), data) must return true or the schedule is rejected. + */ +contract TimelockControllerImpl is TimelockControllerUpgradeable { + struct PendingOp { + bytes32 id; + address target; + bytes data; + uint256 executableAt; + } + + // Append-only array of pending operation IDs (swap-and-pop on removal). + bytes32[] private _pendingIds; + // id => 1-based index into _pendingIds (0 means not in set). + mapping(bytes32 => uint256) private _pendingIndex; + // id => stored op metadata for enumeration + mapping(bytes32 => PendingOp) private _pendingOps; + + constructor() { + _disableInitializers(); + } + + /** + * @notice Initialize the timelock controller + * @param minDelay Minimum delay for operations + * @param proposers Addresses granted proposer and canceller roles + * @param executors Addresses granted executor role + * @param admin Optional admin address (use address(0) for self-administered) + */ + function initialize(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) + external + initializer + { + __TimelockController_init(minDelay, proposers, executors, admin); + } + + // ── Overrides ──────────────────────────────────────────────────────────── + + function schedule( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public override { + _validateTarget(target, data); + super.schedule(target, value, data, predecessor, salt, delay); + bytes32 id = hashOperation(target, value, data, predecessor, salt); + _addPending(id, target, data, block.timestamp + delay); + } + + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public override { + for (uint256 i = 0; i < targets.length; i++) { + _validateTarget(targets[i], payloads[i]); + } + super.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + // For batch ops store empty data — callers should use getPendingOperationIds and decode individually + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); + _addPending(id, address(0), "", block.timestamp + delay); + } + + function execute(address target, uint256 value, bytes calldata payload, bytes32 predecessor, bytes32 salt) + public + payable + override + { + bytes32 id = hashOperation(target, value, payload, predecessor, salt); + super.execute(target, value, payload, predecessor, salt); + _removePending(id); + } + + function executeBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) public payable override { + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); + super.executeBatch(targets, values, payloads, predecessor, salt); + _removePending(id); + } + + function cancel(bytes32 id) public override { + super.cancel(id); + _removePending(id); + } + + // ── Enumeration ────────────────────────────────────────────────────────── + + /** + * @notice Returns all currently pending operation IDs. + */ + function getPendingOperationIds() external view returns (bytes32[] memory) { + return _pendingIds; + } + + /** + * @notice Returns all currently pending operations with metadata. + * @dev For single-call ops, target and data are populated. + * For batch ops, target is address(0) and data is empty. + */ + function getPendingOperations() external view returns (PendingOp[] memory ops) { + ops = new PendingOp[](_pendingIds.length); + for (uint256 i = 0; i < _pendingIds.length; i++) { + ops[i] = _pendingOps[_pendingIds[i]]; + } + } + + // ── Internal helpers ───────────────────────────────────────────────────── + + /// Gas cap for the ERC-165 probe. A compliant target needs ~2k gas; + /// 30k is generous while still bounding returndata-bomb griefing. + uint256 private constant ERC165_PROBE_GAS = 30_000; + + /// Gas cap for the `canCall` query. Validation is view-only logic + /// (storage reads, comparisons) so 200k is plenty; any implementation + /// that needs more is almost certainly misbehaving. + uint256 private constant CANCALL_QUERY_GAS = 200_000; + + /// interfaceId for ICallValidator, computed inline to avoid importing + /// ERC-165's library just to read one constant. + bytes4 private constant I_CALL_VALIDATOR_INTERFACE_ID = type(ICallValidator).interfaceId; + + /** + * @dev Schedule-time target validation. + * + * A target that advertises {ICallValidator} via ERC-165 gets its + * `canCall(this, data)` consulted; if it returns false the schedule + * reverts. Targets that don't advertise the interface are allowed + * through (backwards compatible with any external contract that the + * system might Timelock in the future). + * + * Hardening choices addressed here: + * - ERC-165 first, so a target that reverts for any non-authorization + * reason (including OOG) is NOT silently treated as "not implemented". + * Only a target that explicitly returns `false` from `supportsInterface` + * or doesn't respond to the probe is skipped. + * - Bounded gas on both probes to stop a malicious target from + * exhausting the outer call via runaway `canCall` computation. + * - Bounded returndata (only the first 32 bytes are considered) to + * prevent returndata-bomb griefing; excess is ignored. + * - When `supportsInterface` says yes but `canCall` reverts, that is + * treated as a definitive "no" — the target made a claim it couldn't + * back up, so its pending op is rejected rather than silently allowed. + */ + function _validateTarget(address target, bytes calldata data) private view { + // Only contracts can implement ICallValidator; EOAs and zero-code + // addresses short-circuit without any external calls. + if (target.code.length == 0) return; + + if (!_supportsCallValidator(target)) return; + + // Target claims to implement the interface; ask it. + bytes memory canCallCalldata = abi.encodeWithSelector(ICallValidator.canCall.selector, address(this), data); + (bool ok, bytes memory ret) = target.staticcall{gas: CANCALL_QUERY_GAS}(canCallCalldata); + + // A revert here means the target advertised ICallValidator but its + // implementation failed. Fail closed — do not let it through as if + // the interface was absent. + require(ok && ret.length >= 32, "TimelockController: canCall reverted"); + + bool allowed = abi.decode(ret, (bool)); + require(allowed, "TimelockController: target rejected the call"); + } + + /// @dev Probe `target` for ICallValidator support via ERC-165. + /// Returns false (allow through, treat as non-implementing) on any + /// failure or non-bool return; returns true only on an explicit `true`. + function _supportsCallValidator(address target) private view returns (bool) { + bytes memory probe = abi.encodeWithSelector(IERC165.supportsInterface.selector, I_CALL_VALIDATOR_INTERFACE_ID); + (bool ok, bytes memory ret) = target.staticcall{gas: ERC165_PROBE_GAS}(probe); + if (!ok || ret.length < 32) return false; + return abi.decode(ret, (bool)); + } + + /// Maximum number of concurrently-pending operations tracked in the + /// on-chain enumeration set. Chosen so that getPendingOperations() stays + /// well under the block gas limit even when every entry carries a + /// several-hundred-byte calldata blob. A normal governance workload never + /// approaches this cap; the point is to make _pendingIds growth bounded + /// so a single misbehaving (or compromised) proposer cannot brick + /// off-chain tooling that reads the set. Queued ops above the cap simply + /// cannot be scheduled until older ones are executed or cancelled — + /// scheduling itself reverts, giving callers an immediate error instead + /// of silently committing a non-enumerable op. + uint256 private constant MAX_PENDING_OPS = 32; + + error TooManyPendingOperations(); + + function _addPending(bytes32 id, address target, bytes memory data, uint256 executableAt) private { + if (_pendingIndex[id] != 0) return; // already tracked + if (_pendingIds.length >= MAX_PENDING_OPS) revert TooManyPendingOperations(); + _pendingIds.push(id); + _pendingIndex[id] = _pendingIds.length; // 1-based + _pendingOps[id] = PendingOp({id: id, target: target, data: data, executableAt: executableAt}); + } + + function _removePending(bytes32 id) private { + uint256 idx = _pendingIndex[id]; + if (idx == 0) return; // not tracked (pre-upgrade op) + uint256 i = idx - 1; + uint256 last = _pendingIds.length - 1; + if (i != last) { + bytes32 lastId = _pendingIds[last]; + _pendingIds[i] = lastId; + _pendingIndex[lastId] = idx; // keep 1-based + } + _pendingIds.pop(); + delete _pendingIndex[id]; + delete _pendingOps[id]; + } +} diff --git a/src/interfaces/IAppAuthority.sol b/src/interfaces/IAppAuthority.sol new file mode 100644 index 0000000..a42a039 --- /dev/null +++ b/src/interfaces/IAppAuthority.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {IApp} from "./IApp.sol"; + +/** + * @title IAppAuthority + * @notice Per-app RBAC + owner registry. Centralizes the auth surface that + * AppController previously owned inline — scope ownership (the + * "owner" concept formerly called `creator`) and team roles + * (ADMIN / PAUSER / DEVELOPER). + * + * @dev Design invariants: + * 1. Every scope (app) has at most one owner. `scopeOwner[app] == 0` + * means the scope has not been initialized. + * 2. The owner is always ADMIN on their scope. `transferOwnership` is + * the only path that rotates the owner; it adds the new owner to + * ADMIN and removes the previous owner atomically. + * 3. ADMIN role mutations (grant/revoke) are owner-only. The owner + * cannot renounce or self-revoke ADMIN — `transferOwnership` is the + * only path out. + * 4. Critical ops gated at the consumer layer (upgrade/transfer/ + * terminate on AppController) use `isScopeOwner` — NOT + * `hasRole(ADMIN)`. ADMIN is operational-only in this model. + * 5. `canCall` validates schedule-time that a Timelock operation + * targeting a consumer contract will pass runtime gates, so a + * doomed op doesn't burn a pending-op slot on the Timelock. + */ +interface IAppAuthority { + /// @notice Thrown when a caller is not the current owner of the scope. + error NotScopeOwner(); + + /// @notice Thrown when a caller lacks the required role. + error InvalidRole(); + + /// @notice Thrown when the owner attempts to renounce or self-revoke ADMIN. + error CannotRemoveOwnerAdmin(); + + /// @notice Thrown when revoking/renouncing ADMIN would leave zero admins. + error CannotRemoveLastAdmin(); + + /// @notice Thrown when a scope already has an owner and an attempt is + /// made to initialize it. + error ScopeAlreadyInitialized(); + + /// @notice Thrown when an attempted operation requires the scope to be + /// initialized but it isn't. + error ScopeNotInitialized(); + + /// @notice Thrown when the caller is not the authorized consumer + /// (AppController) and a consumer-only method is called. + error OnlyConsumer(); + + /// @notice Thrown when a zero address is passed where non-zero is required. + error ZeroAddress(); + + /// @notice Thrown when `transferScopeOwnership` is called with the new + /// owner equal to the current owner. The operation is a no-op + /// in intent, but the naive implementation would incorrectly + /// remove the owner from ADMIN (add/remove on the same address + /// collapses to a net remove). Reject explicitly. + error SameOwnerTransfer(); + + /// @notice Emitted when a scope is first initialized with an owner. + event ScopeInitialized(IApp indexed scope, address indexed owner); + + /// @notice Emitted when scope ownership is transferred. + event ScopeOwnershipTransferred(IApp indexed scope, address indexed previousOwner, address indexed newOwner); + + /// @notice Emitted when a role is granted on a scope. + event RoleGranted(IApp indexed scope, Role indexed role, address indexed account); + + /// @notice Emitted when a role is revoked on a scope (by owner, admin, or self-renounce). + event RoleRevoked(IApp indexed scope, Role indexed role, address indexed account); + + /// @notice Per-app team roles. ADMIN is operational-only; it does NOT + /// convey power over critical ops. PAUSER and DEVELOPER are + /// delegable subsets of ADMIN's operational surface. + enum Role { + ADMIN, + PAUSER, + DEVELOPER + } + + /** + * @notice Initialize a scope's owner. May only be called by the consumer + * (AppController) once per scope, at app creation time. Grants + * the owner ADMIN atomically. + * @dev Consumer-only; reverts `OnlyConsumer` from any other caller. + */ + function initializeScope(IApp scope, address owner) external; + + /** + * @notice Transfer ownership of a scope to a new address. Atomically + * adds `newOwner` to ADMIN and removes `previousOwner` from + * ADMIN. Consumer-only — the consumer contract is responsible + * for authenticating the caller. + * @dev Consumer-only; reverts `OnlyConsumer` from any other caller. + */ + function transferScopeOwnership(IApp scope, address newOwner) external; + + /** + * @notice Grant a role to `account` on `scope`. + * @dev For `role == ADMIN`: caller must be the scope's owner. + * @dev For non-ADMIN: caller must hold ADMIN on the scope. + */ + function grantRole(IApp scope, Role role, address account) external; + + /** + * @notice Revoke a role from `account` on `scope`. + * @dev For `role == ADMIN`: caller must be the scope's owner. The owner + * cannot revoke their own ADMIN. Revoking below the last-ADMIN + * floor reverts. + * @dev For non-ADMIN: caller must hold ADMIN on the scope. + */ + function revokeRole(IApp scope, Role role, address account) external; + + /** + * @notice Renounce your own role on `scope`. + * @dev The owner cannot renounce ADMIN. Non-owners may renounce ADMIN + * as long as the set doesn't empty. + */ + function renounceRole(IApp scope, Role role) external; + + /** + * @notice Migrate admins from an external source (e.g., legacy + * PermissionController) into the ADMIN role on each supplied + * scope. Consumer-only. Idempotent. + * @dev For each (scope, admin) pair, grants ADMIN if not already present. + * Refuses to migrate into scopes that have already been initialized + * if the admin would conflict with the owner invariant — specifically, + * migrated admins are operational-only under this model, so the + * downgraded-from-critical-op semantics are already applied. + */ + function migrateAdmins(IApp[] calldata scopes, address[][] calldata admins) external; + + /// @notice The current owner of a scope, or address(0) if uninitialized. + function scopeOwner(IApp scope) external view returns (address); + + /// @notice Whether `account` is the current owner of `scope`. + function isScopeOwner(IApp scope, address account) external view returns (bool); + + /// @notice Whether `account` holds `role` on `scope`. + function hasRole(IApp scope, Role role, address account) external view returns (bool); + + /// @notice Whether `account` holds `role` on `scope`, OR holds ADMIN + /// (which is a superset for operational roles). + function hasRoleOrAdmin(IApp scope, Role role, address account) external view returns (bool); + + /// @notice The consumer contract (AppController) that owns the + /// consumer-only mutation surface. + function consumer() external view returns (address); +} diff --git a/src/interfaces/IAppController.sol b/src/interfaces/IAppController.sol index 7fa3fee..4147b16 100644 --- a/src/interfaces/IAppController.sol +++ b/src/interfaces/IAppController.sol @@ -29,9 +29,22 @@ interface IAppController { /// @notice Thrown when trying to suspend an account that still has active apps error AccountHasActiveApps(); + /// @notice Thrown when a caller is not the current owner (creator) of an app. + /// Critical ops (upgrade/transfer/terminate) are owner-gated. + error NotCreator(); + + /// @notice Thrown when a caller lacks the required operational role on an + /// app. Operational roles (PAUSER, DEVELOPER) are queried from + /// AppAuthority; this error is raised when the queried check fails. + error InvalidTeamRole(); + /// @notice Thrown when trying to confirm an upgrade with no pending release error NoPendingUpgrade(); + /// @notice Thrown when `acceptOwnership` is called by an address that is + /// not the current pending owner of the app. + error NotPendingOwner(); + /// @notice Emitted when a new app is successfully created event AppCreated(address indexed creator, IApp indexed app, uint32 operatorSetId); @@ -65,6 +78,18 @@ interface IAppController { /// @notice Emitted when an app's metadata URI is updated event AppMetadataURIUpdated(IApp indexed app, string metadataURI); + /// @notice Emitted when app ownership is transferred to a new address + event AppOwnershipTransferred(IApp indexed app, address indexed previousOwner, address indexed newOwner); + + /// @notice Emitted when the current owner proposes a new owner. The + /// proposed owner must call `acceptOwnership` to complete. + event OwnershipTransferProposed(IApp indexed app, address indexed currentOwner, address indexed proposedOwner); + + /// @notice Emitted when a pending ownership proposal is cancelled — + /// either explicitly by the current owner, or implicitly by + /// being superseded by a new proposal. + event OwnershipTransferCancelled(IApp indexed app, address indexed currentOwner, address indexed cancelledOwner); + /// @notice Enum for app status enum AppStatus { NONE, // App has not been created yet @@ -134,6 +159,10 @@ interface IAppController { uint32 pendingReleaseBlockNumber; AppStatus status; BillingType billingType; + // Pending owner for two-step transferOwnership. Zero means no + // pending proposal. Fits in the second storage slot alongside the + // first-slot packed fields above; does not shift prior layout. + address pendingOwner; } /// @notice User configuration and state @@ -181,11 +210,15 @@ interface IAppController { function createAppWithIsolatedBilling(bytes32 salt, Release calldata release) external returns (IApp app); /** - * @notice Upgrades an app with a new release to the ReleaseManager + * @notice Upgrades an app with a new release to the ReleaseManager. Critical op. * @param app The app to upgrade with the release * @param release The release to upgrade to * @return releaseId The unique identifier for the published release - * @dev Caller must be UAM permissioned for the app + * @dev Caller must be the app's current owner (`creator`). If the owner + * is a Timelock, the call is forced through schedule → execute; + * if it's a Safe, through the multisig threshold; if it's an EOA, + * directly. The governance mechanism is whatever the owner contract + * is — AppController does not classify it. Co-ADMINs cannot upgrade. * @dev The rms release must have exactly one artifact, with the digest being the docker * image digest and the registry being the docker registry it is stored at. * @dev The env must be a JSON marshalled bytes representing the public environment variables for the app. @@ -195,6 +228,55 @@ interface IAppController { */ function upgradeApp(IApp app, Release calldata release) external returns (uint256); + /** + * @notice Propose transferring app ownership to a new address. Step 1 + * of a two-step transfer. The proposed owner must call + * `acceptOwnership(app)` to complete the transfer — the current + * owner cannot push ownership (and the associated billing / quota + * consumption on DEFAULT-billed apps) onto an unwilling receiver. + * @param app The app to propose ownership transfer for + * @param newOwner The proposed new owner address + * @dev Caller must be the app's current owner (`creator`). + * @dev No state changes to AppAuthority or active-app counters at this + * step — only the pending-owner field updates. If an older + * proposal existed, it is silently superseded. + * @dev Use `cancelOwnershipTransfer(app)` to rescind a pending proposal. + */ + function transferOwnership(IApp app, address newOwner) external; + + /** + * @notice Accept a pending ownership transfer. Step 2 of the two-step + * flow. Atomically rotates scope ownership and ADMIN in + * AppAuthority, mirrors the owner into `creator`, migrates the + * active-app counter (for DEFAULT-billed apps), and verifies + * the receiver has capacity. + * @param app The app to accept ownership of + * @dev Caller must equal the current pending owner of `app`. + * @dev For DEFAULT-billed active apps, the caller's `activeAppCount` + * must be strictly less than their `maxActiveApps` — same rule as + * `createApp`. This prevents an unwilling receiver from exceeding + * their quota or being force-billed above their cap. + * @dev The new owner's contract type (EOA / Safe / Timelock / other) + * determines the governance mechanism for future critical ops. + * AppController does not classify or enforce a choice here. + */ + function acceptOwnership(IApp app) external; + + /** + * @notice Rescind a pending ownership proposal. No-op if no proposal + * exists for `app`. + * @param app The app whose pending ownership transfer should be cancelled + * @dev Caller must be the app's current owner (`creator`). + */ + function cancelOwnershipTransfer(IApp app) external; + + /** + * @notice Returns the address currently pending acceptance as the new + * owner of `app`, or `address(0)` if no proposal exists. + * @param app The app to query + */ + function getPendingOwner(IApp app) external view returns (address); + /** * @notice Confirms a pending upgrade, promoting the pending release to the confirmed (latest) release * @param app The app to confirm the upgrade for @@ -204,34 +286,35 @@ interface IAppController { function confirmUpgrade(IApp app) external; /** - * @notice Updates the metadata URI for an app + * @notice Updates the metadata URI for an app. Operational. * @param app The app to update the metadata URI for * @param metadataURI The new metadata URI - * @dev Caller must be UAM permissioned for the app + * @dev Permitted to ADMIN or DEVELOPER. */ function updateAppMetadataURI(IApp app, string calldata metadataURI) external; /** - * @notice Starts an app, which starts the instance backing it + * @notice Starts an app, which starts the instance backing it. * @param app The app to start - * @dev Caller must be UAM permissioned for the app - * @dev App must be AppStatus.STOPPED + * @dev Permitted to ADMIN only — starting commits capacity; treated as + * privileged. App must be STOPPED. */ function startApp(IApp app) external; /** - * @notice Stops an app, which stops the instance backing it + * @notice Stops an app, which stops the instance backing it. Operational. * @param app The app to stop - * @dev Caller must be UAM permissioned for the app - * @dev App must be AppStatus.STARTED + * @dev Permitted to ADMIN or PAUSER. + * @dev App must be AppStatus.STARTED. */ function stopApp(IApp app) external; /** - * @notice Terminates an app permanently + * @notice Terminates an app permanently. Critical op. * @param app The app to terminate - * @dev Caller must be UAM permissioned for the app - * @dev Once terminated, no further write operations are allowed + * @dev Caller must be the app's current owner (`creator`). Co-ADMINs + * cannot terminate. + * @dev Once terminated, no further write operations are allowed. */ function terminateApp(IApp app) external; @@ -253,6 +336,21 @@ interface IAppController { */ function suspend(address account, IApp[] calldata apps) external; + /** + * @notice Migrate pre-v1.5.0 apps to AppAuthority-based RBAC. For each + * app in `apps`: + * - seeds AppAuthority.scopeOwner(app) from AppController.creator + * if not already set; + * - seeds AppAuthority.ADMIN role with the app's PermissionController + * admins. ADMIN is an operational-only role — it does NOT + * confer critical-op power (upgrade / transfer / terminate), + * so migrated admins cannot replay pre-v1.5.0 critical ops. + * @dev Caller must be UAM permissioned for the AppController itself + * (platform admin). Intended to be called once per app after the + * v1.5.0 upgrade; safe to call again (idempotent per-(app, admin)). + */ + function migrateAppsToAppAuthority(IApp[] calldata apps) external; + /** * @notice Gets the maximum global active apps limit * @return The maximum number of active apps globally diff --git a/src/interfaces/ICallValidator.sol b/src/interfaces/ICallValidator.sol new file mode 100644 index 0000000..f3716d0 --- /dev/null +++ b/src/interfaces/ICallValidator.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/** + * @title ICallValidator + * @notice Optional interface that Timelock targets can implement to reject + * operations at schedule-time rather than waiting until execute-time. + * + * @dev Targets MUST opt in via ERC-165 `supportsInterface` so that a reverting + * `canCall` is never silently interpreted as "not implemented": + * + * supportsInterface(type(ICallValidator).interfaceId) == true + * + * If a target advertises the interface and `canCall` reverts, the caller + * should fail the request — not fall through as if the interface was + * absent. See `TimelockControllerImpl._validateTarget` for the enforcement + * point. + */ +interface ICallValidator is IERC165 { + /** + * @notice Returns true if `caller` is authorized to execute `data` on this contract. + * @param caller The address that will ultimately call (e.g., the Timelock). + * @param data The calldata that will be forwarded to the target. + */ + function canCall(address caller, bytes calldata data) external view returns (bool); +} diff --git a/src/interfaces/ISafe.sol b/src/interfaces/ISafe.sol new file mode 100644 index 0000000..6d07d76 --- /dev/null +++ b/src/interfaces/ISafe.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// Minimal interface for the Gnosis Safe singleton. Hand-rolled to avoid +// pulling the full safe-global/safe-contracts dependency for two function +// selectors. +// +// Signature source: Safe singleton `setup(address[],uint256,address,bytes, +// address,address,uint256,address)`. Stable across Safe v1.3.0 / v1.3.0-l2 / +// v1.4.1 / v1.4.1-l2 — the versions currently deployed on the chains we +// target (mainnet, Sepolia, OP-stack L2s). If Safe ships a breaking change +// in a future major version, update this file and pin the deployed +// singleton in zeus env accordingly. +interface ISafe { + function setup( + address[] calldata _owners, + uint256 _threshold, + address to, + bytes calldata data, + address fallbackHandler, + address paymentToken, + uint256 payment, + address payable paymentReceiver + ) external; +} diff --git a/src/interfaces/ISafeProxyFactory.sol b/src/interfaces/ISafeProxyFactory.sol new file mode 100644 index 0000000..da454e3 --- /dev/null +++ b/src/interfaces/ISafeProxyFactory.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// Minimal interface for Gnosis SafeProxyFactory. Hand-rolled to avoid +// depending on safe-global/safe-contracts for two function selectors. +// +// Signature source: SafeProxyFactory v1.3.0 through v1.4.1 — stable across +// the factory versions currently deployed on our target chains. +// `createProxyWithNonce` is what we call; `proxyCreationCode` is used to +// precompute deterministic Safe addresses via CREATE2. +interface ISafeProxyFactory { + function createProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce) + external + returns (address proxy); + + function proxyCreationCode() external pure returns (bytes memory); +} diff --git a/src/interfaces/ISafeTimelockFactory.sol b/src/interfaces/ISafeTimelockFactory.sol new file mode 100644 index 0000000..8a78456 --- /dev/null +++ b/src/interfaces/ISafeTimelockFactory.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/** + * @title ISafeTimelockFactory + * @notice Factory for deploying verified Gnosis Safes and TimelockControllers + * @dev Deployed entities can be verified as "official" via isSafe() and isTimelock() + */ +interface ISafeTimelockFactory { + /// STRUCTS + /// @notice Configuration for deploying a Gnosis Safe + struct SafeConfig { + address[] owners; // Addresses that can sign transactions + uint256 threshold; // Number of required signatures + } + + /// @notice Configuration for deploying a TimelockController + struct TimelockConfig { + uint256 minDelay; // Minimum delay in seconds before execution + address[] proposers; // Addresses that can propose and cancel operations + address[] executors; // Addresses that can execute ready operations + } + + /// EVENTS + + /// @notice Emitted when a Gnosis Safe is deployed + event SafeDeployed( + address indexed deployer, address indexed safe, address[] owners, uint256 threshold, bytes32 salt + ); + + /// @notice Emitted when a TimelockController is deployed + event TimelockDeployed( + address indexed deployer, + address indexed timelock, + uint256 minDelay, + address[] proposers, + address[] executors, + bytes32 salt + ); + + /// ERRORS + + /// @notice Thrown when proposers array is empty + error NoProposers(); + + /// @notice Thrown when executors array is empty + error NoExecutors(); + + /// @notice Thrown when a proposer address is zero + error ZeroAddressProposer(); + + /// @notice Thrown when an executor address is zero + error ZeroAddressExecutor(); + + /// EXTERNAL FUNCTIONS + + /** + * @notice Deploys a new Gnosis Safe with deterministic address + * @param config Safe configuration (owners, threshold) + * @param salt User-provided salt for deterministic deployment + * @return safe The deployed Safe address + */ + function deploySafe(SafeConfig calldata config, bytes32 salt) external returns (address safe); + + /** + * @notice Deploys a new TimelockController with deterministic address + * @param config Timelock configuration (minDelay, proposers, executors) + * @param salt User-provided salt for deterministic deployment + * @return timelock The deployed TimelockController address + */ + function deployTimelock(TimelockConfig calldata config, bytes32 salt) external returns (address timelock); + + /// VIEW FUNCTIONS + + /** + * @notice Checks if an address is a Safe deployed by this factory + * @param safe The address to check + * @return True if the address is a factory-deployed Safe + */ + function isSafe(address safe) external view returns (bool); + + /** + * @notice Checks if an address is a Timelock deployed by this factory + * @param timelock The address to check + * @return True if the address is a factory-deployed Timelock + */ + function isTimelock(address timelock) external view returns (bool); + + /** + * @notice Pre-computes the address of a Safe deployment + * @param deployer The address that will deploy + * @param config Safe configuration + * @param salt User-provided salt + * @return The computed Safe address + */ + function calculateSafeAddress(address deployer, SafeConfig calldata config, bytes32 salt) + external + view + returns (address); + + /** + * @notice Pre-computes the address of a Timelock deployment + * @param deployer The address that will deploy + * @param salt User-provided salt + * @return The computed TimelockController address + */ + function calculateTimelockAddress(address deployer, bytes32 salt) external view returns (address); + + /** + * @notice Returns the official Gnosis Safe singleton address + * @return The Safe singleton (master copy) address + */ + function safeSingleton() external view returns (address); + + /** + * @notice Returns the official Gnosis Safe proxy factory address + * @return The SafeProxyFactory address + */ + function safeProxyFactory() external view returns (address); + + /** + * @notice Returns the TimelockController implementation address for minimal proxies + * @return The TimelockControllerImpl address + */ + function timelockImplementation() external view returns (address); + + /** + * @notice Returns all Timelocks deployed by a given deployer + * @param deployer The deployer address + * @return Array of Timelock addresses + */ + function getTimelocksByDeployer(address deployer) external view returns (address[] memory); + + /** + * @notice Returns all Safes deployed by a given deployer + * @param deployer The deployer address + * @return Array of Safe addresses + */ + function getSafesByDeployer(address deployer) external view returns (address[] memory); +} diff --git a/src/storage/AppControllerStorage.sol b/src/storage/AppControllerStorage.sol index 0bf97a4..ca234d2 100644 --- a/src/storage/AppControllerStorage.sol +++ b/src/storage/AppControllerStorage.sol @@ -9,6 +9,7 @@ import {IApp} from "../interfaces/IApp.sol"; import {IAppController} from "../interfaces/IAppController.sol"; import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IAppAuthority} from "../interfaces/IAppAuthority.sol"; abstract contract AppControllerStorage is IAppController { using EnumerableSet for EnumerableSet.AddressSet; @@ -38,6 +39,15 @@ abstract contract AppControllerStorage is IAppController { /// @notice The beacon used for creating App proxies IBeacon public immutable appBeacon; + /// @notice Authority contract that owns per-app ownership and RBAC state. + /// @dev AppController delegates auth to this contract — ownership + /// transfer, role management, and the scope-owner reads used by + /// `onlyCreator` / `canCall` all flow through AppAuthority. The + /// owner-gated invariants (only the owner may mutate ADMIN; owner + /// is always in ADMIN; transferScopeOwnership is the only owner + /// rotation path) are enforced in AppAuthority, not here. + IAppAuthority public immutable appAuthority; + /// @notice Set of all created apps EnumerableSet.AddressSet internal _allApps; @@ -57,12 +67,14 @@ abstract contract AppControllerStorage is IAppController { IReleaseManager _releaseManager, IComputeOperator _computeOperator, IComputeAVSRegistrar _computeAVSRegistrar, - IBeacon _appBeacon + IBeacon _appBeacon, + IAppAuthority _appAuthority ) { releaseManager = _releaseManager; computeOperator = _computeOperator; computeAVSRegistrar = _computeAVSRegistrar; appBeacon = _appBeacon; + appAuthority = _appAuthority; } /** diff --git a/src/storage/SafeTimelockFactoryStorage.sol b/src/storage/SafeTimelockFactoryStorage.sol new file mode 100644 index 0000000..bcc2a2d --- /dev/null +++ b/src/storage/SafeTimelockFactoryStorage.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {ISafeTimelockFactory} from "../interfaces/ISafeTimelockFactory.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +/** + * @title SafeTimelockFactoryStorage + * @notice Storage contract for SafeTimelockFactory + */ +abstract contract SafeTimelockFactoryStorage is ISafeTimelockFactory { + using EnumerableSet for EnumerableSet.AddressSet; + + /// IMMUTABLES + + /// @notice The official Gnosis Safe singleton (master copy) address + address public immutable safeSingleton; + + /// @notice The official Gnosis Safe proxy factory address + address public immutable safeProxyFactory; + + /// @notice The default fallback handler for Safes + address public immutable safeFallbackHandler; + + /// @notice The TimelockController implementation for minimal proxies + address public immutable timelockImplementation; + + /// STATE VARIABLES + + /// @notice Set of all Safes deployed by this factory + EnumerableSet.AddressSet internal _safes; + + /// @notice Set of all Timelocks deployed by this factory + EnumerableSet.AddressSet internal _timelocks; + + /// @notice Timelocks indexed by deployer address + mapping(address => EnumerableSet.AddressSet) internal _timelocksByDeployer; + + /// @notice Safes indexed by deployer address + mapping(address => EnumerableSet.AddressSet) internal _safesByDeployer; + + /// STORAGE GAP + + /// @notice Storage gap for future upgrades + uint256[44] private __gap; + + /// CONSTRUCTOR + + /** + * @param _safeSingleton The official Gnosis Safe singleton address + * @param _safeProxyFactory The official Gnosis Safe proxy factory address + * @param _safeFallbackHandler The default fallback handler for Safes + * @param _timelockImplementation The TimelockController implementation for minimal proxies + */ + constructor( + address _safeSingleton, + address _safeProxyFactory, + address _safeFallbackHandler, + address _timelockImplementation + ) { + safeSingleton = _safeSingleton; + safeProxyFactory = _safeProxyFactory; + safeFallbackHandler = _safeFallbackHandler; + timelockImplementation = _timelockImplementation; + } +} diff --git a/test/AppAuthority.t.sol b/test/AppAuthority.t.sol new file mode 100644 index 0000000..842e5a0 --- /dev/null +++ b/test/AppAuthority.t.sol @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import {AppAuthority} from "../src/governance/AppAuthority.sol"; +import {IAppAuthority} from "../src/interfaces/IAppAuthority.sol"; +import {IApp} from "../src/interfaces/IApp.sol"; + +contract AppAuthorityTest is Test { + AppAuthority internal authority; + + address internal consumer = makeAddr("consumer"); + address internal alice = makeAddr("alice"); + address internal bob = makeAddr("bob"); + address internal carol = makeAddr("carol"); + + IApp internal constant APP_A = IApp(address(0xA0A0)); + IApp internal constant APP_B = IApp(address(0xB0B0)); + + function setUp() public { + // Deploy the impl directly. Tests don't need the proxy layer — + // they exercise pure authority logic. `_disableInitializers` in the + // constructor means initialize() is unreachable, which is fine: the + // initializer body is a no-op. + authority = new AppAuthority(consumer); + } + + // ========== scope initialization ========== + + function test_initializeScope_consumerOnly() public { + vm.prank(alice); + vm.expectRevert(IAppAuthority.OnlyConsumer.selector); + authority.initializeScope(APP_A, alice); + } + + function test_initializeScope_rejectsZeroOwner() public { + vm.prank(consumer); + vm.expectRevert(IAppAuthority.ZeroAddress.selector); + authority.initializeScope(APP_A, address(0)); + } + + function test_initializeScope_seedsOwnerAndAdmin() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + assertEq(authority.scopeOwner(APP_A), alice); + assertTrue(authority.isScopeOwner(APP_A, alice)); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, alice)); + } + + function test_initializeScope_rejectsReinit() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(consumer); + vm.expectRevert(IAppAuthority.ScopeAlreadyInitialized.selector); + authority.initializeScope(APP_A, bob); + } + + // ========== ownership transfer ========== + + function test_transferScopeOwnership_consumerOnly() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); // even the owner can't directly — goes via consumer + vm.expectRevert(IAppAuthority.OnlyConsumer.selector); + authority.transferScopeOwnership(APP_A, bob); + } + + function test_transferScopeOwnership_rotatesAdmin() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(consumer); + authority.transferScopeOwnership(APP_A, bob); + + assertEq(authority.scopeOwner(APP_A), bob); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, bob)); + assertFalse(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, alice)); + } + + function test_transferScopeOwnership_rejectsUninitialized() public { + vm.prank(consumer); + vm.expectRevert(IAppAuthority.ScopeNotInitialized.selector); + authority.transferScopeOwnership(APP_A, bob); + } + + function test_transferScopeOwnership_rejectsZeroNewOwner() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(consumer); + vm.expectRevert(IAppAuthority.ZeroAddress.selector); + authority.transferScopeOwnership(APP_A, address(0)); + } + + function test_transferScopeOwnership_rejectsSelfTransfer() public { + // Self-transfer would collapse add(alice) + remove(alice) on the + // ADMIN set into a net remove — alice ends up not-an-ADMIN despite + // the owner pointer unchanged. Must reject explicitly. + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(consumer); + vm.expectRevert(IAppAuthority.SameOwnerTransfer.selector); + authority.transferScopeOwnership(APP_A, alice); + + // State must be untouched. + assertEq(authority.scopeOwner(APP_A), alice); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, alice)); + } + + // ========== grantRole ========== + + function test_grantRole_adminRequiresOwner() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + // alice (owner) can grant ADMIN + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.ADMIN, bob); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, bob)); + + // bob (ADMIN but not owner) cannot grant ADMIN + vm.prank(bob); + vm.expectRevert(IAppAuthority.NotScopeOwner.selector); + authority.grantRole(APP_A, IAppAuthority.Role.ADMIN, carol); + } + + function test_grantRole_pauserRequiresAdmin() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + // alice (owner, so ADMIN) can grant PAUSER + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.PAUSER, bob); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.PAUSER, bob)); + + // carol (not ADMIN) cannot grant PAUSER + vm.prank(carol); + vm.expectRevert(IAppAuthority.InvalidRole.selector); + authority.grantRole(APP_A, IAppAuthority.Role.PAUSER, carol); + + // bob (granted ADMIN manually for this test) can grant PAUSER + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.ADMIN, bob); + + vm.prank(bob); + authority.grantRole(APP_A, IAppAuthority.Role.PAUSER, carol); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.PAUSER, carol)); + } + + function test_grantRole_rejectsZeroAccount() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); + vm.expectRevert(IAppAuthority.ZeroAddress.selector); + authority.grantRole(APP_A, IAppAuthority.Role.ADMIN, address(0)); + } + + // ========== revokeRole ========== + + function test_revokeRole_adminRequiresOwner() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.ADMIN, bob); + + // bob (ADMIN, not owner) cannot revoke alice + vm.prank(bob); + vm.expectRevert(IAppAuthority.NotScopeOwner.selector); + authority.revokeRole(APP_A, IAppAuthority.Role.ADMIN, alice); + + // alice (owner) can revoke bob + vm.prank(alice); + authority.revokeRole(APP_A, IAppAuthority.Role.ADMIN, bob); + assertFalse(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, bob)); + } + + function test_revokeRole_ownerCannotRevokeSelf() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); + vm.expectRevert(IAppAuthority.CannotRemoveOwnerAdmin.selector); + authority.revokeRole(APP_A, IAppAuthority.Role.ADMIN, alice); + } + + function test_revokeRole_cannotRemoveLastAdmin() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + // Cannot revoke alice when she's the last admin. But the owner-self + // check fires first. Add bob then revoke alice's own role — still + // blocked by CannotRemoveOwnerAdmin. + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.ADMIN, bob); + + // Remove bob: OK, alice remains. + vm.prank(alice); + authority.revokeRole(APP_A, IAppAuthority.Role.ADMIN, bob); + + // Can't revoke alice (owner-self block is stricter than last-admin). + vm.prank(alice); + vm.expectRevert(IAppAuthority.CannotRemoveOwnerAdmin.selector); + authority.revokeRole(APP_A, IAppAuthority.Role.ADMIN, alice); + } + + function test_revokeRole_pauserRequiresAdmin() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.PAUSER, bob); + + // carol isn't admin — cannot revoke + vm.prank(carol); + vm.expectRevert(IAppAuthority.InvalidRole.selector); + authority.revokeRole(APP_A, IAppAuthority.Role.PAUSER, bob); + + // alice (owner/admin) can revoke + vm.prank(alice); + authority.revokeRole(APP_A, IAppAuthority.Role.PAUSER, bob); + assertFalse(authority.hasRole(APP_A, IAppAuthority.Role.PAUSER, bob)); + } + + // ========== renounceRole ========== + + function test_renounceRole_ownerCannotRenounceAdmin() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); + vm.expectRevert(IAppAuthority.CannotRemoveOwnerAdmin.selector); + authority.renounceRole(APP_A, IAppAuthority.Role.ADMIN); + } + + function test_renounceRole_coAdminCanRenounce() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.ADMIN, bob); + + vm.prank(bob); + authority.renounceRole(APP_A, IAppAuthority.Role.ADMIN); + + assertFalse(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, bob)); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, alice)); + } + + function test_renounceRole_operationalRoleOk() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + vm.prank(alice); + authority.grantRole(APP_A, IAppAuthority.Role.PAUSER, bob); + + vm.prank(bob); + authority.renounceRole(APP_A, IAppAuthority.Role.PAUSER); + + assertFalse(authority.hasRole(APP_A, IAppAuthority.Role.PAUSER, bob)); + } + + // ========== migrateAdmins ========== + + function test_migrateAdmins_consumerOnly() public { + IApp[] memory scopes = new IApp[](1); + address[][] memory admins = new address[][](1); + scopes[0] = APP_A; + admins[0] = new address[](0); + + vm.prank(alice); + vm.expectRevert(IAppAuthority.OnlyConsumer.selector); + authority.migrateAdmins(scopes, admins); + } + + function test_migrateAdmins_seedsAdmins() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + IApp[] memory scopes = new IApp[](1); + address[][] memory admins = new address[][](1); + scopes[0] = APP_A; + admins[0] = new address[](2); + admins[0][0] = bob; + admins[0][1] = carol; + + vm.prank(consumer); + authority.migrateAdmins(scopes, admins); + + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, bob)); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, carol)); + } + + function test_migrateAdmins_idempotent() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + IApp[] memory scopes = new IApp[](1); + address[][] memory admins = new address[][](1); + scopes[0] = APP_A; + admins[0] = new address[](1); + admins[0][0] = bob; + + vm.prank(consumer); + authority.migrateAdmins(scopes, admins); + + // Run again: safe. + vm.prank(consumer); + authority.migrateAdmins(scopes, admins); + + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, bob)); + } + + function test_migrateAdmins_skipsZeroAddress() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + IApp[] memory scopes = new IApp[](1); + address[][] memory admins = new address[][](1); + scopes[0] = APP_A; + admins[0] = new address[](2); + admins[0][0] = address(0); + admins[0][1] = bob; + + vm.prank(consumer); + authority.migrateAdmins(scopes, admins); + + assertFalse(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, address(0))); + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, bob)); + } + + // ========== hasRoleOrAdmin ========== + + function test_hasRoleOrAdmin_adminIsSupersetForOperational() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + + // alice is ADMIN only. PAUSER query still returns true because ADMIN is + // a superset for operational roles. + assertTrue(authority.hasRoleOrAdmin(APP_A, IAppAuthority.Role.PAUSER, alice)); + assertTrue(authority.hasRoleOrAdmin(APP_A, IAppAuthority.Role.DEVELOPER, alice)); + + // Direct role checks are strict. + assertFalse(authority.hasRole(APP_A, IAppAuthority.Role.PAUSER, alice)); + } + + // ========== scope isolation ========== + + function test_scopesAreIsolated() public { + vm.prank(consumer); + authority.initializeScope(APP_A, alice); + vm.prank(consumer); + authority.initializeScope(APP_B, bob); + + // A-scope ADMIN on alice has no bearing on B-scope. + assertTrue(authority.hasRole(APP_A, IAppAuthority.Role.ADMIN, alice)); + assertFalse(authority.hasRole(APP_B, IAppAuthority.Role.ADMIN, alice)); + + // Owner gates are scope-local. + vm.prank(alice); + vm.expectRevert(IAppAuthority.NotScopeOwner.selector); + authority.grantRole(APP_B, IAppAuthority.Role.ADMIN, carol); + } +} diff --git a/test/AppController.t.sol b/test/AppController.t.sol index 5694106..acf9fe4 100644 --- a/test/AppController.t.sol +++ b/test/AppController.t.sol @@ -2,10 +2,14 @@ pragma solidity ^0.8.27; import {IAppController} from "../src/interfaces/IAppController.sol"; +import {IAppAuthority} from "../src/interfaces/IAppAuthority.sol"; +import {AppController} from "../src/AppController.sol"; import {ComputeDeployer} from "./utils/ComputeDeployer.sol"; import {IApp} from "../src/interfaces/IApp.sol"; import {PermissionControllerMixin} from "@eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol"; import {IReleaseManagerTypes} from "@eigenlayer-contracts/src/contracts/interfaces/IReleaseManager.sol"; +import {ICallValidator} from "../src/interfaces/ICallValidator.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; contract AppControllerTest is ComputeDeployer { bytes32 public constant SALT = keccak256("test_salt"); @@ -286,9 +290,11 @@ contract AppControllerTest is ComputeDeployer { rmsRelease: rmsRelease, publicEnv: "", encryptedEnv: "", containerPolicy: emptyPolicy }); - // Try to upgrade as unauthorized user + // Try to upgrade as unauthorized user. Critical ops are owner-gated + // unconditionally now — not even an ADMIN other than the owner can + // trigger an upgrade. vm.prank(user); - vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + vm.expectRevert(IAppController.NotCreator.selector); appController.upgradeApp(app, release); } @@ -384,9 +390,12 @@ contract AppControllerTest is ComputeDeployer { // Verify this app doesn't exist assertEq(uint256(appController.getAppStatus(fakeApp)), uint256(IAppController.AppStatus.NONE)); - // Try to start a non-existent app - should revert with InvalidAppStatus + // Try to start a non-existent app — now fails on the onlyAdmin + // check first (the caller holds no team role on an address whose + // team was never created). This is strictly stronger than the + // pre-RBAC InvalidAppStatus revert. vm.prank(fakeAppAddress); - vm.expectRevert(abi.encodeWithSelector(IAppController.InvalidAppStatus.selector)); + vm.expectRevert(IAppController.InvalidTeamRole.selector); appController.startApp(fakeApp); // Verify status is still NONE @@ -635,34 +644,41 @@ contract AppControllerTest is ComputeDeployer { assertEq(address(otherApps[1]), address(otherApp2)); } - function test_getAppsByCreator_worksWithoutAdminRights() public { + function test_getAppsByDeveloper_filtersByAppAuthorityAdmin() public { + // Developer creates two apps; both auto-seed developer as ADMIN in + // AppAuthority at createApp time. vm.prank(developer); IApp app1 = appController.createApp(keccak256("admin_test_1"), _assembleRelease()); vm.prank(developer); IApp app2 = appController.createApp(keccak256("admin_test_2"), _assembleRelease()); - // Only accept admin on app1, leave app2 without accepting admin - vm.prank(developer); - permissionController.acceptAdmin(address(app1)); - - // getAppsByCreator should return BOTH apps (filters by creator, not admin) + // Both apps list developer as creator AND as ADMIN. (IApp[] memory creatorApps,) = appController.getAppsByCreator(developer, 0, 10); assertEq(creatorApps.length, 2); - assertEq(address(creatorApps[0]), address(app1)); - assertEq(address(creatorApps[1]), address(app2)); - // getAppsByDeveloper should only return app1 (developer only accepted admin on app1) (IApp[] memory devApps,) = appController.getAppsByDeveloper(developer, 0, 10); + assertEq(devApps.length, 2, "developer is ADMIN on every app they created"); + + // Transfer app2 to another owner via the two-step flow. The ADMIN + // rotation happens at accept time. + address newOwner = makeAddr("newOwner"); + _setMaxActiveAppsPerUser(newOwner, 10); + vm.prank(developer); + appController.transferOwnership(app2, newOwner); + vm.prank(newOwner); + appController.acceptOwnership(app2); + + // Now developer is ADMIN only on app1, but remains "creator" on + // app1 only (creator got overwritten on app2 transfer). So + // getAppsByDeveloper reflects the change. + (devApps,) = appController.getAppsByDeveloper(developer, 0, 10); assertEq(devApps.length, 1); assertEq(address(devApps[0]), address(app1)); - // Verify the creator field is set correctly for both apps - assertEq(appController.getAppCreator(app1), developer); - assertEq(appController.getAppCreator(app2), developer); - - // Verify developer is only admin of app1 - assertTrue(permissionController.isAdmin(address(app1), developer)); - assertFalse(permissionController.isAdmin(address(app2), developer)); + // Sanity: app2's new owner is now ADMIN on app2. + (IApp[] memory newOwnerApps,) = appController.getAppsByDeveloper(newOwner, 0, 10); + assertEq(newOwnerApps.length, 1); + assertEq(address(newOwnerApps[0]), address(app2)); } // ========== Helper Functions ========== @@ -776,7 +792,7 @@ contract AppControllerTest is ComputeDeployer { // Try to update metadata as unauthorized user vm.prank(user); - vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + vm.expectRevert(IAppController.InvalidTeamRole.selector); appController.updateAppMetadataURI(app, "https://example.com/metadata"); } @@ -808,9 +824,11 @@ contract AppControllerTest is ComputeDeployer { // Verify this app doesn't exist assertEq(uint256(appController.getAppStatus(fakeApp)), uint256(IAppController.AppStatus.NONE)); - // Try to update metadata for a non-existent app - should revert + // Try to update metadata for a non-existent app — caller holds no + // DEVELOPER/ADMIN role on the fake team, so the outer role gate + // rejects first. Strictly stronger than the pre-RBAC behavior. vm.prank(fakeAppAddress); - vm.expectRevert(abi.encodeWithSelector(IAppController.InvalidAppStatus.selector)); + vm.expectRevert(IAppController.InvalidTeamRole.selector); appController.updateAppMetadataURI(fakeApp, "https://example.com/fake-metadata"); } @@ -1453,4 +1471,644 @@ contract AppControllerTest is ComputeDeployer { assertEq(appController.getActiveAppCount(developer), 0); assertEq(uint256(appController.getAppStatus(app)), uint256(IAppController.AppStatus.TERMINATED)); } + + // ========== Owner-gated critical ops ========== + // + // These tests pin down the runtime invariant that sensitive ops on any + // app are gated on the current owner — not on ADMIN membership. If the + // owner happens to be a Timelock, critical ops naturally flow through + // schedule → execute; if a Safe, through the multisig; if an EOA, + // directly. AppController does not classify the owner contract. + + function test_upgradeApp_blocksCoAdminNonOwner() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + // Owner grants ADMIN to a co-admin. Under the owner-gated model, + // holding ADMIN is the strongest authority a co-admin could + // plausibly have — but ADMIN does NOT convey upgrade power. + address coAdmin = makeAddr("coAdmin"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, coAdmin); + + // Direct upgrade from the co-admin MUST revert with NotCreator — + // the owner-gate blocks everyone except the current owner, regardless + // of ADMIN membership. + vm.prank(coAdmin); + vm.expectRevert(IAppController.NotCreator.selector); + appController.upgradeApp(app, _assembleRelease()); + + // The owner can still upgrade directly. + vm.prank(developer); + appController.upgradeApp(app, _assembleRelease()); + } + + function test_terminateApp_blocksCoAdminNonOwner() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address coAdmin = makeAddr("coAdmin"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, coAdmin); + + vm.prank(coAdmin); + vm.expectRevert(IAppController.NotCreator.selector); + appController.terminateApp(app); + + // Owner can terminate. + vm.prank(developer); + appController.terminateApp(app); + assertEq(uint256(appController.getAppStatus(app)), uint256(IAppController.AppStatus.TERMINATED)); + } + + function test_terminateAppByAdmin_worksOnAnyApp() public { + // Protocol admin can terminate any app uniformly. No longer gated on + // governance type. If the protocol wants its own termination actions + // to be delay-gated, that's an operational decision about the UAM + // admin multisig — not an AppController concern. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + vm.prank(admin); + appController.terminateAppByAdmin(app); + + assertEq(uint256(appController.getAppStatus(app)), uint256(IAppController.AppStatus.TERMINATED)); + } + + // ========== transferOwnership (two-step) ========== + + event AppOwnershipTransferred(IApp indexed app, address indexed previousOwner, address indexed newOwner); + event OwnershipTransferProposed(IApp indexed app, address indexed currentOwner, address indexed proposedOwner); + event OwnershipTransferCancelled(IApp indexed app, address indexed currentOwner, address indexed cancelledOwner); + + function test_transferOwnership_proposeEmitsEvent() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address newOwner = makeAddr("newOwner"); + + vm.expectEmit(true, true, true, true); + emit OwnershipTransferProposed(app, developer, newOwner); + + vm.prank(developer); + appController.transferOwnership(app, newOwner); + + // Propose alone does NOT change creator; it only records the pending. + assertEq(appController.getAppCreator(app), developer); + assertEq(appController.getPendingOwner(app), newOwner); + } + + function test_transferOwnership_fullTwoStepFlow() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address newOwner = makeAddr("newOwner"); + _setMaxActiveAppsPerUser(newOwner, 10); + + vm.prank(developer); + appController.transferOwnership(app, newOwner); + + vm.expectEmit(true, true, true, true); + emit AppOwnershipTransferred(app, developer, newOwner); + + vm.prank(newOwner); + appController.acceptOwnership(app); + + assertEq(appController.getAppCreator(app), newOwner); + assertEq(appController.getPendingOwner(app), address(0)); + } + + function test_acceptOwnership_rejectsNonPendingCaller() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address proposed = makeAddr("proposed"); + vm.prank(developer); + appController.transferOwnership(app, proposed); + + address imposter = makeAddr("imposter"); + vm.prank(imposter); + vm.expectRevert(IAppController.NotPendingOwner.selector); + appController.acceptOwnership(app); + } + + function test_acceptOwnership_rejectsWhenNoProposalPending() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + // No proposal — any accept attempt reverts because pendingOwner == 0. + vm.prank(developer); + vm.expectRevert(IAppController.NotPendingOwner.selector); + appController.acceptOwnership(app); + } + + function test_acceptOwnership_enforcesReceiverQuota() public { + // Receiver with no quota must not be able to accept a DEFAULT-billed + // active app — the counter bump would push them past maxActiveApps + // (which defaults to 0 for fresh accounts). + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address broke = makeAddr("broke"); // maxActiveApps == 0 (not provisioned) + vm.prank(developer); + appController.transferOwnership(app, broke); + + vm.prank(broke); + vm.expectRevert(IAppController.MaxActiveAppsExceeded.selector); + appController.acceptOwnership(app); + + // Once provisioned, accept succeeds. + _setMaxActiveAppsPerUser(broke, 1); + vm.prank(broke); + appController.acceptOwnership(app); + assertEq(appController.getAppCreator(app), broke); + } + + function test_transferOwnership_blocksCoAdminNonOwner() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + // Owner grants ADMIN to coAdmin. ADMIN is the strongest role a + // co-admin could plausibly have; the owner-gate must still block + // them from proposing a transfer. + address coAdmin = makeAddr("coAdmin"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, coAdmin); + + address attacker = makeAddr("attacker"); + _setMaxActiveAppsPerUser(attacker, 10); + + vm.prank(coAdmin); + vm.expectRevert(IAppController.NotCreator.selector); + appController.transferOwnership(app, attacker); + + // Owner can propose + attacker-as-receiver accepts. + vm.prank(developer); + appController.transferOwnership(app, attacker); + vm.prank(attacker); + appController.acceptOwnership(app); + assertEq(appController.getAppCreator(app), attacker); + } + + function test_transferOwnership_revertsZeroAddress() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + vm.prank(developer); + vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + appController.transferOwnership(app, address(0)); + } + + function test_transferOwnership_cancelByOwner() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address proposed = makeAddr("proposed"); + vm.prank(developer); + appController.transferOwnership(app, proposed); + assertEq(appController.getPendingOwner(app), proposed); + + vm.expectEmit(true, true, true, true); + emit OwnershipTransferCancelled(app, developer, proposed); + + vm.prank(developer); + appController.cancelOwnershipTransfer(app); + assertEq(appController.getPendingOwner(app), address(0)); + + // Previously-proposed address can no longer accept. + vm.prank(proposed); + vm.expectRevert(IAppController.NotPendingOwner.selector); + appController.acceptOwnership(app); + } + + function test_transferOwnership_resupercedesOldProposal() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address first = makeAddr("first"); + vm.prank(developer); + appController.transferOwnership(app, first); + + address second = makeAddr("second"); + _setMaxActiveAppsPerUser(second, 10); + + // Re-proposing should emit both Cancelled(first) and Proposed(second). + vm.expectEmit(true, true, true, true); + emit OwnershipTransferCancelled(app, developer, first); + vm.expectEmit(true, true, true, true); + emit OwnershipTransferProposed(app, developer, second); + + vm.prank(developer); + appController.transferOwnership(app, second); + + // First can no longer accept; second can. + vm.prank(first); + vm.expectRevert(IAppController.NotPendingOwner.selector); + appController.acceptOwnership(app); + + vm.prank(second); + appController.acceptOwnership(app); + assertEq(appController.getAppCreator(app), second); + } + + function test_transferOwnership_ownerRetainsAuthorityDuringPending() public { + // Pending proposal does NOT freeze the current owner's authority. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address proposed = makeAddr("proposed"); + vm.prank(developer); + appController.transferOwnership(app, proposed); + + // Owner can still upgrade. Receiver has not accepted yet. + vm.prank(developer); + appController.upgradeApp(app, _assembleRelease()); + + // Owner can still terminate (which invalidates the pending). + vm.prank(developer); + appController.terminateApp(app); + + // Receiver cannot accept a terminated app (appExists check fails + // at the top of acceptOwnership? Actually appExists allows TERMINATED; + // the accept will proceed but the downstream billing migration is + // a no-op since _isActive is false. The transfer of ownership on a + // terminated app is a degenerate but non-harmful case.) + // We only assert that the pending pointer wasn't cleared by terminate. + assertEq(appController.getPendingOwner(app), proposed); + } + + function test_transferOwnership_defaultBilling_movesActiveCounter() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address newOwner = makeAddr("newOwner"); + _setMaxActiveAppsPerUser(newOwner, 10); + + uint32 beforeDev = appController.getActiveAppCount(developer); + uint32 beforeNew = appController.getActiveAppCount(newOwner); + + vm.prank(developer); + appController.transferOwnership(app, newOwner); + // Propose alone must NOT touch counters — that's the whole point + // of the two-step flow. + assertEq(appController.getActiveAppCount(developer), beforeDev); + assertEq(appController.getActiveAppCount(newOwner), beforeNew); + + vm.prank(newOwner); + appController.acceptOwnership(app); + + // Accept migrates the counter. + assertEq(appController.getActiveAppCount(developer), beforeDev - 1); + assertEq(appController.getActiveAppCount(newOwner), beforeNew + 1); + } + + function test_transferOwnership_isolatedBilling_doesNotMoveCounter() public { + _setMaxActiveAppsPerUser(address(appController.calculateAppId(developer, SALT)), 10); + + vm.prank(developer); + IApp app = appController.createAppWithIsolatedBilling(SALT, _assembleRelease()); + + uint32 beforeApp = appController.getActiveAppCount(address(app)); + uint32 beforeDev = appController.getActiveAppCount(developer); + + address newOwner = makeAddr("newOwner"); + // Isolated billing doesn't bill the user, so receiver quota is + // irrelevant — no provisioning needed. + vm.prank(developer); + appController.transferOwnership(app, newOwner); + vm.prank(newOwner); + appController.acceptOwnership(app); + + assertEq(appController.getActiveAppCount(address(app)), beforeApp); + assertEq(appController.getActiveAppCount(developer), beforeDev); + assertEq(appController.getActiveAppCount(newOwner), 0); + } + + // ========== ICallValidator / canCall ========== + + function test_supportsInterface_advertisesCallValidator() public { + AppController ac = AppController(address(appController)); + bytes4 callValidatorId = type(ICallValidator).interfaceId; + bytes4 erc165Id = type(IERC165).interfaceId; + + assertTrue(ac.supportsInterface(callValidatorId), "AppController must advertise ICallValidator"); + assertTrue(ac.supportsInterface(erc165Id), "AppController must advertise IERC165"); + assertFalse(ac.supportsInterface(0xdeadbeef), "Unrelated interface must return false"); + } + + function test_canCall_returnsTrueForShortCalldata() public view { + // Fallback: anything under 4 bytes can't be a meaningful call; defer. + assertTrue(ICallValidator(address(appController)).canCall(address(this), "")); + assertTrue(ICallValidator(address(appController)).canCall(address(this), hex"00")); + } + + function test_canCall_rejectsNonOwnerUpgradeApp() public { + // Owner-gated model: canCall rejects any non-owner schedule of a + // critical op. Schedule-time rejection matches the runtime + // onlyCreator gate. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + bytes memory callData = abi.encodeWithSelector(IAppController.upgradeApp.selector, app, _assembleRelease()); + + assertFalse( + ICallValidator(address(appController)).canCall(makeAddr("anyone"), callData), + "canCall must reject non-owner schedule" + ); + assertTrue( + ICallValidator(address(appController)).canCall(developer, callData), "canCall must accept owner schedule" + ); + } + + // ========== Team-role RBAC ========== + + function test_createApp_grantsAdminRoleToCreator() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + assertTrue( + appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, developer), "creator must be seeded as ADMIN on create" + ); + } + + function test_grantTeamRole_asAdmin() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address pauser = makeAddr("pauser"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.PAUSER, pauser); + + assertTrue(appAuthority.hasRole(app, IAppAuthority.Role.PAUSER, pauser)); + } + + function test_grantTeamRole_nonAdminCannotGrant() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address outsider = makeAddr("outsider"); + vm.prank(outsider); + vm.expectRevert(IAppAuthority.InvalidRole.selector); + appAuthority.grantRole(app, IAppAuthority.Role.PAUSER, outsider); + } + + function test_grantTeamRole_operationalRoleNotOwnerGated() public { + // PAUSER/DEVELOPER grants are NOT owner-gated — any existing ADMIN + // can grant these bounded operational roles. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + // Give coAdmin ADMIN, then confirm coAdmin can grant PAUSER freely. + address coAdmin = makeAddr("coAdmin"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, coAdmin); + + address pauser = makeAddr("pauser"); + vm.prank(coAdmin); + appAuthority.grantRole(app, IAppAuthority.Role.PAUSER, pauser); + assertTrue(appAuthority.hasRole(app, IAppAuthority.Role.PAUSER, pauser)); + } + + function test_grantTeamRole_adminGrantGatedByOwner() public { + // Owner-gated model: ADMIN grants are owner-only regardless of + // whether the owner is an EOA, Safe, or Timelock. A co-ADMIN + // cannot mint another ADMIN. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address coAdmin = makeAddr("coAdmin"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, coAdmin); + + // coAdmin has ADMIN but is NOT the owner: grant attempt must fail. + address usurper = makeAddr("usurper"); + vm.prank(coAdmin); + vm.expectRevert(IAppAuthority.NotScopeOwner.selector); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, usurper); + + // The owner can still grant. + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, usurper); + assertTrue(appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, usurper)); + } + + function test_revokeTeamRole_adminRevokeGatedByOwner() public { + // Mirror of grant: revoking ADMIN is owner-only unconditionally. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address coAdmin = makeAddr("coAdmin"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.ADMIN, coAdmin); + + // coAdmin attempts to revoke the Timelock — MUST fail. + vm.prank(coAdmin); + vm.expectRevert(IAppAuthority.NotScopeOwner.selector); + appAuthority.revokeRole(app, IAppAuthority.Role.ADMIN, developer); + + // The Timelock (creator) can revoke coAdmin. + vm.prank(developer); + appAuthority.revokeRole(app, IAppAuthority.Role.ADMIN, coAdmin); + assertFalse(appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, coAdmin)); + } + + function test_revokeTeamRole_creatorCannotRevokeSelf() public { + // The creator is the only address that can call revokeTeamRole(ADMIN), + // but they cannot revoke their own ADMIN — the invariant + // `creator ∈ ADMIN` must hold. transferOwnership is the only path that + // rotates. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + vm.prank(developer); + vm.expectRevert(IAppAuthority.CannotRemoveOwnerAdmin.selector); + appAuthority.revokeRole(app, IAppAuthority.Role.ADMIN, developer); + } + + function test_renounceTeamRole_creatorCannotRenounceAdmin() public { + // The creator cannot drop out of ADMIN via renounce — doing so would + // leave the owner slot pointing at an address that isn't ADMIN + // (though ADMIN is operational-only in this model, keeping the + // invariant still matters for startApp and role-management clarity). + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + vm.prank(developer); + vm.expectRevert(IAppAuthority.CannotRemoveOwnerAdmin.selector); + appAuthority.renounceRole(app, IAppAuthority.Role.ADMIN); + } + + function test_renounceTeamRole_operationalRoleOk() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address pauser = makeAddr("pauser"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.PAUSER, pauser); + + vm.prank(pauser); + appAuthority.renounceRole(app, IAppAuthority.Role.PAUSER); + assertFalse(appAuthority.hasRole(app, IAppAuthority.Role.PAUSER, pauser)); + } + + function test_stopApp_pauserCanCall() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address pauser = makeAddr("pauser"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.PAUSER, pauser); + + vm.prank(pauser); + appController.stopApp(app); + assertEq(uint256(appController.getAppStatus(app)), uint256(IAppController.AppStatus.STOPPED)); + } + + function test_stopApp_developerCannotCall() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address dev = makeAddr("dev"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.DEVELOPER, dev); + + vm.prank(dev); + vm.expectRevert(IAppController.InvalidTeamRole.selector); + appController.stopApp(app); + } + + function test_updateAppMetadataURI_developerCanCall() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address dev = makeAddr("dev"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.DEVELOPER, dev); + + vm.prank(dev); + appController.updateAppMetadataURI(app, "ipfs://new"); + } + + function test_updateAppMetadataURI_pauserCannotCall() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address pauser = makeAddr("pauser"); + vm.prank(developer); + appAuthority.grantRole(app, IAppAuthority.Role.PAUSER, pauser); + + vm.prank(pauser); + vm.expectRevert(IAppController.InvalidTeamRole.selector); + appController.updateAppMetadataURI(app, "ipfs://new"); + } + + function test_transferOwnership_grantsAdminToNewOwner() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address newOwner = makeAddr("newOwner"); + _setMaxActiveAppsPerUser(newOwner, 10); + + vm.prank(developer); + appController.transferOwnership(app, newOwner); + vm.prank(newOwner); + appController.acceptOwnership(app); + + assertTrue( + appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, newOwner), "new owner must be granted ADMIN on accept" + ); + } + + function test_transferOwnership_removesPreviousOwnerFromAdmin() public { + // Fix for audit A-3 / V-10: the previous owner must be removed from + // the ADMIN set on ownership handoff. Otherwise an old owner (EOA, + // Safe, or old Timelock post-handoff) retains operational powers + // and, if they remain ADMIN while the new owner holds a weaker key, + // they can re-grab the app. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address newOwner = makeAddr("newOwner"); + _setMaxActiveAppsPerUser(newOwner, 10); + + vm.prank(developer); + appController.transferOwnership(app, newOwner); + vm.prank(newOwner); + appController.acceptOwnership(app); + + assertFalse( + appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, developer), + "previous owner must be removed from ADMIN on accept" + ); + assertTrue(appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, newOwner), "new owner must still be ADMIN"); + } + + function test_transferOwnership_previousOwnerLosesCriticalPower() public { + // After the transfer completes (accept), the previous owner cannot + // perform critical ops even though they used to. This is the direct + // user-visible consequence of the owner-gate + ADMIN cleanup. + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + address newOwner = makeAddr("newOwner"); + _setMaxActiveAppsPerUser(newOwner, 10); + + vm.prank(developer); + appController.transferOwnership(app, newOwner); + vm.prank(newOwner); + appController.acceptOwnership(app); + + // Previous owner cannot upgrade, terminate, or propose a new transfer. + vm.prank(developer); + vm.expectRevert(IAppController.NotCreator.selector); + appController.upgradeApp(app, _assembleRelease()); + + vm.prank(developer); + vm.expectRevert(IAppController.NotCreator.selector); + appController.terminateApp(app); + + vm.prank(developer); + vm.expectRevert(IAppController.NotCreator.selector); + appController.transferOwnership(app, makeAddr("someoneElse")); + } + + function test_migrateAdmins_seedsAdminFromPermissionController() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + // Set up two UAM admins who are NOT yet in the team-role set. + address pcAdminA = makeAddr("pcAdminA"); + address pcAdminB = makeAddr("pcAdminB"); + vm.prank(developer); + permissionController.acceptAdmin(address(app)); + vm.prank(developer); + permissionController.addPendingAdmin(address(app), pcAdminA); + vm.prank(pcAdminA); + permissionController.acceptAdmin(address(app)); + vm.prank(developer); + permissionController.addPendingAdmin(address(app), pcAdminB); + vm.prank(pcAdminB); + permissionController.acceptAdmin(address(app)); + + assertFalse(appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, pcAdminA)); + assertFalse(appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, pcAdminB)); + + IApp[] memory apps = new IApp[](1); + apps[0] = app; + vm.prank(admin); + appController.migrateAppsToAppAuthority(apps); + + assertTrue(appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, pcAdminA)); + assertTrue(appAuthority.hasRole(app, IAppAuthority.Role.ADMIN, pcAdminB)); + } + + function test_migrateAdmins_callerMustBePlatformAdmin() public { + vm.prank(developer); + IApp app = appController.createApp(SALT, _assembleRelease()); + + IApp[] memory apps = new IApp[](1); + apps[0] = app; + vm.prank(user); + vm.expectRevert(); + appController.migrateAppsToAppAuthority(apps); + } } diff --git a/test/TimelockControllerImplValidation.t.sol b/test/TimelockControllerImplValidation.t.sol new file mode 100644 index 0000000..00ace0f --- /dev/null +++ b/test/TimelockControllerImplValidation.t.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {TimelockControllerImpl} from "../src/governance/TimelockControllerImpl.sol"; +import {ICallValidator} from "../src/interfaces/ICallValidator.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +/// @dev Behaviors exercised here come from TimelockControllerImpl._validateTarget. +/// We spin up a real TimelockControllerImpl, grant the deployer PROPOSER_ROLE, +/// and call schedule() against various mock targets that exhibit specific +/// misbehaviors (reverts, returndata-bombs, OOG in canCall, honest rejects). +contract TimelockControllerImplValidationTest is Test { + TimelockControllerImpl internal timelock; + address internal proposer = address(0xA11CE); + + function setUp() public { + TimelockControllerImpl impl = new TimelockControllerImpl(); + // Impl has initializers disabled; deploy via clone to call initialize. + timelock = TimelockControllerImpl(payable(Clones.clone(address(impl)))); + + address[] memory proposers = new address[](1); + proposers[0] = proposer; + address[] memory executors = new address[](1); + executors[0] = proposer; + timelock.initialize(60, proposers, executors, address(0)); + } + + function _schedule(address target, bytes memory data) internal { + vm.prank(proposer); + timelock.schedule(target, 0, data, bytes32(0), bytes32(0), 60); + } + + /// --- Target that does NOT advertise ICallValidator at all --- + + function test_schedule_targetWithoutERC165_isAllowedThrough() public { + InertContract inert = new InertContract(); + _schedule(address(inert), hex"12345678"); + } + + function test_schedule_EOA_isAllowedThrough() public { + // Zero-code address fast path. + _schedule(makeAddr("eoa"), hex"12345678"); + } + + /// --- Target that returns false from supportsInterface --- + + function test_schedule_targetClaimsNoInterface_isAllowedThrough() public { + ERC165SaysNo liar = new ERC165SaysNo(); + _schedule(address(liar), hex"12345678"); + } + + /// --- Target that advertises ICallValidator --- + + function test_schedule_validatorAllows_schedulesSuccessfully() public { + AllowingValidator v = new AllowingValidator(); + _schedule(address(v), hex"12345678"); + } + + function test_schedule_validatorRejects_reverts() public { + RejectingValidator v = new RejectingValidator(); + vm.expectRevert(bytes("TimelockController: target rejected the call")); + _schedule(address(v), hex"12345678"); + } + + function test_schedule_validatorReverts_failsClosed() public { + // Advertises the interface, but canCall reverts. MUST reject rather than + // silently pass through as "no interface" — that was the pre-hardening + // bug the audit called out (G-3 / V-4 / D-2 / P-9). + RevertingValidator v = new RevertingValidator(); + vm.expectRevert(bytes("TimelockController: canCall reverted")); + _schedule(address(v), hex"12345678"); + } + + function test_schedule_validatorOOG_failsClosed() public { + // Validator runs an infinite loop; the bounded gas on the staticcall + // cuts it off and the scheduling call fails with "canCall reverted". + InfiniteLoopValidator v = new InfiniteLoopValidator(); + vm.expectRevert(bytes("TimelockController: canCall reverted")); + _schedule(address(v), hex"12345678"); + } + + function test_schedule_validatorReturndataBomb_failsClosed() public { + // Validator returns a huge blob. The hardened validator reads only + // the first 32 bytes; abi.decode(bool) succeeds with whatever the + // first 32 bytes encode (here: 1 → allowed), so this should succeed + // without OOGing the outer call. + ReturndataBombValidator v = new ReturndataBombValidator(); + _schedule(address(v), hex"12345678"); + } + + // ========== Pending-ops list cap (D-1) ========== + + function test_schedule_capsPendingOperationsList() public { + // Fill the pending set to its max (128) by scheduling 128 distinct ops + // against an inert target that short-circuits validation. Each gets a + // unique salt so hashOperation produces distinct ids. + InertContract target = new InertContract(); + uint256 cap = 32; // must match TimelockControllerImpl.MAX_PENDING_OPS + + for (uint256 i = 0; i < cap; i++) { + vm.prank(proposer); + timelock.schedule(address(target), 0, hex"dead", bytes32(0), bytes32(i + 1), 60); + } + + // The 129th schedule must revert with TooManyPendingOperations. + vm.prank(proposer); + vm.expectRevert(abi.encodeWithSignature("TooManyPendingOperations()")); + timelock.schedule(address(target), 0, hex"dead", bytes32(0), bytes32(uint256(cap + 1)), 60); + + // After canceling one op, scheduling a new one must succeed again — + // the cap is a ceiling on concurrently-pending, not a permanent limit. + bytes32 firstId = timelock.hashOperation(address(target), 0, hex"dead", bytes32(0), bytes32(uint256(1))); + vm.prank(proposer); + timelock.cancel(firstId); + + vm.prank(proposer); + timelock.schedule(address(target), 0, hex"dead", bytes32(0), bytes32(uint256(cap + 2)), 60); + } +} + +/// --- Mock targets --- + +contract InertContract { + // No ERC-165 — calls to supportsInterface revert with no reason. + // Used to verify fast-path for non-validator targets. + + } + +contract ERC165SaysNo is IERC165 { + function supportsInterface(bytes4) external pure returns (bool) { + return false; + } +} + +contract AllowingValidator is ICallValidator { + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(ICallValidator).interfaceId || interfaceId == type(IERC165).interfaceId; + } + + function canCall(address, bytes calldata) external pure returns (bool) { + return true; + } +} + +contract RejectingValidator is ICallValidator { + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(ICallValidator).interfaceId || interfaceId == type(IERC165).interfaceId; + } + + function canCall(address, bytes calldata) external pure returns (bool) { + return false; + } +} + +contract RevertingValidator is ICallValidator { + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(ICallValidator).interfaceId || interfaceId == type(IERC165).interfaceId; + } + + function canCall(address, bytes calldata) external pure returns (bool) { + revert("no"); + } +} + +contract InfiniteLoopValidator is ICallValidator { + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(ICallValidator).interfaceId || interfaceId == type(IERC165).interfaceId; + } + + function canCall(address, bytes calldata) external view returns (bool) { + // staticcall so state writes would revert; burn gas in a tight loop. + uint256 i; + while (true) { + i++; + } + return true; // unreachable + } +} + +contract ReturndataBombValidator is ICallValidator { + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(ICallValidator).interfaceId || interfaceId == type(IERC165).interfaceId; + } + + function canCall(address, bytes calldata) external pure returns (bool) { + // Return true (first 32 bytes encode bool(true)), then a padding blob. + // The hardened validator should only read the first 32 bytes. + bytes memory blob = new bytes(1024); + blob[31] = 0x01; // decodes as true + assembly { + return(add(blob, 32), mload(blob)) + } + } +} diff --git a/test/utils/ComputeDeployer.sol b/test/utils/ComputeDeployer.sol index 37ce85d..783e148 100644 --- a/test/utils/ComputeDeployer.sol +++ b/test/utils/ComputeDeployer.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import {Deploy} from "../../script/Deploy.s.sol"; import {Parser} from "../../script/Parser.s.sol"; import {IAppController} from "../../src/interfaces/IAppController.sol"; +import {IAppAuthority} from "../../src/interfaces/IAppAuthority.sol"; import {ComputeAVSRegistrar} from "../../src/ComputeAVSRegistrar.sol"; import {ComputeOperator} from "../../src/ComputeOperator.sol"; import {ImageAllowlist} from "../../src/ImageAllowlist.sol"; @@ -27,6 +28,7 @@ contract ComputeDeployer is Test { address public admin = makeAddr("admin"); IAppController public appController; + IAppAuthority public appAuthority; PermissionController public permissionController; ReleaseManager public releaseManager; AllocationManager public allocationManager; @@ -66,6 +68,7 @@ contract ComputeDeployer is Test { Parser.DeployedContracts memory deployed = deployer.deployForTesting(params); appController = deployed.appController; + appAuthority = deployed.appAuthority; computeAVSRegistrar = ComputeAVSRegistrar(address(deployed.computeAVSRegistrar)); computeOperator = ComputeOperator(address(deployed.computeOperator)); imageAllowlist = ImageAllowlist(address(deployed.imageAllowlist));