Commit caa1919f by Francisco Giordano

Merge remote-tracking branch 'vanilla/master' into patches

parents 963cb6af 4ac1070c
name: Upgradeable Trigger
on:
push:
branches:
- master
- release-v*
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- id: app
uses: getsentry/action-github-app-token@v1
with:
app_id: ${{ secrets.UPGRADEABLE_APP_ID }}
private_key: ${{ secrets.UPGRADEABLE_APP_PK }}
- run: |
curl -X POST \
https://api.github.com/repos/OpenZeppelin/openzeppelin-contracts-upgradeable/dispatches \
-H 'Accept: application/vnd.github.v3+json' \
-H 'Authorization: token ${{ steps.app.outputs.token }}' \
-d '{ "event_type": "Update", "client_payload": { "ref": "${{ github.ref }}" } }'
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
* `ERC2771Context`: use private variable from storage to store the forwarder address. Fixes issues where `_msgSender()` was not callable from constructors. ([#2754](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2754)) * `ERC2771Context`: use private variable from storage to store the forwarder address. Fixes issues where `_msgSender()` was not callable from constructors. ([#2754](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2754))
* `EnumerableSet`: add `values()` functions that returns an array containing all values in a single call. ([#2768](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2768)) * `EnumerableSet`: add `values()` functions that returns an array containing all values in a single call. ([#2768](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2768))
* `Governor`: added a modular system of `Governor` contracts based on `GovernorAlpha` and `GovernorBravo`. ([#2672](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2672)) * `Governor`: added a modular system of `Governor` contracts based on `GovernorAlpha` and `GovernorBravo`. ([#2672](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2672))
* Add an `interfaces` folder containing solidity interfaces to final ERCs. ([#2517](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2517))
* `ECDSA`: add `tryRecover` functions that will not throw if the signature is invalid, and will return an error flag instead. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661))
* `SignatureChecker`: Reduce gas usage of the `isValidSignatureNow` function for the "signature by EOA" case. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661))
## 4.2.0 (2021-06-30) ## 4.2.0 (2021-06-30)
......
...@@ -2,26 +2,12 @@ ...@@ -2,26 +2,12 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol"; import "../utils/Context.sol";
import "../utils/Strings.sol"; import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol"; import "../utils/introspection/ERC165.sol";
/** /**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
function hasRole(bytes32 role, address account) external view returns (bool);
function getRoleAdmin(bytes32 role) external view returns (bytes32);
function grantRole(bytes32 role, address account) external;
function revokeRole(bytes32 role, address account) external;
function renounceRole(bytes32 role, address account) external;
}
/**
* @dev Contract module that allows children to implement role-based access * @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role * control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some * members except through off-chain means by accessing the contract event logs. Some
...@@ -70,33 +56,6 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { ...@@ -70,33 +56,6 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 {
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/** /**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Modifier that checks that an account has a specific role. Reverts * @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role. * with a standardized message including the required role.
* *
......
...@@ -2,19 +2,11 @@ ...@@ -2,19 +2,11 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "./IAccessControlEnumerable.sol";
import "./AccessControl.sol"; import "./AccessControl.sol";
import "../utils/structs/EnumerableSet.sol"; import "../utils/structs/EnumerableSet.sol";
/** /**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable {
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role. * @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/ */
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
...@@ -56,7 +48,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon ...@@ -56,7 +48,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon
/** /**
* @dev Overload {grantRole} to track enumerable memberships * @dev Overload {grantRole} to track enumerable memberships
*/ */
function grantRole(bytes32 role, address account) public virtual override { function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
super.grantRole(role, account); super.grantRole(role, account);
_roleMembers[role].add(account); _roleMembers[role].add(account);
} }
...@@ -64,7 +56,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon ...@@ -64,7 +56,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon
/** /**
* @dev Overload {revokeRole} to track enumerable memberships * @dev Overload {revokeRole} to track enumerable memberships
*/ */
function revokeRole(bytes32 role, address account) public virtual override { function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
super.revokeRole(role, account); super.revokeRole(role, account);
_roleMembers[role].remove(account); _roleMembers[role].remove(account);
} }
...@@ -72,7 +64,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon ...@@ -72,7 +64,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon
/** /**
* @dev Overload {renounceRole} to track enumerable memberships * @dev Overload {renounceRole} to track enumerable memberships
*/ */
function renounceRole(bytes32 role, address account) public virtual override { function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
super.renounceRole(role, account); super.renounceRole(role, account);
_roleMembers[role].remove(account); _roleMembers[role].remove(account);
} }
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
...@@ -12,6 +12,10 @@ This directory provides ways to restrict who can access the functions of a contr ...@@ -12,6 +12,10 @@ This directory provides ways to restrict who can access the functions of a contr
{{Ownable}} {{Ownable}}
{{IAccessControl}}
{{AccessControl}} {{AccessControl}}
{{IAccessControlEnumerable}}
{{AccessControlEnumerable}} {{AccessControlEnumerable}}
...@@ -14,7 +14,7 @@ import "./IGovernor.sol"; ...@@ -14,7 +14,7 @@ import "./IGovernor.sol";
/** /**
* @dev Core of the governance system, designed to be extended though various modules. * @dev Core of the governance system, designed to be extended though various modules.
* *
* This contract is abstract and requiers several function to be implemented in various modules: * This contract is abstract and requires several function to be implemented in various modules:
* *
* - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote} * - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote}
* - A voting module must implement {getVotes} * - A voting module must implement {getVotes}
...@@ -137,7 +137,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { ...@@ -137,7 +137,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
} }
/** /**
* @dev Amount of votes already casted passes the threshold limit. * @dev Amount of votes already cast passes the threshold limit.
*/ */
function _quorumReached(uint256 proposalId) internal view virtual returns (bool); function _quorumReached(uint256 proposalId) internal view virtual returns (bool);
...@@ -305,7 +305,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor { ...@@ -305,7 +305,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
} }
/** /**
* @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been casted yet, retrieve * @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve
* voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. * voting weight using {IGovernor-getVotes} and call the {_countVote} internal function.
* *
* Emits a {IGovernor-VoteCast} event. * Emits a {IGovernor-VoteCast} event.
......
...@@ -47,7 +47,7 @@ abstract contract IGovernor is IERC165 { ...@@ -47,7 +47,7 @@ abstract contract IGovernor is IERC165 {
event ProposalExecuted(uint256 proposalId); event ProposalExecuted(uint256 proposalId);
/** /**
* @dev Emitted when a vote is casted. * @dev Emitted when a vote is cast.
* *
* Note: `support` values should be seen as buckets. There interpretation depends on the voting module used. * Note: `support` values should be seen as buckets. There interpretation depends on the voting module used.
*/ */
...@@ -131,7 +131,7 @@ abstract contract IGovernor is IERC165 { ...@@ -131,7 +131,7 @@ abstract contract IGovernor is IERC165 {
/** /**
* @notice module:user-config * @notice module:user-config
* @dev Minimum number of casted voted requiered for a proposal to be successful. * @dev Minimum number of cast voted required for a proposal to be successful.
* *
* Note: The `blockNumber` parameter corresponds to the snaphot used for counting vote. This allows to scale the * Note: The `blockNumber` parameter corresponds to the snaphot used for counting vote. This allows to scale the
* quroum depending on values such as the totalSupply of a token at this block (see {ERC20Votes}). * quroum depending on values such as the totalSupply of a token at this block (see {ERC20Votes}).
...@@ -149,7 +149,7 @@ abstract contract IGovernor is IERC165 { ...@@ -149,7 +149,7 @@ abstract contract IGovernor is IERC165 {
/** /**
* @notice module:voting * @notice module:voting
* @dev Returns weither `account` has casted a vote on `proposalId`. * @dev Returns weither `account` has cast a vote on `proposalId`.
*/ */
function hasVoted(uint256 proposalId, address account) public view virtual returns (bool); function hasVoted(uint256 proposalId, address account) public view virtual returns (bool);
...@@ -167,12 +167,12 @@ abstract contract IGovernor is IERC165 { ...@@ -167,12 +167,12 @@ abstract contract IGovernor is IERC165 {
) public virtual returns (uint256 proposalId); ) public virtual returns (uint256 proposalId);
/** /**
* @dev Execute a successful proposal. This requiers the quorum to be reached, the vote to be successful, and the * @dev Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the
* deadline to be reached. * deadline to be reached.
* *
* Emits a {ProposalExecuted} event. * Emits a {ProposalExecuted} event.
* *
* Note: some module can modify the requierements for execution, for example by adding an additional timelock. * Note: some module can modify the requirements for execution, for example by adding an additional timelock.
*/ */
function execute( function execute(
address[] memory targets, address[] memory targets,
......
...@@ -7,31 +7,48 @@ This directory includes primitives for on-chain governance. ...@@ -7,31 +7,48 @@ This directory includes primitives for on-chain governance.
== Governor == Governor
The {Governor} contract provides primitive to set an on-chain voting system similar to https://compound.finance/docs/governance[Compound's Governor Alpha & Bravo]. This modular system of Governor contracts allows the deployment on-chain voting protocols similar to https://compound.finance/docs/governance[Compound's Governor Alpha & Bravo] and beyond, through the ability to easily customize multiple aspects of the protocol.
Similarly to our other contracts, it is customizable through inheritance and comes with extensions: [TIP]
====
For a guided experience, set up your Governor contract using https://wizard.openzeppelin.com/#governor[Contracts Wizard].
* {GovernorTimelockControl}: A {Governor} extension that performs executions through a {TimelockController}. This requires a successful proposal to be queued before then can be executed. The {TimelockController} will enforce a delay between the queueing and the execution. With this module, proposals are executed by the external {TimelockController} contract, which would have to hold the assets that are being governed. For a written walkthrough, check out our guide on xref:ROOT:governance.adoc[How to set up on-chain governance].
====
* {GovernorTimelockCompound}: A {Governor} extension that performs executions through a compound https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[`Timelock`]. This requires a successful proposal to be queued before then can be executed. The `Timelock` will enforce a delay between the queueing and the execution. With this module, proposals are executed by the external `Timelock` contract, which would have to hold the assets that are being governed. * {Governor}: The core contract that contains all the logic and primitives. It is abstract and requires choosing one of each of the modules below, or custom ones.
* {GovernorCountingSimple}: A simple voting mechanism for {Governor} with support 3 vote options: Against, For and Abstain. Votes modules determine the source of voting power, and sometimes quorum number.
* {GovernorVotes}: Binding to extract voting weight from an {ERC20Votes} token. * {GovernorVotes}: Extracts voting weight from an {ERC20Votes} token.
* {GovernorVotesQuorumFraction}: Binding to extract voting weight from an {ERC20Votes} token and set the quorum as a fraction of the (snapshoted) total token supply. * {GovernorVotesComp}: Extracts voting weight from a COMP-like or {ERC20VotesComp} token.
* {GovernorVotesComp}: Binding to extract voting weight from a Comp or {ERC20VotesComp} token. * {GovernorVotesQuorumFraction}: Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply.
In addition to modules, the {Governor} requires a few virtual functions to be implemented to your particular specifications: Counting modules determine valid voting options.
* <<Governor-votingOffset-,`votingOffset()`>>: Delay (number of blocks), between the proposal, is submitted and the snapshot block used for voting. This can be used to enforce a delay after a proposal is published for users to buy tokens, or delegate their votes (default: 0). * {GovernorCountingSimple}: Simple voting mechanism with 3 voting options: Against, For and Abstain.
* <<Governor-votingDuration-,`votingDuration()`>>: Delay (in seconds), between the proposal, is submitted and the vote ends.
* <<Governor-quorum-uint256-,`quorum(uint256 blockNumber)`>>: Quorum required for a proposal to be successful. This function includes a `blockNumber` argument so the quorum can adapt through time, for example, to follow a token's `totalSupply`. Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed.
* {GovernorTimelockControl}: Connects with an instance of {TimelockController}. Allows multiple proposers and executors, in addition to the Governor itself.
* {GovernorTimelockCompound}: Connects with an instance of Compound's https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[`Timelock`] contract.
Other extensions can customize the behavior or interface in multiple ways.
Note: Function of the {Governor} contract does NOT include access control. If you want to restrict access (for example to require a minimum balance to submit a proposal), you should add these checks by overloading the particular functions. For security reasons, the {Governor-_cancel} method is internal, and you will have to expose it (which the right access control mechanism) yourself if this is a mechanism you need. * {GovernorCompatibilityBravo}: Extends the interface to be fully `GovernorBravo`-compatible. Note that events are compatible regardless of whether this extension is included or not.
Events emitted by the {Governor} contract are compatible with Compound's `GovernorBravo`. Additionnaly, function compatibility can be added using the {GovernorCompatibilityBravo} compatibility layer. This layer includes a voting system but does not include token bindings. This layer also requiers a timelock module (either {GovernorTimelockControl} or {GovernorTimelockCompound}). * {GovernorProposalThreshold}: Restricts proposals to delegates with a minimum voting power.
In addition to modules and extensions, the core contract requires a few virtual functions to be implemented to your particular specifications:
* <<Governor-votingDelay-,`votingDelay()`>>: Delay (in number of blocks) since the proposal is submitted until voting power is fixed and voting starts. This can be used to enforce a delay after a proposal is published for users to buy tokens, or delegate their votes.
* <<Governor-votingPeriod-,`votingPeriod()`>>: Delay (in number of blocks) since the proposal starts until voting ends.
* <<Governor-quorum-uint256-,`quorum(uint256 blockNumber)`>>: Quorum required for a proposal to be successful. This function includes a `blockNumber` argument so the quorum can adapt through time, for example, to follow a token's `totalSupply`.
NOTE: Functions of the `Governor` contract do not include access control. If you want to restrict access, you should add these checks by overloading the particular functions. Among these, {Governor-_cancel} is internal by default, and you will have to expose it (which the right access control mechanism) yourself if this function is needed.
=== Core === Core
...@@ -39,11 +56,7 @@ Events emitted by the {Governor} contract are compatible with Compound's `Govern ...@@ -39,11 +56,7 @@ Events emitted by the {Governor} contract are compatible with Compound's `Govern
{{Governor}} {{Governor}}
=== Extensions === Modules
{{GovernorTimelockControl}}
{{GovernorTimelockCompound}}
{{GovernorCountingSimple}} {{GovernorCountingSimple}}
...@@ -53,7 +66,13 @@ Events emitted by the {Governor} contract are compatible with Compound's `Govern ...@@ -53,7 +66,13 @@ Events emitted by the {Governor} contract are compatible with Compound's `Govern
{{GovernorVotesComp}} {{GovernorVotesComp}}
=== Compatibility === Extensions
{{GovernorTimelockControl}}
{{GovernorTimelockCompound}}
{{GovernorProposalThreshold}}
{{GovernorCompatibilityBravo}} {{GovernorCompatibilityBravo}}
......
...@@ -283,7 +283,7 @@ abstract contract GovernorCompatibilityBravo is ...@@ -283,7 +283,7 @@ abstract contract GovernorCompatibilityBravo is
ProposalDetails storage details = _proposalDetails[proposalId]; ProposalDetails storage details = _proposalDetails[proposalId];
Receipt storage receipt = details.receipts[account]; Receipt storage receipt = details.receipts[account];
require(!receipt.hasVoted, "GovernorCompatibilityBravo: vote already casted"); require(!receipt.hasVoted, "GovernorCompatibilityBravo: vote already cast");
receipt.hasVoted = true; receipt.hasVoted = true;
receipt.support = support; receipt.support = support;
receipt.votes = SafeCast.toUint96(weight); receipt.votes = SafeCast.toUint96(weight);
......
...@@ -89,7 +89,7 @@ abstract contract GovernorCountingSimple is Governor { ...@@ -89,7 +89,7 @@ abstract contract GovernorCountingSimple is Governor {
) internal virtual override { ) internal virtual override {
ProposalVote storage proposalvote = _proposalVotes[proposalId]; ProposalVote storage proposalvote = _proposalVotes[proposalId];
require(!proposalvote.hasVoted[account], "GovernorVotingSimple: vote already casted"); require(!proposalvote.hasVoted[account], "GovernorVotingSimple: vote already cast");
proposalvote.hasVoted[account] = true; proposalvote.hasVoted[account] = true;
if (support == uint8(VoteType.Against)) { if (support == uint8(VoteType.Against)) {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC1155/IERC1155.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC1155/extensions/IERC1155MetadataURI.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC1155/IERC1155Receiver.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./IERC165.sol";
interface IERC1363 is IERC165, IERC20 {
/*
* Note: the ERC-165 identifier for this interface is 0x4bbee2df.
* 0x4bbee2df ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)'))
*/
/*
* Note: the ERC-165 identifier for this interface is 0xfb9ec8ce.
* 0xfb9ec8ce ===
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver
* @param to address The address which you want to transfer to
* @param value uint256 The amount of tokens to be transferred
* @return true unless throwing
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver
* @param to address The address which you want to transfer to
* @param value uint256 The amount of tokens to be transferred
* @param data bytes Additional data with no specified format, sent in call to `to`
* @return true unless throwing
*/
function transferAndCall(
address to,
uint256 value,
bytes memory data
) external returns (bool);
/**
* @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 The amount of tokens to be transferred
* @return true unless throwing
*/
function transferFromAndCall(
address from,
address to,
uint256 value
) external returns (bool);
/**
* @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 The amount of tokens to be transferred
* @param data bytes Additional data with no specified format, sent in call to `to`
* @return true unless throwing
*/
function transferFromAndCall(
address from,
address to,
uint256 value,
bytes memory data
) external returns (bool);
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender
* and then call `onApprovalReceived` on spender.
* @param spender address The address which will spend the funds
* @param value uint256 The amount of tokens to be spent
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender
* and then call `onApprovalReceived` on spender.
* @param spender address The address which will spend the funds
* @param value uint256 The amount of tokens to be spent
* @param data bytes Additional data with no specified format, sent in call to `spender`
*/
function approveAndCall(
address spender,
uint256 value,
bytes memory data
) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC1363Receiver {
/*
* Note: the ERC-165 identifier for this interface is 0x88a7ca5c.
* 0x88a7ca5c === bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))
*/
/**
* @notice Handle the receipt of ERC1363 tokens
* @dev Any ERC1363 smart contract calls this function on the recipient
* after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the
* transfer. Return of other than the magic value MUST result in the
* transaction being reverted.
* Note: the token contract address is always the message sender.
* @param operator address The address which called `transferAndCall` or `transferFromAndCall` function
* @param from address The address which are token transferred from
* @param value uint256 The amount of tokens transferred
* @param data bytes Additional data with no specified format
* @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))`
* unless throwing
*/
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes memory data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC1363Spender {
/*
* Note: the ERC-165 identifier for this interface is 0x7b04a2d0.
* 0x7b04a2d0 === bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))
*/
/**
* @notice Handle the approval of ERC1363 tokens
* @dev Any ERC1363 smart contract calls this function on the recipient
* after an `approve`. This function MAY throw to revert and reject the
* approval. Return of other than the magic value MUST result in the
* transaction being reverted.
* Note: the token contract address is always the message sender.
* @param owner address The address which called `approveAndCall` function
* @param value uint256 The amount of tokens to be spent
* @param data bytes Additional data with no specified format
* @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`
* unless throwing
*/
function onApprovalReceived(
address owner,
uint256 value,
bytes memory data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/introspection/IERC1820Implementer.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/introspection/IERC1820Registry.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC20/extensions/IERC20Metadata.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard
*/
interface IERC2981 is IERC165 {
/**
* @dev Called with the sale price to determine how much royalty is owed and to whom.
* @param tokenId - the NFT asset queried for royalty information
* @param salePrice - the sale price of the NFT asset specified by `tokenId`
* @return receiver - address of who should be sent the royalty payment
* @return royaltyAmount - the royalty payment amount for `salePrice`
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
}
...@@ -2,62 +2,5 @@ ...@@ -2,62 +2,5 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
/** import "./IERC3156FlashBorrower.sol";
* @dev Interface of the ERC3156 FlashBorrower, as defined in import "./IERC3156FlashLender.sol";
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* _Available since v4.1._
*/
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}
/**
* @dev Interface of the ERC3156 FlashLender, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*/
interface IERC3156FlashLender {
/**
* @dev The amount of currency available to be lended.
* @param token The loan currency.
* @return The amount of `token` that can be borrowed.
*/
function maxFlashLoan(address token) external view returns (uint256);
/**
* @dev The fee to be charged for a given loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function flashFee(address token, uint256 amount) external view returns (uint256);
/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC3156 FlashBorrower, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* _Available since v4.1._
*/
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC3156FlashBorrower.sol";
/**
* @dev Interface of the ERC3156 FlashLender, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* _Available since v4.1._
*/
interface IERC3156FlashLender {
/**
* @dev The amount of currency available to be lended.
* @param token The loan currency.
* @return The amount of `token` that can be borrowed.
*/
function maxFlashLoan(address token) external view returns (uint256);
/**
* @dev The fee to be charged for a given loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function flashFee(address token, uint256 amount) external view returns (uint256);
/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC721/IERC721.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC721/extensions/IERC721Enumerable.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC721/extensions/IERC721Metadata.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC721/IERC721Receiver.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC777/IERC777.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC777/IERC777Recipient.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC777/IERC777Sender.sol";
= Interfaces
[.readme-notice]
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/interfaces
== List of standardized interfaces
These interfaces are available as `.sol` files, and also as compiler `.json` ABI files (through the npm package). These
are usefull to interract with third party contracts that implement them.
- {IERC20}
- {IERC20Metadata}
- {IERC165}
- {IERC721}
- {IERC721Receiver}
- {IERC721Enumerable}
- {IERC721Metadata}
- {IERC777}
- {IERC777Recipient}
- {IERC777Sender}
- {IERC1155}
- {IERC1155Receiver}
- {IERC1155MetadataURI}
- {IERC1271}
- {IERC1363}
- {IERC1820Implementer}
- {IERC1820Registry}
- {IERC2612}
- {IERC2981}
- {IERC3156FlashLender}
- {IERC3156FlashBorrower}
== Detailed ABI
{{IERC1271}}
{{IERC1363}}
{{IERC1363Receiver}}
{{IERC1820Implementer}}
{{IERC1820Registry}}
{{IERC2612}}
{{IERC2981}}
{{IERC3156FlashLender}}
{{IERC3156FlashBorrower}}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../token/ERC20/extensions/draft-IERC20Permit.sol";
interface IERC2612 is IERC20Permit {}
...@@ -11,6 +11,25 @@ contract ECDSAMock { ...@@ -11,6 +11,25 @@ contract ECDSAMock {
return hash.recover(signature); return hash.recover(signature);
} }
// solhint-disable-next-line func-name-mixedcase
function recover_v_r_s(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) public pure returns (address) {
return hash.recover(v, r, s);
}
// solhint-disable-next-line func-name-mixedcase
function recover_r_vs(
bytes32 hash,
bytes32 r,
bytes32 vs
) public pure returns (address) {
return hash.recover(r, vs);
}
function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) {
return hash.toEthSignedMessageHash(); return hash.toEthSignedMessageHash();
} }
......
...@@ -9,9 +9,31 @@ pragma solidity ^0.8.0; ...@@ -9,9 +9,31 @@ pragma solidity ^0.8.0;
* of the private keys of a given address. * of the private keys of a given address.
*/ */
library ECDSA { library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
} else if (error == RecoverError.InvalidSignatureV) {
revert("ECDSA: invalid signature 'v' value");
}
}
/** /**
* @dev Returns the address that signed a hashed message (`hash`) with * @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes. * `signature` or error string. This address can then be used for verification purposes.
* *
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower * this function rejects them by requiring the `s` value to be in the lower
...@@ -26,8 +48,10 @@ library ECDSA { ...@@ -26,8 +48,10 @@ library ECDSA {
* Documentation for signature generation: * Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/ */
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
// Check the signature length // Check the signature length
// - case 65: r,s,v signature (standard) // - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._ // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
...@@ -42,7 +66,7 @@ library ECDSA { ...@@ -42,7 +66,7 @@ library ECDSA {
s := mload(add(signature, 0x40)) s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60))) v := byte(0, mload(add(signature, 0x60)))
} }
return recover(hash, v, r, s); return tryRecover(hash, v, r, s);
} else if (signature.length == 64) { } else if (signature.length == 64) {
bytes32 r; bytes32 r;
bytes32 vs; bytes32 vs;
...@@ -52,42 +76,80 @@ library ECDSA { ...@@ -52,42 +76,80 @@ library ECDSA {
r := mload(add(signature, 0x20)) r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40)) vs := mload(add(signature, 0x40))
} }
return recover(hash, r, vs); return tryRecover(hash, r, vs);
} else { } else {
revert("ECDSA: invalid signature length"); return (address(0), RecoverError.InvalidSignatureLength);
}
} }
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
} }
/** /**
* @dev Overload of {ECDSA-recover} that receives the `r` and `vs` short-signature fields separately. * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
* *
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
* *
* _Available since v4.2._ * _Available since v4.3._
*/ */
function recover( function tryRecover(
bytes32 hash, bytes32 hash,
bytes32 r, bytes32 r,
bytes32 vs bytes32 vs
) internal pure returns (address) { ) internal pure returns (address, RecoverError) {
bytes32 s; bytes32 s;
uint8 v; uint8 v;
assembly { assembly {
s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
v := add(shr(255, vs), 27) v := add(shr(255, vs), 27)
} }
return recover(hash, v, r, s); return tryRecover(hash, v, r, s);
} }
/** /**
* @dev Overload of {ECDSA-recover} that receives the `v`, `r` and `s` signature fields separately. * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/ */
function recover( function recover(
bytes32 hash, bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v, uint8 v,
bytes32 r, bytes32 r,
bytes32 s bytes32 s
) internal pure returns (address) { ) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
...@@ -97,17 +159,35 @@ library ECDSA { ...@@ -97,17 +159,35 @@ library ECDSA {
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well. // these malleable signatures as well.
require( if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, return (address(0), RecoverError.InvalidSignatureS);
"ECDSA: invalid signature 's' value" }
); if (v != 27 && v != 28) {
require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); return (address(0), RecoverError.InvalidSignatureV);
}
// If the signature is valid (and not malleable), return the signer address // If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s); address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature"); if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return signer; return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
} }
/** /**
......
...@@ -22,14 +22,14 @@ library SignatureChecker { ...@@ -22,14 +22,14 @@ library SignatureChecker {
bytes32 hash, bytes32 hash,
bytes memory signature bytes memory signature
) internal view returns (bool) { ) internal view returns (bool) {
if (Address.isContract(signer)) { (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);
try IERC1271(signer).isValidSignature(hash, signature) returns (bytes4 magicValue) { if (error == ECDSA.RecoverError.NoError && recovered == signer) {
return magicValue == IERC1271.isValidSignature.selector; return true;
} catch {
return false;
}
} else {
return ECDSA.recover(hash, signature) == signer;
} }
(bool success, bytes memory result) = signer.staticcall(
abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)
);
return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
} }
} }
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
In this guide we will learn how OpenZeppelin’s Govenor contract works, how to set it up, and how to use it to create proposals, vote for them, and execute them, using tools provided by Ethers.js and Tally. In this guide we will learn how OpenZeppelin’s Govenor contract works, how to set it up, and how to use it to create proposals, vote for them, and execute them, using tools provided by Ethers.js and Tally.
NOTE: Find detailed contract documentation at xref:api:governance.adoc[Governance API].
== Introduction == Introduction
Decentralized protocols are in constant evolution from the moment they are publicly released. Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc. Decentralized protocols are in constant evolution from the moment they are publicly released. Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc.
......
...@@ -15,7 +15,7 @@ The data signer can be recovered with xref:api:cryptography.adoc#ECDSA-recover-b ...@@ -15,7 +15,7 @@ The data signer can be recovered with xref:api:cryptography.adoc#ECDSA-recover-b
---- ----
using ECDSA for bytes32; using ECDSA for bytes32;
function _verify(bytes32 data, address account) pure returns (bool) { function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
return keccak256(data) return keccak256(data)
.toEthSignedMessageHash() .toEthSignedMessageHash()
.recover(signature) == account; .recover(signature) == account;
......
...@@ -59,6 +59,7 @@ module.exports = { ...@@ -59,6 +59,7 @@ module.exports = {
}, },
networks: { networks: {
hardhat: { hardhat: {
hardfork: process.env.COVERAGE ? 'berlin' : 'london',
blockGasLimit: 10000000, blockGasLimit: 10000000,
allowUnlimitedContractSize: !withOptimizations, allowUnlimitedContractSize: !withOptimizations,
}, },
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
}, },
"scripts": { "scripts": {
"compile": "hardhat compile", "compile": "hardhat compile",
"coverage": "hardhat coverage", "coverage": "env COVERAGE=true hardhat coverage",
"docs": "oz-docs", "docs": "oz-docs",
"docs:watch": "npm run docs watch contracts 'docs/*.hbs' docs/helpers.js", "docs:watch": "npm run docs watch contracts 'docs/*.hbs' docs/helpers.js",
"prepare-docs": "scripts/prepare-docs.sh", "prepare-docs": "scripts/prepare-docs.sh",
...@@ -54,7 +54,8 @@ ...@@ -54,7 +54,8 @@
"@nomiclabs/hardhat-truffle5": "^2.0.0", "@nomiclabs/hardhat-truffle5": "^2.0.0",
"@nomiclabs/hardhat-web3": "^2.0.0", "@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/docs-utils": "^0.1.0", "@openzeppelin/docs-utils": "^0.1.0",
"@openzeppelin/test-helpers": "^0.5.9", "@openzeppelin/test-helpers": "^0.5.13",
"@truffle/abi-utils": "^0.2.3",
"chai": "^4.2.0", "chai": "^4.2.0",
"eslint": "^6.5.1", "eslint": "^6.5.1",
"eslint-config-standard": "^14.1.1", "eslint-config-standard": "^14.1.1",
......
...@@ -102,21 +102,21 @@ contract('PaymentSplitter', function (accounts) { ...@@ -102,21 +102,21 @@ contract('PaymentSplitter', function (accounts) {
// distribute to payees // distribute to payees
const initAmount1 = await balance.current(payee1); const tracker1 = await balance.tracker(payee1);
const { logs: logs1 } = await this.contract.release(payee1, { gasPrice: 0 }); const { logs: logs1 } = await this.contract.release(payee1);
const profit1 = (await balance.current(payee1)).sub(initAmount1); const profit1 = await tracker1.delta();
expect(profit1).to.be.bignumber.equal(ether('0.20')); expect(profit1).to.be.bignumber.equal(ether('0.20'));
expectEvent.inLogs(logs1, 'PaymentReleased', { to: payee1, amount: profit1 }); expectEvent.inLogs(logs1, 'PaymentReleased', { to: payee1, amount: profit1 });
const initAmount2 = await balance.current(payee2); const tracker2 = await balance.tracker(payee2);
const { logs: logs2 } = await this.contract.release(payee2, { gasPrice: 0 }); const { logs: logs2 } = await this.contract.release(payee2);
const profit2 = (await balance.current(payee2)).sub(initAmount2); const profit2 = await tracker2.delta();
expect(profit2).to.be.bignumber.equal(ether('0.10')); expect(profit2).to.be.bignumber.equal(ether('0.10'));
expectEvent.inLogs(logs2, 'PaymentReleased', { to: payee2, amount: profit2 }); expectEvent.inLogs(logs2, 'PaymentReleased', { to: payee2, amount: profit2 });
const initAmount3 = await balance.current(payee3); const tracker3 = await balance.tracker(payee3);
const { logs: logs3 } = await this.contract.release(payee3, { gasPrice: 0 }); const { logs: logs3 } = await this.contract.release(payee3);
const profit3 = (await balance.current(payee3)).sub(initAmount3); const profit3 = await tracker3.delta();
expect(profit3).to.be.bignumber.equal(ether('0.70')); expect(profit3).to.be.bignumber.equal(ether('0.70'));
expectEvent.inLogs(logs3, 'PaymentReleased', { to: payee3, amount: profit3 }); expectEvent.inLogs(logs3, 'PaymentReleased', { to: payee3, amount: profit3 });
......
...@@ -440,7 +440,7 @@ contract('Governor', function (accounts) { ...@@ -440,7 +440,7 @@ contract('Governor', function (accounts) {
voter: voter1, voter: voter1,
weight: web3.utils.toWei('5'), weight: web3.utils.toWei('5'),
support: Enums.VoteType.For, support: Enums.VoteType.For,
error: 'GovernorVotingSimple: vote already casted', error: 'GovernorVotingSimple: vote already cast',
}, },
], ],
}; };
......
...@@ -110,7 +110,7 @@ contract('GovernorCompatibilityBravo', function (accounts) { ...@@ -110,7 +110,7 @@ contract('GovernorCompatibilityBravo', function (accounts) {
{ {
voter: voter1, voter: voter1,
support: Enums.VoteType.For, support: Enums.VoteType.For,
error: 'GovernorCompatibilityBravo: vote already casted', error: 'GovernorCompatibilityBravo: vote already cast',
skip: true, skip: true,
}, },
], ],
...@@ -296,7 +296,7 @@ contract('GovernorCompatibilityBravo', function (accounts) { ...@@ -296,7 +296,7 @@ contract('GovernorCompatibilityBravo', function (accounts) {
{ {
voter: voter1, voter: voter1,
support: Enums.VoteType.For, support: Enums.VoteType.For,
error: 'GovernorCompatibilityBravo: vote already casted', error: 'GovernorCompatibilityBravo: vote already cast',
skip: true, skip: true,
}, },
], ],
......
...@@ -10,7 +10,12 @@ const WRONG_MESSAGE = web3.utils.sha3('Nope'); ...@@ -10,7 +10,12 @@ const WRONG_MESSAGE = web3.utils.sha3('Nope');
function to2098Format (signature) { function to2098Format (signature) {
const long = web3.utils.hexToBytes(signature); const long = web3.utils.hexToBytes(signature);
expect(long.length).to.be.equal(65); if (long.length !== 65) {
throw new Error('invalid signature length (expected long format)');
}
if (long[32] >> 7 === 1) {
throw new Error('invalid signature \'s\' value');
}
const short = long.slice(0, 64); const short = long.slice(0, 64);
short[32] |= (long[64] % 27) << 7; // set the first bit of the 32nd byte to the v parity bit short[32] |= (long[64] % 27) << 7; // set the first bit of the 32nd byte to the v parity bit
return web3.utils.bytesToHex(short); return web3.utils.bytesToHex(short);
...@@ -18,12 +23,33 @@ function to2098Format (signature) { ...@@ -18,12 +23,33 @@ function to2098Format (signature) {
function from2098Format (signature) { function from2098Format (signature) {
const short = web3.utils.hexToBytes(signature); const short = web3.utils.hexToBytes(signature);
expect(short.length).to.be.equal(64); if (short.length !== 64) {
throw new Error('invalid signature length (expected short format)');
}
short.push((short[32] >> 7) + 27); short.push((short[32] >> 7) + 27);
short[32] &= (1 << 7) - 1; // zero out the first bit of 1 the 32nd byte short[32] &= (1 << 7) - 1; // zero out the first bit of 1 the 32nd byte
return web3.utils.bytesToHex(short); return web3.utils.bytesToHex(short);
} }
function split (signature) {
const raw = web3.utils.hexToBytes(signature);
switch (raw.length) {
case 64:
return [
web3.utils.bytesToHex(raw.slice(0, 32)), // r
web3.utils.bytesToHex(raw.slice(32, 64)), // vs
];
case 65:
return [
raw[64], // v
web3.utils.bytesToHex(raw.slice(0, 32)), // r
web3.utils.bytesToHex(raw.slice(32, 64)), // s
];
default:
expect.fail('Invalid siganture length, cannot split');
}
}
contract('ECDSA', function (accounts) { contract('ECDSA', function (accounts) {
const [ other ] = accounts; const [ other ] = accounts;
...@@ -80,12 +106,18 @@ contract('ECDSA', function (accounts) { ...@@ -80,12 +106,18 @@ contract('ECDSA', function (accounts) {
const version = '00'; const version = '00';
const signature = signatureWithoutVersion + version; const signature = signatureWithoutVersion + version;
await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value'); await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value');
await expectRevert(
this.ecdsa.recover_v_r_s(TEST_MESSAGE, ...split(signature)),
'ECDSA: invalid signature \'v\' value',
);
}); });
it('works with 27 as version value', async function () { it('works with 27 as version value', async function () {
const version = '1b'; // 27 = 1b. const version = '1b'; // 27 = 1b.
const signature = signatureWithoutVersion + version; const signature = signatureWithoutVersion + version;
expect(await this.ecdsa.recover(TEST_MESSAGE, signature)).to.equal(signer); expect(await this.ecdsa.recover(TEST_MESSAGE, signature)).to.equal(signer);
expect(await this.ecdsa.recover_v_r_s(TEST_MESSAGE, ...split(signature))).to.equal(signer);
expect(await this.ecdsa.recover_r_vs(TEST_MESSAGE, ...split(to2098Format(signature)))).to.equal(signer);
}); });
it('reverts with wrong version', async function () { it('reverts with wrong version', async function () {
...@@ -94,6 +126,10 @@ contract('ECDSA', function (accounts) { ...@@ -94,6 +126,10 @@ contract('ECDSA', function (accounts) {
const version = '02'; const version = '02';
const signature = signatureWithoutVersion + version; const signature = signatureWithoutVersion + version;
await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value'); await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value');
await expectRevert(
this.ecdsa.recover_v_r_s(TEST_MESSAGE, ...split(signature)),
'ECDSA: invalid signature \'v\' value',
);
}); });
it('works with short EIP2098 format', async function () { it('works with short EIP2098 format', async function () {
...@@ -113,12 +149,18 @@ contract('ECDSA', function (accounts) { ...@@ -113,12 +149,18 @@ contract('ECDSA', function (accounts) {
const version = '01'; const version = '01';
const signature = signatureWithoutVersion + version; const signature = signatureWithoutVersion + version;
await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value'); await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value');
await expectRevert(
this.ecdsa.recover_v_r_s(TEST_MESSAGE, ...split(signature)),
'ECDSA: invalid signature \'v\' value',
);
}); });
it('works with 28 as version value', async function () { it('works with 28 as version value', async function () {
const version = '1c'; // 28 = 1c. const version = '1c'; // 28 = 1c.
const signature = signatureWithoutVersion + version; const signature = signatureWithoutVersion + version;
expect(await this.ecdsa.recover(TEST_MESSAGE, signature)).to.equal(signer); expect(await this.ecdsa.recover(TEST_MESSAGE, signature)).to.equal(signer);
expect(await this.ecdsa.recover_v_r_s(TEST_MESSAGE, ...split(signature))).to.equal(signer);
expect(await this.ecdsa.recover_r_vs(TEST_MESSAGE, ...split(to2098Format(signature)))).to.equal(signer);
}); });
it('reverts with wrong version', async function () { it('reverts with wrong version', async function () {
...@@ -127,6 +169,10 @@ contract('ECDSA', function (accounts) { ...@@ -127,6 +169,10 @@ contract('ECDSA', function (accounts) {
const version = '02'; const version = '02';
const signature = signatureWithoutVersion + version; const signature = signatureWithoutVersion + version;
await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value'); await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature \'v\' value');
await expectRevert(
this.ecdsa.recover_v_r_s(TEST_MESSAGE, ...split(signature)),
'ECDSA: invalid signature \'v\' value',
);
}); });
it('works with short EIP2098 format', async function () { it('works with short EIP2098 format', async function () {
...@@ -141,8 +187,12 @@ contract('ECDSA', function (accounts) { ...@@ -141,8 +187,12 @@ contract('ECDSA', function (accounts) {
const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9';
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
const highSSignature = '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b'; const highSSignature = '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b';
await expectRevert(this.ecdsa.recover(message, highSSignature), 'ECDSA: invalid signature \'s\' value'); await expectRevert(this.ecdsa.recover(message, highSSignature), 'ECDSA: invalid signature \'s\' value');
await expectRevert(
this.ecdsa.recover_v_r_s(TEST_MESSAGE, ...split(highSSignature)),
'ECDSA: invalid signature \'s\' value',
);
expect(() => to2098Format(highSSignature)).to.throw('invalid signature \'s\' value');
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment