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

Include EIP-5267 discovery in EIP-712 #3969

Merged
merged 23 commits into from
Feb 8, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/short-roses-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`EIP721`: add EIP-5267 support for better domain discovery.
Amxx marked this conversation as resolved.
Show resolved Hide resolved
27 changes: 27 additions & 0 deletions contracts/interfaces/ERC5267.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface ERC5267 {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();

/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}
70 changes: 51 additions & 19 deletions contracts/utils/cryptography/EIP712.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.0;
pragma solidity ^0.8.8;
frangio marked this conversation as resolved.
Show resolved Hide resolved

import "./ECDSA.sol";
import "../ShortStrings.sol";
import "../../interfaces/ERC5267.sol";

/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
Expand All @@ -24,7 +26,9 @@ import "./ECDSA.sol";
*
* _Available since v3.4._
*/
abstract contract EIP712 {
abstract contract EIP712 is ERC5267 {
using ShortStrings for *;

/* solhint-disable var-name-mixedcase */
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
Expand All @@ -34,7 +38,13 @@ abstract contract EIP712 {

bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
bytes32 private immutable _TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

ShortString private immutable _NAME;
ShortString private immutable _VERSION;
string private _NAME_FALLBACK;
string private _VERSION_FALLBACK;
Amxx marked this conversation as resolved.
Show resolved Hide resolved

/* solhint-enable var-name-mixedcase */

Expand All @@ -51,17 +61,14 @@ abstract contract EIP712 {
* contract upgrade].
*/
constructor(string memory name, string memory version) {
bytes32 hashedName = keccak256(bytes(name));
bytes32 hashedVersion = keccak256(bytes(version));
bytes32 typeHash = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_NAME = name.toShortStringWithFallback(_NAME_FALLBACK);
_VERSION = version.toShortStringWithFallback(_VERSION_FALLBACK);

_HASHED_NAME = keccak256(bytes(name));
_HASHED_VERSION = keccak256(bytes(version));
_CACHED_CHAIN_ID = block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator();
_CACHED_THIS = address(this);
_TYPE_HASH = typeHash;
}

/**
Expand All @@ -71,16 +78,12 @@ abstract contract EIP712 {
if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
return _buildDomainSeparator();
}
}

function _buildDomainSeparator(
bytes32 typeHash,
bytes32 nameHash,
bytes32 versionHash
frangio marked this conversation as resolved.
Show resolved Hide resolved
) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, block.chainid, address(this)));
}

/**
Expand All @@ -101,4 +104,33 @@ abstract contract EIP712 {
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
}

/**
* @dev See {EIP-5267}.
*/
function eip712Domain()
public
view
virtual
override
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
return (
hex"0f", // 01111
_NAME.toStringWithFallback(_NAME_FALLBACK),
_VERSION.toStringWithFallback(_VERSION_FALLBACK),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
}
36 changes: 17 additions & 19 deletions test/governance/Governor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const ethSigUtil = require('eth-sig-util');
const Wallet = require('ethereumjs-wallet').default;
const { fromRpcSig } = require('ethereumjs-util');
const Enums = require('../helpers/enums');
const { EIP712Domain } = require('../helpers/eip712');
const { getDomain, domainType } = require('../helpers/eip712');
const { GovernorHelper } = require('../helpers/governance');

const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior');
Expand All @@ -20,7 +20,7 @@ contract('Governor', function (accounts) {
const empty = web3.utils.toChecksumAddress(web3.utils.randomHex(20));

const name = 'OZ-Governor';
const version = '1';
// const version = '1';
Amxx marked this conversation as resolved.
Show resolved Hide resolved
const tokenName = 'MockToken';
const tokenSymbol = 'MTKN';
const tokenSupply = web3.utils.toWei('100');
Expand Down Expand Up @@ -148,24 +148,22 @@ contract('Governor', function (accounts) {
const voterBySig = Wallet.generate();
const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString());

const signature = async message => {
return fromRpcSig(
ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), {
data: {
types: {
EIP712Domain,
Ballot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
],
},
domain: { name, version, chainId: this.chainId, verifyingContract: this.mock.address },
primaryType: 'Ballot',
message,
const signature = (contract, message) =>
getDomain(contract)
.then(domain => ({
primaryType: 'Ballot',
types: {
EIP712Domain: domainType(domain),
Ballot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
],
},
}),
);
};
domain,
message,
}))
.then(data => ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), { data }))
.then(fromRpcSig);

await this.token.delegate(voterBySigAddress, { from: voter1 });

Expand Down
40 changes: 19 additions & 21 deletions test/governance/extensions/GovernorWithParams.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const ethSigUtil = require('eth-sig-util');
const Wallet = require('ethereumjs-wallet').default;
const { fromRpcSig } = require('ethereumjs-util');
const Enums = require('../../helpers/enums');
const { EIP712Domain } = require('../../helpers/eip712');
const { getDomain, domainType } = require('../../helpers/eip712');
const { GovernorHelper } = require('../../helpers/governance');

const Token = artifacts.require('$ERC20VotesComp');
Expand All @@ -22,7 +22,7 @@ contract('GovernorWithParams', function (accounts) {
const [owner, proposer, voter1, voter2, voter3, voter4] = accounts;

const name = 'OZ-Governor';
const version = '1';
// const version = '1';
Amxx marked this conversation as resolved.
Show resolved Hide resolved
const tokenName = 'MockToken';
const tokenSymbol = 'MTKN';
const tokenSupply = web3.utils.toWei('100');
Expand Down Expand Up @@ -116,26 +116,24 @@ contract('GovernorWithParams', function (accounts) {
const voterBySig = Wallet.generate();
const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString());

const signature = async message => {
return fromRpcSig(
ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), {
data: {
types: {
EIP712Domain,
ExtendedBallot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
{ name: 'reason', type: 'string' },
{ name: 'params', type: 'bytes' },
],
},
domain: { name, version, chainId: this.chainId, verifyingContract: this.mock.address },
primaryType: 'ExtendedBallot',
message,
const signature = (contract, message) =>
getDomain(contract)
.then(domain => ({
primaryType: 'ExtendedBallot',
types: {
EIP712Domain: domainType(domain),
ExtendedBallot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
{ name: 'reason', type: 'string' },
{ name: 'params', type: 'bytes' },
],
},
}),
);
};
domain,
message,
}))
.then(data => ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), { data }))
.then(fromRpcSig);

await this.token.delegate(voterBySigAddress, { from: voter2 });

Expand Down