Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

metadata extension #36

Merged
merged 9 commits into from
Oct 8, 2024
1 change: 0 additions & 1 deletion packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ const config: HardhatUserConfig = {
solidity: {
version: '0.8.17',
settings: {
viaIR: true,
metadata: {
// Not including the metadata hash
// https://github.com/paulrberg/hardhat-template/issues/31
Expand Down
13 changes: 11 additions & 2 deletions packages/contracts/src/MajorityVotingBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/Pl
import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol";
import {IProposal} from "@aragon/osx-commons-contracts/src/plugin/extensions/proposal/IProposal.sol";
import {Action} from "@aragon/osx-commons-contracts/src/executors/IExecutor.sol";
import {MetadataExtensionUpgradeable} from "@aragon/osx-commons-contracts/src/utils/metadata/MetadataExtensionUpgradeable.sol";

import {IMajorityVoting} from "./IMajorityVoting.sol";

Expand Down Expand Up @@ -122,6 +123,7 @@ abstract contract MajorityVotingBase is
IMajorityVoting,
Initializable,
ERC165Upgradeable,
MetadataExtensionUpgradeable,
PluginUUPSUpgradeable,
ProposalUpgradeable
{
Expand Down Expand Up @@ -300,12 +302,14 @@ abstract contract MajorityVotingBase is
IDAO _dao,
VotingSettings calldata _votingSettings,
TargetConfig calldata _targetConfig,
uint256 _minApprovals
uint256 _minApprovals,
bytes calldata _pluginMetadata
) internal onlyInitializing {
__PluginUUPSUpgradeable_init(_dao);
_updateVotingSettings(_votingSettings);
_updateMinApprovals(_minApprovals);
_setTargetConfig(_targetConfig);
_updateMetadata(_pluginMetadata);
}

/// @notice Checks if this or the parent contract supports an interface by its ID.
Expand All @@ -317,7 +321,12 @@ abstract contract MajorityVotingBase is
public
view
virtual
override(ERC165Upgradeable, PluginUUPSUpgradeable, ProposalUpgradeable)
override(
ERC165Upgradeable,
MetadataExtensionUpgradeable,
PluginUUPSUpgradeable,
ProposalUpgradeable
)
returns (bool)
{
// In addition to the current IMajorityVoting interface, also support previous version
Expand Down
45 changes: 33 additions & 12 deletions packages/contracts/src/TokenVoting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ contract TokenVoting is IMembership, MajorityVotingBase {
VotingSettings calldata _votingSettings,
IVotesUpgradeable _token,
TargetConfig calldata _targetConfig,
uint256 _minApprovals
uint256 _minApprovals,
bytes calldata _pluginMetadata
) external onlyCallAtInitialization reinitializer(2) {
__MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals);
__MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals, _pluginMetadata);

votingToken = _token;

Expand All @@ -61,13 +62,14 @@ contract TokenVoting is IMembership, MajorityVotingBase {
/// @param _initData The initialization data to be passed to via `upgradeToAndCall` (see [ERC-1967](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Upgrade)).
function initializeFrom(uint16 _fromBuild, bytes calldata _initData) external reinitializer(2) {
if (_fromBuild < 3) {
(uint256 minApprovals, TargetConfig memory targetConfig) = abi.decode(
_initData,
(uint256, TargetConfig)
);
(uint256 minApprovals, TargetConfig memory targetConfig, bytes memory pluginMetadata) = abi
.decode(_initData, (uint256, TargetConfig, bytes));

_updateMinApprovals(minApprovals);

_setTargetConfig(targetConfig);

_updateMetadata(pluginMetadata);
}
}

Expand Down Expand Up @@ -158,14 +160,13 @@ contract TokenVoting is IMembership, MajorityVotingBase {
vote(proposalId, _voteOption, _tryEarlyExecution);
}

emit ProposalCreated(
proposalId,
_msgSender(),
_startDate,
_endDate,
_emitProposalCreatedEvent(
_metadata,
_actions,
_allowFailureMap
_allowFailureMap,
proposalId,
_startDate,
_endDate
);
}

Expand Down Expand Up @@ -311,6 +312,26 @@ contract TokenVoting is IMembership, MajorityVotingBase {
return true;
}

/// @dev Helper function to avoid stack too deep in non via-ir compilation mode.
function _emitProposalCreatedEvent(
bytes calldata _metadata,
Action[] calldata _actions,
uint256 _allowFailureMap,
uint256 proposalId,
uint64 _startDate,
uint64 _endDate
) private {
emit ProposalCreated(
proposalId,
_msgSender(),
_startDate,
_endDate,
_metadata,
_actions,
_allowFailureMap
);
}

/// @dev This empty reserved space is put in place to allow future versions to add new
/// variables without shifting down storage in the inheritance chain.
/// https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
Expand Down
91 changes: 58 additions & 33 deletions packages/contracts/src/TokenVotingSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
bytes32 public constant SET_TARGET_CONFIG_PERMISSION_ID =
keccak256("SET_TARGET_CONFIG_PERMISSION");

/// @notice The ID of the permission required to call the `updateMetadata` function.
bytes32 public constant UPDATE_METADATA_PERMISSION_ID = keccak256("UPDATE_METADATA_PERMISSION");

/// @notice The ID of the permission required to call the `upgradeToAndCall` function.
bytes32 internal constant UPGRADE_PLUGIN_PERMISSION_ID = keccak256("UPGRADE_PLUGIN_PERMISSION");

Expand Down Expand Up @@ -104,25 +107,23 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
// only used for GovernanceERC20(token is not passed)
GovernanceERC20.MintSettings memory mintSettings,
PluginUUPSUpgradeable.TargetConfig memory targetConfig,
uint256 minApprovals
uint256 minApprovals,
bytes memory pluginMetadata
) = abi.decode(
_data,
(
MajorityVotingBase.VotingSettings,
TokenSettings,
GovernanceERC20.MintSettings,
PluginUUPSUpgradeable.TargetConfig,
uint256
uint256,
bytes
)
);

address token = tokenSettings.addr;
bool tokenAddressNotZero = token != address(0);

// Prepare helpers.
address[] memory helpers = new address[](2);

if (tokenAddressNotZero) {
if (tokenSettings.addr != address(0)) {
if (!token.isContract()) {
revert TokenNotContract(token);
}
Expand Down Expand Up @@ -153,28 +154,29 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
);
}


// Prepare and deploy plugin proxy.
plugin = address(tokenVotingBase).deployUUPSProxy(
abi.encodeWithSignature(
"initialize(address,(uint8,uint32,uint32,uint64,uint256),address,(address,uint8),uint256)",
IDAO(_dao),
votingSettings,
IVotesUpgradeable(token),
targetConfig,
minApprovals
abi.encodeCall(
TokenVoting.initialize,
(
IDAO(_dao),
votingSettings,
IVotesUpgradeable(token),
targetConfig,
minApprovals,
pluginMetadata
)
)
);

address votingPowerCondition = address(new VotingPowerCondition(plugin));

helpers[0] = token;
helpers[1] = votingPowerCondition;
preparedSetupData.helpers = new address[](2);
preparedSetupData.helpers[0] = token;
preparedSetupData.helpers[1] = address(new VotingPowerCondition(plugin));

// Prepare permissions
PermissionLib.MultiTargetPermission[]
memory permissions = new PermissionLib.MultiTargetPermission[](
tokenAddressNotZero ? 4 : 5
tokenSettings.addr != address(0) ? 5 : 6
);

// Set plugin permissions to be granted.
Expand All @@ -200,7 +202,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
PermissionLib.Operation.GrantWithCondition,
plugin,
address(type(uint160).max), // ANY_ADDR
votingPowerCondition,
preparedSetupData.helpers[1], // VotingPowerCondition
TokenVoting(IMPLEMENTATION).CREATE_PROPOSAL_PERMISSION_ID()
);

Expand All @@ -212,10 +214,18 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
permissionId: SET_TARGET_CONFIG_PERMISSION_ID
});

if (!tokenAddressNotZero) {
permissions[4] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: plugin,
who: _dao,
condition: PermissionLib.NO_CONDITION,
permissionId: UPDATE_METADATA_PERMISSION_ID
});

if (tokenSettings.addr == address(0)) {
bytes32 tokenMintPermission = GovernanceERC20(token).MINT_PERMISSION_ID();

permissions[4] = PermissionLib.MultiTargetPermission({
permissions[5] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: token,
who: _dao,
Expand All @@ -224,7 +234,6 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
});
}

preparedSetupData.helpers = helpers;
preparedSetupData.permissions = permissions;
}

