Skip to content

Commit

Permalink
New plugin update approach (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jør∂¡ committed Nov 22, 2023
1 parent 20898c5 commit b048e9d
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 48 deletions.
85 changes: 37 additions & 48 deletions packages/contracts/src/GovernancePluginsSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol";
import {DAO} from "@aragon/osx/core/dao/DAO.sol";
import {IDAO} from "@aragon/osx/core/dao/IDAO.sol";
import {PluginSetup, IPluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol";
import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol";
import {MemberAccessPlugin} from "./MemberAccessPlugin.sol";
import {MemberAccessExecuteCondition} from "./MemberAccessExecuteCondition.sol";
import {OnlyPluginUpgraderCondition} from "./OnlyPluginUpgraderCondition.sol";
import {MainVotingPlugin} from "./MainVotingPlugin.sol";
import {MajorityVotingBase} from "@aragon/osx/plugins/governance/majority-voting/MajorityVotingBase.sol";

Expand All @@ -16,11 +18,13 @@ import {MajorityVotingBase} from "@aragon/osx/plugins/governance/majority-voting
contract GovernancePluginsSetup is PluginSetup {
address private immutable mainVotingPluginImplementation;
address public immutable memberAccessPluginImplementation;
address private immutable pluginSetupProcessor;

/// @notice Thrown when the array of helpers does not have the correct size
error InvalidHelpers(uint256 actualLength);

constructor() {
constructor(PluginSetupProcessor _pluginSetupProcessor) {
pluginSetupProcessor = address(_pluginSetupProcessor);
mainVotingPluginImplementation = address(new MainVotingPlugin());
memberAccessPluginImplementation = address(new MemberAccessPlugin());
}
Expand All @@ -30,7 +34,7 @@ contract GovernancePluginsSetup is PluginSetup {
function prepareInstallation(
address _dao,
bytes memory _data
) external returns (address mainVotingPlugin, PreparedSetupData memory preparedSetupData) {
) external returns (address _mainVotingPlugin, PreparedSetupData memory _preparedSetupData) {
// Decode the custom installation parameters
(
MajorityVotingBase.VotingSettings memory _votingSettings,
Expand All @@ -40,7 +44,7 @@ contract GovernancePluginsSetup is PluginSetup {
) = decodeInstallationParams(_data);

// Deploy the main voting plugin
mainVotingPlugin = createERC1967Proxy(
_mainVotingPlugin = createERC1967Proxy(
mainVotingPluginImplementation,
abi.encodeCall(
MainVotingPlugin.initialize,
Expand All @@ -51,34 +55,36 @@ contract GovernancePluginsSetup is PluginSetup {
// Deploy the member access plugin
MemberAccessPlugin.MultisigSettings memory _multisigSettings;
_multisigSettings.proposalDuration = _memberAccessProposalDuration;
_multisigSettings.mainVotingPlugin = MainVotingPlugin(mainVotingPlugin);
_multisigSettings.mainVotingPlugin = MainVotingPlugin(_mainVotingPlugin);

address _memberAccessPlugin = createERC1967Proxy(
memberAccessPluginImplementation,
abi.encodeCall(MemberAccessPlugin.initialize, (IDAO(_dao), _multisigSettings))
);

// Condition contract (member access plugin execute)
address conditionContract = address(new MemberAccessExecuteCondition(mainVotingPlugin));
address _memberAccessExecuteCondition = address(
new MemberAccessExecuteCondition(_mainVotingPlugin)
);

// List the requested permissions
PermissionLib.MultiTargetPermission[]
memory permissions = new PermissionLib.MultiTargetPermission[](
_pluginUpgrader == address(0x0) ? 7 : 9
_pluginUpgrader == address(0x0) ? 5 : 6
);

// The main voting plugin can execute on the DAO
permissions[0] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: _dao,
who: mainVotingPlugin,
who: _mainVotingPlugin,
condition: PermissionLib.NO_CONDITION,
permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID()
});
// The DAO can update the main voting plugin settings
permissions[1] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: mainVotingPlugin,
where: _mainVotingPlugin,
who: _dao,
condition: PermissionLib.NO_CONDITION,
permissionId: MainVotingPlugin(mainVotingPluginImplementation)
Expand All @@ -87,33 +93,23 @@ contract GovernancePluginsSetup is PluginSetup {
// The DAO can manage the list of addresses
permissions[2] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: mainVotingPlugin,
where: _mainVotingPlugin,
who: _dao,
condition: PermissionLib.NO_CONDITION,
permissionId: MainVotingPlugin(mainVotingPluginImplementation)
.UPDATE_ADDRESSES_PERMISSION_ID()
});
// The DAO can upgrade the main voting plugin
permissions[3] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: mainVotingPlugin,
who: _dao,
condition: PermissionLib.NO_CONDITION,
permissionId: MainVotingPlugin(mainVotingPluginImplementation)
.UPGRADE_PLUGIN_PERMISSION_ID()
});

// The member access plugin needs to execute on the DAO
permissions[4] = PermissionLib.MultiTargetPermission({
permissions[3] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: _dao,
who: _memberAccessPlugin,
condition: conditionContract,
condition: _memberAccessExecuteCondition,
permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID()
});

// The DAO needs to be able to update the member access plugin settings
permissions[5] = PermissionLib.MultiTargetPermission({
permissions[4] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: _memberAccessPlugin,
who: _dao,
Expand All @@ -122,39 +118,32 @@ contract GovernancePluginsSetup is PluginSetup {
.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()
});

// The DAO needs to be able to upgrade the member access plugin
permissions[6] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: _memberAccessPlugin,
who: _dao,
condition: PermissionLib.NO_CONDITION,
permissionId: MemberAccessPlugin(memberAccessPluginImplementation)
.UPGRADE_PLUGIN_PERMISSION_ID()
});
// The DAO doesn't need APPLY_UPDATE_PERMISSION_ID on the PSP

// pluginUpgrader needs to be able to upgrade the plugins
// pluginUpgrader permissions
if (_pluginUpgrader != address(0x0)) {
permissions[7] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: mainVotingPlugin,
who: _pluginUpgrader,
condition: PermissionLib.NO_CONDITION,
permissionId: MainVotingPlugin(mainVotingPluginImplementation)
.UPGRADE_PLUGIN_PERMISSION_ID()
});
permissions[8] = PermissionLib.MultiTargetPermission({
// pluginUpgrader can make the DAO execute applyUpdate
// pluginUpgrader can make the DAO execute grant/revoke
address[] memory _targetPluginAddresses = new address[](2);
_targetPluginAddresses[0] = _mainVotingPlugin;
_targetPluginAddresses[1] = _memberAccessPlugin;
OnlyPluginUpgraderCondition _onlyPluginUpgraderCondition = new OnlyPluginUpgraderCondition(
DAO(payable(_dao)),
PluginSetupProcessor(pluginSetupProcessor),
_targetPluginAddresses
);
permissions[5] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: _memberAccessPlugin,
where: _dao,
who: _pluginUpgrader,
condition: PermissionLib.NO_CONDITION,
permissionId: MemberAccessPlugin(memberAccessPluginImplementation)
.UPGRADE_PLUGIN_PERMISSION_ID()
condition: address(_onlyPluginUpgraderCondition),
permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID()
});
}

preparedSetupData.permissions = permissions;
preparedSetupData.helpers = new address[](1);
preparedSetupData.helpers[0] = _memberAccessPlugin;
_preparedSetupData.permissions = permissions;
_preparedSetupData.helpers = new address[](1);
_preparedSetupData.helpers[0] = _memberAccessPlugin;
}

/// @inheritdoc IPluginSetup
Expand Down
137 changes: 137 additions & 0 deletions packages/contracts/src/OnlyPluginUpgraderCondition.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity 0.8.17;

import {PermissionCondition} from "@aragon/osx/core/permission/PermissionCondition.sol";
import {PermissionManager} from "@aragon/osx/core/permission/PermissionManager.sol";
import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol";
import {DAO} from "@aragon/osx/core/dao/DAO.sol";
import {IDAO} from "@aragon/osx/core/dao/IDAO.sol";
import {MEMBER_PERMISSION_ID} from "./constants.sol";

/// @notice The condition associated with the `pluginUpgrader`
contract OnlyPluginUpgraderCondition is PermissionCondition {
bytes32 private constant UPGRADE_PLUGIN_PERMISSION_ID = keccak256("UPGRADE_PLUGIN_PERMISSION");

/// @notice The address of the DAO contract
address private dao;
/// @notice The address of the PluginSetupProcessor contract
address private psp;
/// @notice The address of the contract where the permission can be granted
address[] private targetPluginAddresses;

/// @notice Thrown when the constructor receives empty parameters
error InvalidParameters();

/// @notice The constructor of the condition
/// @param _targetPluginAddresses The addresses of the contracts where upgradeTo and upgradeToAndCall can be called
constructor(DAO _dao, PluginSetupProcessor _psp, address[] memory _targetPluginAddresses) {
if (
address(_dao) == address(0) ||
address(_psp) == address(0) ||
_targetPluginAddresses.length == 0
) {
revert InvalidParameters();
}
targetPluginAddresses = _targetPluginAddresses;
psp = address(_psp);
dao = address(_dao);
}

function getSelector(bytes memory _data) public pure returns (bytes4 selector) {
// Slices are only supported for bytes calldata
assembly {
selector := mload(add(_data, 32))
}
}

/// @notice Checks whether the current action wants to grant membership on the predefined address
function isGranted(
address _where,
address _who,
bytes32 _permissionId,
bytes calldata _data
) external view returns (bool) {
(_where, _who, _permissionId);

bytes4 _requestedFuncSig = getSelector(_data);
if (_requestedFuncSig != IDAO.execute.selector) {
return false;
}

(, IDAO.Action[] memory _actions, ) = abi.decode(
_data[4:],
(bytes32, IDAO.Action[], uint256)
);

// Check all actions
for (uint256 i; i < _actions.length; ) {
_requestedFuncSig = getSelector(_actions[i].data);

// Can only grant/revoke UPGRADE_PLUGIN_PERMISSION_ID to the PSP on the plugins
if (
_requestedFuncSig == PermissionManager.grant.selector ||
_requestedFuncSig == PermissionManager.revoke.selector
) {
if (!isValidExecuteGrantRevokeCalldata(_actions[i].data)) return false;
else if (_actions[i].to != dao) return false;
}
// Can only make the DAO execute applyUpdate() on the PSP
else if (_requestedFuncSig == PluginSetupProcessor.applyUpdate.selector) {
if (!isValidExecuteApplyUpdateCalldata(_actions[i].data)) return false;
else if (_actions[i].to != psp) return false;
}
// Not allowed
else {
return false;
}

unchecked {
i++;
}
}

return true;
}

// Internal helpers

function isValidExecuteGrantRevokeCalldata(bytes memory _data) private view returns (bool) {
// Decode the call being requested
(, address _requestedWhere, address _requestedWho, bytes32 _requestedPermission) = abi
.decode(_data, (bytes4, address, address, bytes32));

if (_requestedPermission != UPGRADE_PLUGIN_PERMISSION_ID) return false;
else if (_requestedWho != psp) return false;

// Search the first match
for (uint256 j = 0; j < targetPluginAddresses.length; ) {
if (_requestedWhere == targetPluginAddresses[j]) return true;

unchecked {
j++;
}
}
return false;
}

function isValidExecuteApplyUpdateCalldata(bytes memory _data) private view returns (bool) {
//
(, address _dao, PluginSetupProcessor.ApplyUpdateParams memory _applyParams) = abi.decode(
_data,
(bytes4, address, PluginSetupProcessor.ApplyUpdateParams)
);

if (_dao != dao) return false;

// Search the first match
for (uint256 j = 0; j < targetPluginAddresses.length; ) {
if (_applyParams.plugin != targetPluginAddresses[j]) return false;

unchecked {
j++;
}
}
return true;
}
}

0 comments on commit b048e9d

Please sign in to comment.