diff --git a/contracts/src/dao/IDAO.sol b/contracts/src/dao/IDAO.sol index ba1151eb..133970a8 100644 --- a/contracts/src/dao/IDAO.sol +++ b/contracts/src/dao/IDAO.sol @@ -7,16 +7,6 @@ pragma solidity ^0.8.8; /// @notice The interface required for DAOs within the Aragon App DAO framework. /// @custom:security-contact sirt@aragon.org interface IDAO { - /// @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; - } - /// @notice Checks if an address has permission on a contract via a permission identifier and considers if `ANY_ADDRESS` was used in the granting process. /// @param _where The address of the contract. /// @param _who The address of a EOA or contract to give the permissions. @@ -38,35 +28,6 @@ interface IDAO { /// @param metadata The IPFS hash of the new metadata object. event MetadataSet(bytes metadata); - /// @notice Executes a list of actions. If a zero allow-failure map is provided, a failing action reverts the entire execution. If a non-zero allow-failure map is provided, allowed actions can fail without the entire call being reverted. - /// @param _callId The ID of the call. The definition of the value of `callId` is up to the calling contract and can be used, e.g., as a nonce. - /// @param _actions The array of actions. - /// @param _allowFailureMap A bitmap allowing execution to succeed, even if individual actions might revert. If the bit at index `i` is 1, the execution succeeds even if the `i`th action reverts. A failure map value of 0 requires every action to not revert. - /// @return The array of results obtained from the executed actions in `bytes`. - /// @return The resulting failure map containing the actions have actually failed. - function execute( - bytes32 _callId, - Action[] memory _actions, - uint256 _allowFailureMap - ) external returns (bytes[] memory, uint256); - - /// @notice Emitted when a proposal is executed. - /// @param actor The address of the caller. - /// @param callId The ID of the call. - /// @param actions The array of actions executed. - /// @param allowFailureMap The allow failure map encoding which actions are allowed to fail. - /// @param failureMap The failure map encoding which actions have failed. - /// @param execResults The array with the results of the executed actions. - /// @dev The value of `callId` is defined by the component/contract calling the execute function. A `Plugin` implementation can use it, for example, as a nonce. - event Executed( - address indexed actor, - bytes32 callId, - Action[] actions, - uint256 allowFailureMap, - uint256 failureMap, - bytes[] execResults - ); - /// @notice Emitted when a standard callback is registered. /// @param interfaceId The ID of the interface. /// @param callbackSelector The selector of the callback function. diff --git a/contracts/src/executors/Executor.sol b/contracts/src/executors/Executor.sol new file mode 100644 index 00000000..ed7c5a3c --- /dev/null +++ b/contracts/src/executors/Executor.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {IExecutor, Action} from "./IExecutor.sol"; +import {flipBit, hasBit} from "../utils/math/BitMap.sol"; + +/// @notice Simple Executor that loops through the actions and executes them. +/// @dev Reverts in case enough gas was not provided for the last action. +contract Executor is IExecutor { + /// @notice The internal constant storing the maximal action array length. + uint256 internal constant MAX_ACTIONS = 256; + + /// @notice Thrown if the action array length is larger than `MAX_ACTIONS`. + error TooManyActions(); + + /// @notice Thrown if an action has insufficient gas left. + error InsufficientGas(); + + /// @notice Thrown if action execution has failed. + /// @param index The index of the action in the action array that failed. + error ActionFailed(uint256 index); + + /// @inheritdoc IExecutor + function execute( + bytes32 _callId, + Action[] memory _actions, + uint256 _allowFailureMap + ) public virtual override returns (bytes[] memory execResults, uint256 failureMap) { + // Check that the action array length is within bounds. + if (_actions.length > MAX_ACTIONS) { + revert TooManyActions(); + } + + execResults = new bytes[](_actions.length); + + uint256 gasBefore; + uint256 gasAfter; + + for (uint256 i = 0; i < _actions.length; ) { + gasBefore = gasleft(); + + (bool success, bytes memory data) = _actions[i].to.call{value: _actions[i].value}( + _actions[i].data + ); + + gasAfter = gasleft(); + + // Check if failure is allowed + if (!hasBit(_allowFailureMap, uint8(i))) { + // Check if the call failed. + if (!success) { + revert ActionFailed(i); + } + } else { + // Check if the call failed. + if (!success) { + // Make sure that the action call did not fail because 63/64 of `gasleft()` was insufficient to execute the external call `.to.call` (see [ERC-150](https://eips.ethereum.org/EIPS/eip-150)). + // In specific scenarios, i.e. proposal execution where the last action in the action array is allowed to fail, the account calling `execute` could force-fail this action by setting a gas limit + // where 63/64 is insufficient causing the `.to.call` to fail, but where the remaining 1/64 gas are sufficient to successfully finish the `execute` call. + if (gasAfter < gasBefore / 64) { + revert InsufficientGas(); + } + + // Store that this action failed. + failureMap = flipBit(failureMap, uint8(i)); + } + } + + execResults[i] = data; + + unchecked { + ++i; + } + } + + emit Executed({ + actor: msg.sender, + callId: _callId, + actions: _actions, + allowFailureMap: _allowFailureMap, + failureMap: failureMap, + execResults: execResults + }); + } +} diff --git a/contracts/src/executors/IExecutor.sol b/contracts/src/executors/IExecutor.sol new file mode 100644 index 00000000..2961b87e --- /dev/null +++ b/contracts/src/executors/IExecutor.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {IDAO} from "../dao/IDAO.sol"; + +/// @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; +} + +/// @title IDAO +/// @author Aragon X - 2022-2023 +/// @notice The interface required for Executors within the Aragon App DAO framework. +/// @custom:security-contact sirt@aragon.org +interface IExecutor { + /// @notice Emitted when a proposal is executed. + /// @param actor The address of the caller. + /// @param callId The ID of the call. + /// @param actions The array of actions executed. + /// @param allowFailureMap The allow failure map encoding which actions are allowed to fail. + /// @param failureMap The failure map encoding which actions have failed. + /// @param execResults The array with the results of the executed actions. + /// @dev The value of `callId` is defined by the component/contract calling the execute function. A `Plugin` implementation can use it, for example, as a nonce. + event Executed( + address indexed actor, + bytes32 callId, + Action[] actions, + uint256 allowFailureMap, + uint256 failureMap, + bytes[] execResults + ); + + /// @notice Executes a list of actions. If a zero allow-failure map is provided, a failing action reverts the entire execution. If a non-zero allow-failure map is provided, allowed actions can fail without the entire call being reverted. + /// @param _callId The ID of the call. The definition of the value of `callId` is up to the calling contract and can be used, e.g., as a nonce. + /// @param _actions The array of actions. + /// @param _allowFailureMap A bitmap allowing execution to succeed, even if individual actions might revert. If the bit at index `i` is 1, the execution succeeds even if the `i`th action reverts. A failure map value of 0 requires every action to not revert. + /// @return The array of results obtained from the executed actions in `bytes`. + /// @return The resulting failure map containing the actions have actually failed. + function execute( + bytes32 _callId, + Action[] memory _actions, + uint256 _allowFailureMap + ) external returns (bytes[] memory, uint256); +} diff --git a/contracts/src/mocks/dao/DAOMock.sol b/contracts/src/mocks/dao/DAOMock.sol index dbd49e1a..ff8ffc21 100644 --- a/contracts/src/mocks/dao/DAOMock.sol +++ b/contracts/src/mocks/dao/DAOMock.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.8; import {IDAO} from "../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../executors/IExecutor.sol"; /// @notice A mock DAO that anyone can set permissions in. /// @dev DO NOT USE IN PRODUCTION! -contract DAOMock is IDAO { +contract DAOMock is IDAO, IExecutor { bool public hasPermissionReturnValueMock; function setHasPermissionReturnValueMock(bool _hasPermissionReturnValueMock) external { diff --git a/contracts/src/mocks/plugin/CustomExecutorMock.sol b/contracts/src/mocks/plugin/CustomExecutorMock.sol index 9e5a05e2..acab86e9 100644 --- a/contracts/src/mocks/plugin/CustomExecutorMock.sol +++ b/contracts/src/mocks/plugin/CustomExecutorMock.sol @@ -3,26 +3,18 @@ pragma solidity ^0.8.8; import {IDAO} from "../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../executors/IExecutor.sol"; /// @notice A mock DAO that anyone can set permissions in. /// @dev DO NOT USE IN PRODUCTION! -contract CustomExecutorMock { +contract CustomExecutorMock is IExecutor { error Failed(); - event Executed( - address indexed actor, - bytes32 callId, - IDAO.Action[] actions, - uint256 allowFailureMap, - uint256 failureMap, - bytes[] execResults - ); - function execute( bytes32 callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 allowFailureMap - ) external returns (bytes[] memory execResults, uint256 failureMap) { + ) external override returns (bytes[] memory execResults, uint256 failureMap) { if (callId == bytes32(0)) { revert Failed(); } else if (callId == bytes32(uint256(123))) { diff --git a/contracts/src/mocks/plugin/PluginCloneableMock.sol b/contracts/src/mocks/plugin/PluginCloneableMock.sol index 568940f5..2a5a4ecd 100644 --- a/contracts/src/mocks/plugin/PluginCloneableMock.sol +++ b/contracts/src/mocks/plugin/PluginCloneableMock.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.8; import {PluginCloneable} from "../../plugin/PluginCloneable.sol"; import {IDAO} from "../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../executors/IExecutor.sol"; /// @notice A mock cloneable plugin to be deployed via the minimal proxy pattern. /// v1.1 (Release 1, Build 1) @@ -19,7 +20,7 @@ contract PluginCloneableMockBuild1 is PluginCloneable { function execute( uint256 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap ) external returns (bytes[] memory execResults, uint256 failureMap) { (execResults, failureMap) = _execute(bytes32(_callId), _actions, _allowFailureMap); @@ -28,7 +29,7 @@ contract PluginCloneableMockBuild1 is PluginCloneable { function execute( address _target, uint256 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap, Operation _op ) external returns (bytes[] memory execResults, uint256 failureMap) { diff --git a/contracts/src/mocks/plugin/PluginMock.sol b/contracts/src/mocks/plugin/PluginMock.sol index 3b4099f3..22fa5cc5 100644 --- a/contracts/src/mocks/plugin/PluginMock.sol +++ b/contracts/src/mocks/plugin/PluginMock.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.8; import {Plugin} from "../../plugin/Plugin.sol"; import {IDAO} from "../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../executors/IExecutor.sol"; /// @notice A mock plugin to be deployed via the `new` keyword. /// v1.1 (Release 1, Build 1) @@ -17,7 +18,7 @@ contract PluginMockBuild1 is Plugin { function execute( uint256 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap ) external returns (bytes[] memory execResults, uint256 failureMap) { (execResults, failureMap) = _execute(bytes32(_callId), _actions, _allowFailureMap); @@ -26,7 +27,7 @@ contract PluginMockBuild1 is Plugin { function execute( address _target, uint256 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap, Operation _op ) external returns (bytes[] memory execResults, uint256 failureMap) { diff --git a/contracts/src/mocks/plugin/PluginUUPSUpgradeableMock.sol b/contracts/src/mocks/plugin/PluginUUPSUpgradeableMock.sol index fd6c9597..a792458c 100644 --- a/contracts/src/mocks/plugin/PluginUUPSUpgradeableMock.sol +++ b/contracts/src/mocks/plugin/PluginUUPSUpgradeableMock.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.8; import {PluginUUPSUpgradeable} from "../../plugin/PluginUUPSUpgradeable.sol"; import {IDAO} from "../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../executors/IExecutor.sol"; /// @notice A mock upgradeable plugin to be deployed via the UUPS proxy pattern. /// v1.1 (Release 1, Build 1) @@ -19,7 +20,7 @@ contract PluginUUPSUpgradeableMockBuild1 is PluginUUPSUpgradeable { function execute( uint256 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap ) external returns (bytes[] memory execResults, uint256 failureMap) { (execResults, failureMap) = _execute(bytes32(_callId), _actions, _allowFailureMap); @@ -28,7 +29,7 @@ contract PluginUUPSUpgradeableMockBuild1 is PluginUUPSUpgradeable { function execute( address _target, uint256 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap, Operation _op ) external returns (bytes[] memory execResults, uint256 failureMap) { diff --git a/contracts/src/mocks/plugin/extensions/proposal/ProposalMock.sol b/contracts/src/mocks/plugin/extensions/proposal/ProposalMock.sol index cdf3491c..1bf07368 100644 --- a/contracts/src/mocks/plugin/extensions/proposal/ProposalMock.sol +++ b/contracts/src/mocks/plugin/extensions/proposal/ProposalMock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.8; import {Proposal} from "../../../../plugin/extensions/proposal/Proposal.sol"; -import {IDAO} from "../../../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../../../executors/IExecutor.sol"; /// @notice A mock contract. /// @dev DO NOT USE IN PRODUCTION! @@ -14,15 +14,18 @@ contract ProposalMock is Proposal { // solhint-disable no-empty-blocks function createProposal( bytes memory data, - IDAO.Action[] memory actions, + Action[] memory actions, uint64 startDate, - uint64 endDate + uint64 endDate, + bytes memory ) external returns (uint256 proposalId) {} function canExecute(uint256 proposalId) external view returns (bool) {} function createProposalId( - IDAO.Action[] memory actions, + Action[] memory actions, bytes memory metadata ) external view returns (uint256) {} + + function createProposalParamsABI() external view returns (string memory) {} } diff --git a/contracts/src/mocks/plugin/extensions/proposal/ProposalUpgradeableMock.sol b/contracts/src/mocks/plugin/extensions/proposal/ProposalUpgradeableMock.sol index 88aa154f..ff06644a 100644 --- a/contracts/src/mocks/plugin/extensions/proposal/ProposalUpgradeableMock.sol +++ b/contracts/src/mocks/plugin/extensions/proposal/ProposalUpgradeableMock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.8; import {ProposalUpgradeable} from "../../../../plugin/extensions/proposal/ProposalUpgradeable.sol"; -import {IDAO} from "../../../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../../../executors/IExecutor.sol"; /// @notice A mock contract. /// @dev DO NOT USE IN PRODUCTION! @@ -14,15 +14,18 @@ contract ProposalUpgradeableMock is ProposalUpgradeable { // solhint-disable no-empty-blocks function createProposal( bytes memory data, - IDAO.Action[] memory actions, + Action[] memory actions, uint64 startDate, - uint64 endDate + uint64 endDate, + bytes memory ) external returns (uint256 proposalId) {} function canExecute(uint256 proposalId) external view returns (bool) {} function createProposalId( - IDAO.Action[] memory actions, + Action[] memory actions, bytes memory metadata ) external view returns (uint256) {} + + function createProposalParamsABI() external view returns (string memory) {} } diff --git a/contracts/src/plugin/Plugin.sol b/contracts/src/plugin/Plugin.sol index 27be98fd..11b33e1f 100644 --- a/contracts/src/plugin/Plugin.sol +++ b/contracts/src/plugin/Plugin.sol @@ -10,6 +10,7 @@ import {ProtocolVersion} from "../utils/versioning/ProtocolVersion.sol"; import {DaoAuthorizable} from "../permission/auth/DaoAuthorizable.sol"; import {IDAO} from "../dao/IDAO.sol"; import {IPlugin} from "./IPlugin.sol"; +import {IExecutor, Action} from "../executors/IExecutor.sol"; /// @title Plugin /// @author Aragon X - 2022-2023 @@ -117,7 +118,7 @@ abstract contract Plugin is IPlugin, ERC165, DaoAuthorizable, ProtocolVersion { /// @return failureMap address of the implementation contract. function _execute( bytes32 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap ) internal virtual returns (bytes[] memory execResults, uint256 failureMap) { return @@ -140,7 +141,7 @@ abstract contract Plugin is IPlugin, ERC165, DaoAuthorizable, ProtocolVersion { function _execute( address _target, bytes32 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap, Operation _op ) internal virtual returns (bytes[] memory execResults, uint256 failureMap) { @@ -150,7 +151,7 @@ abstract contract Plugin is IPlugin, ERC165, DaoAuthorizable, ProtocolVersion { // solhint-disable-next-line avoid-low-level-calls (success, data) = _target.delegatecall( - abi.encodeCall(IDAO.execute, (_callId, _actions, _allowFailureMap)) + abi.encodeCall(IExecutor.execute, (_callId, _actions, _allowFailureMap)) ); if (!success) { @@ -166,7 +167,11 @@ abstract contract Plugin is IPlugin, ERC165, DaoAuthorizable, ProtocolVersion { } (execResults, failureMap) = abi.decode(data, (bytes[], uint256)); } else { - (execResults, failureMap) = IDAO(_target).execute(_callId, _actions, _allowFailureMap); + (execResults, failureMap) = IExecutor(_target).execute( + _callId, + _actions, + _allowFailureMap + ); } } } diff --git a/contracts/src/plugin/PluginCloneable.sol b/contracts/src/plugin/PluginCloneable.sol index 5f7ceb86..cb1704c8 100644 --- a/contracts/src/plugin/PluginCloneable.sol +++ b/contracts/src/plugin/PluginCloneable.sol @@ -10,6 +10,7 @@ import {ProtocolVersion} from "../utils/versioning/ProtocolVersion.sol"; import {DaoAuthorizableUpgradeable} from "../permission/auth/DaoAuthorizableUpgradeable.sol"; import {IDAO} from "../dao/IDAO.sol"; import {IPlugin} from "./IPlugin.sol"; +import {IExecutor, Action} from "../executors/IExecutor.sol"; /// @title PluginCloneable /// @author Aragon X - 2022-2023 @@ -132,7 +133,7 @@ abstract contract PluginCloneable is /// @return failureMap address of the implementation contract. function _execute( bytes32 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap ) internal virtual returns (bytes[] memory execResults, uint256 failureMap) { return @@ -155,7 +156,7 @@ abstract contract PluginCloneable is function _execute( address _target, bytes32 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap, Operation _op ) internal virtual returns (bytes[] memory execResults, uint256 failureMap) { @@ -165,7 +166,7 @@ abstract contract PluginCloneable is // solhint-disable-next-line avoid-low-level-calls (success, data) = _target.delegatecall( - abi.encodeCall(IDAO.execute, (_callId, _actions, _allowFailureMap)) + abi.encodeCall(IExecutor.execute, (_callId, _actions, _allowFailureMap)) ); if (!success) { @@ -181,7 +182,11 @@ abstract contract PluginCloneable is } (execResults, failureMap) = abi.decode(data, (bytes[], uint256)); } else { - (execResults, failureMap) = IDAO(_target).execute(_callId, _actions, _allowFailureMap); + (execResults, failureMap) = IExecutor(_target).execute( + _callId, + _actions, + _allowFailureMap + ); } } } diff --git a/contracts/src/plugin/PluginUUPSUpgradeable.sol b/contracts/src/plugin/PluginUUPSUpgradeable.sol index d2e5f693..80c6a66d 100644 --- a/contracts/src/plugin/PluginUUPSUpgradeable.sol +++ b/contracts/src/plugin/PluginUUPSUpgradeable.sol @@ -10,8 +10,9 @@ import {ERC165CheckerUpgradeable} from "@openzeppelin/contracts-upgradeable/util import {IProtocolVersion} from "../utils/versioning/IProtocolVersion.sol"; import {ProtocolVersion} from "../utils/versioning/ProtocolVersion.sol"; import {DaoAuthorizableUpgradeable} from "../permission/auth/DaoAuthorizableUpgradeable.sol"; -import {IDAO} from "../dao/IDAO.sol"; import {IPlugin} from "./IPlugin.sol"; +import {IDAO} from "../dao/IDAO.sol"; +import {IExecutor, Action} from "../executors/IExecutor.sol"; /// @title PluginUUPSUpgradeable /// @author Aragon X - 2022-2023 @@ -147,7 +148,7 @@ abstract contract PluginUUPSUpgradeable is /// @return failureMap address of the implementation contract. function _execute( bytes32 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap ) internal virtual returns (bytes[] memory execResults, uint256 failureMap) { return @@ -170,7 +171,7 @@ abstract contract PluginUUPSUpgradeable is function _execute( address _target, bytes32 _callId, - IDAO.Action[] memory _actions, + Action[] memory _actions, uint256 _allowFailureMap, Operation _op ) internal virtual returns (bytes[] memory execResults, uint256 failureMap) { @@ -180,7 +181,7 @@ abstract contract PluginUUPSUpgradeable is // solhint-disable-next-line avoid-low-level-calls (success, data) = _target.delegatecall( - abi.encodeCall(IDAO.execute, (_callId, _actions, _allowFailureMap)) + abi.encodeCall(IExecutor.execute, (_callId, _actions, _allowFailureMap)) ); if (!success) { @@ -196,7 +197,11 @@ abstract contract PluginUUPSUpgradeable is } (execResults, failureMap) = abi.decode(data, (bytes[], uint256)); } else { - (execResults, failureMap) = IDAO(_target).execute(_callId, _actions, _allowFailureMap); + (execResults, failureMap) = IExecutor(_target).execute( + _callId, + _actions, + _allowFailureMap + ); } } diff --git a/contracts/src/plugin/extensions/proposal/IProposal.sol b/contracts/src/plugin/extensions/proposal/IProposal.sol index 2a184b57..5c325366 100644 --- a/contracts/src/plugin/extensions/proposal/IProposal.sol +++ b/contracts/src/plugin/extensions/proposal/IProposal.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.8; import {IDAO} from "../../../dao/IDAO.sol"; +import {IExecutor, Action} from "../../../executors/IExecutor.sol"; /// @title IProposal /// @author Aragon X - 2022-2023 @@ -23,7 +24,7 @@ interface IProposal { uint64 startDate, uint64 endDate, bytes metadata, - IDAO.Action[] actions, + Action[] actions, uint256 allowFailureMap ); @@ -32,16 +33,18 @@ interface IProposal { event ProposalExecuted(uint256 indexed proposalId); /// @notice Creates a new proposal. - /// @param data The metadata of the proposal. + /// @param metadata The metadata of the proposal. /// @param actions The actions that will be executed after the proposal passes. /// @param startDate The start date of the proposal. /// @param endDate The end date of the proposal. + /// @param data The additional abi-encoded data to include more necessary fields. /// @return proposalId The id of the proposal. function createProposal( - bytes memory data, - IDAO.Action[] memory actions, + bytes memory metadata, + Action[] memory actions, uint64 startDate, - uint64 endDate + uint64 endDate, + bytes memory data ) external returns (uint256 proposalId); /// @notice Whether proposal can be executed or not. @@ -54,10 +57,15 @@ interface IProposal { /// @param metadata The custom metadata that is passed when creating a proposal. /// @return proposalId The id of the proposal. function createProposalId( - IDAO.Action[] memory actions, + Action[] memory actions, bytes memory metadata ) external view returns (uint256); + /// @notice The human-readable abi format for extra params included in `data` of `createProposal`. + /// @dev Used for UI to easily detect what extra params the contract expects. + /// @return abi ABI of params in `data` of `createProposal`. + function createProposalParamsABI() external view returns (string memory abi); + /// @notice Returns the proposal count determining the next proposal ID. /// @dev This function has been deprecated but due to backwards compatibility, it still stays in the interface /// but returns maximum value of uint256 to let consumers know not to depend on it anymore. diff --git a/contracts/src/plugin/extensions/proposal/Proposal.sol b/contracts/src/plugin/extensions/proposal/Proposal.sol index 42e77421..ba6ae044 100644 --- a/contracts/src/plugin/extensions/proposal/Proposal.sol +++ b/contracts/src/plugin/extensions/proposal/Proposal.sol @@ -27,7 +27,8 @@ abstract contract Proposal is IProposal, ERC165 { type(IProposal).interfaceId ^ IProposal.createProposal.selector ^ IProposal.canExecute.selector ^ - IProposal.createProposalId.selector || + IProposal.createProposalId.selector ^ + IProposal.createProposalParamsABI.selector || _interfaceId == type(IProposal).interfaceId || super.supportsInterface(_interfaceId); } diff --git a/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol b/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol index 13448bc0..0d380df4 100644 --- a/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol +++ b/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol @@ -33,7 +33,8 @@ abstract contract ProposalUpgradeable is IProposal, ERC165Upgradeable { type(IProposal).interfaceId ^ IProposal.createProposal.selector ^ IProposal.canExecute.selector ^ - IProposal.createProposalId.selector || + IProposal.createProposalId.selector ^ + IProposal.createProposalParamsABI.selector || _interfaceId == type(IProposal).interfaceId || super.supportsInterface(_interfaceId); } diff --git a/contracts/test/dao/dao.ts b/contracts/test/dao/dao.ts deleted file mode 100644 index a6c1af75..00000000 --- a/contracts/test/dao/dao.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {IDAO__factory} from '../../typechain'; -import {getInterfaceId} from '@aragon/osx-commons-sdk'; -import {IDAO__factory as IDAO_V1_0_0__factory} from '@aragon/osx-ethers-v1.0.0'; -import {expect} from 'chai'; - -describe('IDAO', function () { - it('has the same interface ID as its initial version introduced in v1.0.0', async () => { - const current = getInterfaceId(IDAO__factory.createInterface()); - const initial = getInterfaceId(IDAO_V1_0_0__factory.createInterface()); - expect(current).to.equal(initial); - }); -});