Expand All @@ -243,7 +252,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
address votingPowerCondition = address(new VotingPowerCondition(_payload.plugin));

PermissionLib.MultiTargetPermission[]
memory permissions = new PermissionLib.MultiTargetPermission[](3);
memory permissions = new PermissionLib.MultiTargetPermission[](4);

permissions[0] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Revoke,
Expand All @@ -269,6 +278,14 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
permissionId: SET_TARGET_CONFIG_PERMISSION_ID
});

permissions[3] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Grant,
where: _payload.plugin,
who: _dao,
condition: PermissionLib.NO_CONDITION,
permissionId: UPDATE_METADATA_PERMISSION_ID
});

preparedSetupData.permissions = permissions;
preparedSetupData.helpers = new address[](1);
preparedSetupData.helpers[0] = votingPowerCondition;
Expand All @@ -283,7 +300,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
SetupPayload calldata _payload
) external view returns (PermissionLib.MultiTargetPermission[] memory permissions) {
// Prepare permissions.
permissions = new PermissionLib.MultiTargetPermission[](4);
permissions = new PermissionLib.MultiTargetPermission[](5);

// Set permissions to be Revoked.
permissions[0] = PermissionLib.MultiTargetPermission({
Expand All @@ -310,13 +327,21 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
permissionId: SET_TARGET_CONFIG_PERMISSION_ID
});

permissions[3] = PermissionLib.MultiTargetPermission(
PermissionLib.Operation.Revoke,
_payload.plugin,
address(type(uint160).max), // ANY_ADDR
PermissionLib.NO_CONDITION,
TokenVoting(IMPLEMENTATION).CREATE_PROPOSAL_PERMISSION_ID()
);
permissions[3] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Revoke,
where: _payload.plugin,
who: _dao,
condition: PermissionLib.NO_CONDITION,
permissionId: UPDATE_METADATA_PERMISSION_ID
});

