Update Security Check
Context
Aragon DAOs and the plugins installed can be updated via proposals. Upon execution, the proposal will call the DAO's execute
function and execute whatever action are specified inside its action list.
Accordingly, proposals must be checked thoroughly, especially when it comes to DAO or plugin updates. This happens automatically on the Aragon App frontend.
All proposals scheduled in the DAO are checked via their proposal ID. A proposal is identified as an update proposals if it contains
- one call to either
- one or more calls to
applyUpdate(address _dao, ApplyUpdateParams _params)
If a proposal is identified as an update proposal, we conduct general and specific checks on it.
General Proposal Checks
After a proposal was identified as an update proposal, we check that only allowed actions are part of the action list and that none of them is allowed to fail.
Error | Explanation |
---|---|
"invalidActions" | The proposal contains unexpected actions that are not allowed for update proposals. |
"nonZeroAllowFailureMapValue" | The allow failure map is not zero (see the Aragon OSX docs). |
"proposalNotFound" | The proposal could not be found. |
Action-Specific Checks
After the general checks, we check every action in the Action[]
array separately. An action has three fields see:
/// @notice The action struct to be consumed by the DAO's `execute` function resulting in an external call.
/// @param to The address to call.
/// @param value The native token value to be sent with the call.
/// @param data The bytes-encoded function selector and calldata for the call.
struct Action {
address to;
uint256 value;
bytes data;
}
In the following, we explain the action specific checks.
DAO Update
For DAO updates, we expect single action at position 0 of the action array. This action, must call
upgradeTo(address newImplementation)
ORupgradeToAndCall(address newImplementation, bytes memory data)
Checking the upgradeTo
Calldata
Item | Error | Explanation |
---|---|---|
to | "invalidToAddress" | The to address of the action must be the DAO contract. |
value | "nonZeroCallValue" | The value native token value send with the call must be zero. |
data | "invalidActions" | The first 4 bytes must match the upgradeTo function selector (see General Proposal Checks). |
"invalidUpgradeToImplementationAddress" | The newImplementation address must match with a newer DAO implementation contract developed by Aragon. |
Checking the Action data
calling upgradeToAndCall
Item | Error | Explanation |
---|---|---|
to | "invalidToAddress" | The to address of the action must be the DAO contract. |
value | "nonZeroCallValue" | The value native token value send with the call must be zero. |
data | "invalidActions" | The first 4 bytes must match the upgradeToAndCall function selector (see General Proposal Checks). |
"invalidUpgradeToAndCallImplementationAddress" | The newImplementation address must match with a newer DAO implementation contract developed by Aragon. | |
"invalidUpgradeToAndCallData" | The data passed into upgradeToAndCall must call the initializeFrom(uint8[3] _previousProtocolVersion, bytes _initData) function. The first 96 bytes of data must be occupied by an uint[3] _previousProtocolVersion semantic version number. The sub _initData must be empty for the current Aragon updates. | |
"invalidUpgradeToAndCallVersion" | uint[3] _previousProtocolVersion must match with the semantic version number of the DAO the upgrade is transitioning from. |
Plugin Update
For each plugin update, we expect a block of associated actions. There can be multiple, independent plugin updates happening in one update proposal. We expect two types of blocks:
[
grant({_where: plugin, _who: pluginSetupProcessor, _permissionId: UPGRADE_PLUGIN_PERMISSION_ID}),
applyUpdate({_dao: dao, _params: applyUpdateParams}),
revoke({_where: plugin, _who: pluginSetupProcessor, _permissionId: UPGRADE_PLUGIN_PERMISSION_ID})
]
or
[
grant({_where: plugin, _who: pluginSetupProcessor, _permissionId: UPGRADE_PLUGIN_PERMISSION_ID}),
grant({_where: dao, _who: pluginSetupProcessor, _permissionId: ROOT_PERMISSION_ID}),
applyUpdate({_dao: dao, _params: applyUpdateParams}),
revoke({_where: dao, _who: pluginSetupProcessor, _permissionId: ROOT_PERMISSION_ID}),
revoke({_where: plugin, _who: pluginSetupProcessor, _permissionId: UPGRADE_PLUGIN_PERMISSION_ID})
]
Mandatory applyUpdate
Call
Each block being related to a plugin update must contain an action calling the applyUpdate(address _dao, ApplyUpdateParams _params)
function exactly once.
This action is composed as follows:
This action calls the applyUpdate(address _dao, ApplyUpdateParams _params)
function
Action field | Error | Explanation |
---|---|---|
to | "invalidToAddress" | The to address of the action must be Aragon's PluginSetupProcessor contract. |
value | "nonZeroApplyUpdateCallValue" | The value send with the call must be zero. |
data | "invalidActions" | The first 4 bytes must match the applyUpdate function selector (see General Proposal Checks). |
"invalidData" | The bytes must be decodable as specified in the plugins _metadata [see our specs][build-metadata] (currently we skip this). | |
"invalidPluginRepoMetadata" | The [build-metadata ][build-metadata] could not be found or is incorrectly formatted. | |
"pluginNotInstalled" | The plugin address referenced in the _params is not installed. | |
"missingPluginRepo" | The plugin repo referenced in the _params is not existing. | |
"notAragonPluginRepo" | The plugin repo referenced in the _params is not an Aragon plugin repo. | |
"missingPluginPreparation" | The update was not prepared in Aragon's PluginSetupProcessor contract. | |
"updateToOlderOrSameBuild" | The update wants to transition to the same or an older build. | |
"updateToIncompatibleRelease" | The update wants to transition to a different, incompatible release. |
Mandatory grant
/revoke
UPGRADE_PLUGIN_PERMISSION
Calls
The applyUpdate
action must be wrapped by grant
and revoke
actions:
Action field | Error | Explanation |
---|---|---|
to | "invalidGrantUpgradePluginPermissionToAddress" | The to address must be the DAO contract. |
value | "nonZeroGrantUpgradePluginPermissionCallValue" | The value send with the call must be zero. |
"nonZeroRevokeUpgradePluginPermissionCallValue" | " | |
data | "invalidActions" | The first 4 bytes must match the grant or revoke function selector (see General Proposal Checks). |
"invalidGrantUpgradePluginPermissionWhereAddress" | The where address must be the plugin proxy contract. | |
"invalidRevokeUpgradePluginPermissionWhereAddress" | " | |
"invalidGrantUpgradePluginPermissionWhoAddress" | The who address must be Aragon's PluginSetupProcessor contract. | |
"invalidRevokeUpgradePluginPermissionWhoAddress" | " | |
"invalidGrantUpgradePluginPermissionPermissionId" | permissionId must be keccak256("UPGRADE_PLUGIN_PERMISSION") . | |
"invalidRevokeUpgradePluginPermissionPermissionId" | " | |
"invalidGrantUpgradePluginPermissionPermissionName" | permissionName must be UPGRADE_PLUGIN_PERMISSION . | |
"invalidRevokeUpgradePluginPermissionPermissionName" | " |
Optional grant
/revoke
ROOT_PERMISSION
Calls
The applyUpdate
action CAN be wrapped by grant
and revoke
actions:
Action field | Error | Explanation |
---|---|---|
to | "invalidGrantRootPermissionToAddress" | The to address must be the DAO contract. |
value | "nonZeroGrantRootPermissionCallValue" | The value send with the call must be zero. |
"nonZeroRevokeRootPermissionCallValue" | The value send with the call must be zero. | |
data | "invalidActions" | The first 4 bytes must match the grant or revoke function selector (see General Proposal Checks). |
"invalidGrantRootPermissionWhereAddress" | The where address must be the DAO contract. | |
"invalidRevokeRootPermissionWhereAddress" | " | |
"invalidGrantRootPermissionWhoAddress" | The who address must be Aragon's PluginSetupProcessor . | |
"invalidRevokeRootPermissionWhoAddress" | " | |
"invalidGrantRootPermissionPermissionId" | The permissionId must be keccak256("ROOT_PERMISSION") . | |
"invalidRevokeRootPermissionPermissionId" | " | |
"invalidGrantRootPermissionPermissionName" | The permissionName must be ROOT_PERMISSION . | |
"invalidRevokeRootPermissionPermissionName" | " |