permissions[4] = PermissionLib.MultiTargetPermission({
operation: PermissionLib.Operation.Revoke,
where: _payload.plugin,
who: address(type(uint160).max), // ANY_ADDR
condition: PermissionLib.NO_CONDITION,
permissionId: TokenVoting(IMPLEMENTATION).CREATE_PROPOSAL_PERMISSION_ID()
});
}

/// @notice Unsatisfiably determines if the token is an IVotes interface.
Expand Down
20 changes: 16 additions & 4 deletions packages/contracts/src/build-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@
"name": "minApproval",
"type": "uint256",
"description": "The minimum amount of yes votes needed for the proposal advance."
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general comment, I would recommend also using MetadataExtension on PluginRepo's and get rid of the build/release metadata fields as they currently are.
Not for now, but still

{
"internalType": "bytes",
"name": "metadata",
"type": "bytes",
"description": "The metadata that contains the information about the multisig."
}
]
},
Expand All @@ -127,11 +133,11 @@
"inputs": []
},
"3": {
"description": "The information required for the installation.",
"description": "The information required for the update.",
"inputs": [
{
"internalType": "uint256",
"name": "minApproval",
"name": "minApprovals",
"type": "uint256",
"description": "The minimum amount of yes votes needed for the proposal advance."
},
Expand All @@ -144,16 +150,22 @@
"description": "The target contract to which actions will be forwarded to for execution."
},
{
"internalType": "enum PluginUUPSUpgradeable.Operation",
"internalType": "uint8",
"name": "operation",
"type": "uint8",
"description": "The operation type(either `call` or `delegatecall`) that will be used for execution forwarding."
}
],
"internalType": "struct PluginUUPSUpgradeable.TargetConfig",
"internalType": "struct TokenVoting.TargetConfig",
"name": "TargetConfig",
"type": "tuple",
"description": "The initial target config"
},
{
"internalType": "bytes",
"name": "metadata",
"type": "bytes",
"description": "The metadata that contains the information about the multisig."
}
]
}
Expand Down
5 changes: 3 additions & 2 deletions packages/contracts/src/mocks/MajorityVotingMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ contract MajorityVotingMock is MajorityVotingBase {
IDAO _dao,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why importing IExecutor above?

VotingSettings calldata _votingSettings,
TargetConfig calldata _targetConfig,
uint256 _minApprovals
uint256 _minApprovals,
bytes calldata _metadata
) public initializer {
__MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals);
__MajorityVotingBase_init(_dao, _votingSettings, _targetConfig, _minApprovals, _metadata);
}

function createProposalId(
Expand Down
Loading
Loading