From 19a413af3ddecba5a53f70c899a28f581f293b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 15 May 2020 13:07:48 -0300 Subject: [PATCH 01/33] Initial storage-based implementation --- contracts/cryptography/ECDSA.sol | 4 ++ contracts/token/ERC20/ERC20Permit.sol | 62 ++++++++++++++++++++++++ contracts/token/ERC20/IERC2612Permit.sol | 5 ++ scripts/compile.sh | 2 +- 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 contracts/token/ERC20/ERC20Permit.sol create mode 100644 contracts/token/ERC20/IERC2612Permit.sol diff --git a/contracts/cryptography/ECDSA.sol b/contracts/cryptography/ECDSA.sol index 46d412c7140..c3f22f51d82 100644 --- a/contracts/cryptography/ECDSA.sol +++ b/contracts/cryptography/ECDSA.sol @@ -41,6 +41,10 @@ library ECDSA { v := byte(0, mload(add(signature, 0x60))) } + return recover(hash, v, r, s); + } + + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { // 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 // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol new file mode 100644 index 00000000000..6b3e1ed7cdf --- /dev/null +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -0,0 +1,62 @@ +pragma solidity ^0.6.0; + +import "./ERC20.sol"; +import "./IERC2612Permit.sol"; +import "../../cryptography/ECDSA.sol"; + +abstract contract ERC20Permit is ERC20, IERC2612Permit { + mapping (address => uint256) private _nonces; + + bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + string private constant _VERSION = "1"; + + bytes32 public DOMAIN_SEPARATOR; + + constructor() internal { + updateDomainSeparator(); + } + + function updateDomainSeparator() public { + uint256 chainID; + assembly { + chainID := chainid() + } + + DOMAIN_SEPARATOR = keccak256( + abi.encodePacked( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(name())), + keccak256(bytes(_VERSION)), + chainID, + address(this) + ) + ); + } + + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public override { + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + bytes32 hash = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encodePacked( + _PERMIT_TYPEHASH, + owner, + spender, + value, + _nonces[owner], + deadline + ) + ) + ) + ); + + address signer = ECDSA.recover(hash, v, r, s); + require(signer == owner, "ERC20Permit: invalid signature"); + + _nonces[owner] += 1; + _approve(owner, spender, value); + } +} diff --git a/contracts/token/ERC20/IERC2612Permit.sol b/contracts/token/ERC20/IERC2612Permit.sol new file mode 100644 index 00000000000..7c763b66468 --- /dev/null +++ b/contracts/token/ERC20/IERC2612Permit.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.6.0; + +interface IERC2612Permit { + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; +} diff --git a/scripts/compile.sh b/scripts/compile.sh index ab4eb5e0726..b09bf44e518 100755 --- a/scripts/compile.sh +++ b/scripts/compile.sh @@ -6,4 +6,4 @@ fi export OPENZEPPELIN_NON_INTERACTIVE=true -npx oz compile +npx oz compile --evm-version istanbul --solc-version 0.6.7 From 48c41dfc625edeac829a803a4cc0d02de3638705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 15 May 2020 20:50:52 -0300 Subject: [PATCH 02/33] Improve gas efficiency and docs --- contracts/cryptography/ECDSA.sol | 4 ++ contracts/token/ERC20/ERC20Permit.sol | 92 +++++++++++++++++------- contracts/token/ERC20/IERC2612Permit.sol | 42 ++++++++++- contracts/token/ERC20/README.adoc | 8 +++ 4 files changed, 120 insertions(+), 26 deletions(-) diff --git a/contracts/cryptography/ECDSA.sol b/contracts/cryptography/ECDSA.sol index c3f22f51d82..ba598edef42 100644 --- a/contracts/cryptography/ECDSA.sol +++ b/contracts/cryptography/ECDSA.sol @@ -44,6 +44,10 @@ library ECDSA { return recover(hash, v, r, s); } + /** + * @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`, + * `r` and `s` signature fields separately. + */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { // 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 diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol index 6b3e1ed7cdf..aac60314f16 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -4,48 +4,47 @@ import "./ERC20.sol"; import "./IERC2612Permit.sol"; import "../../cryptography/ECDSA.sol"; +/** + * @dev Extension of {ERC20} that allows token holders to use their tokens + * without sending any transactions by setting {IERC20-allowance} with a + * signature using the {permit} method, and then spend them via + * {IERC20-transferFrom}. + * + * The {permit} signature mechanism conforms to the {IERC2612Permit} interface. + */ abstract contract ERC20Permit is ERC20, IERC2612Permit { mapping (address => uint256) private _nonces; bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - string private constant _VERSION = "1"; - bytes32 public DOMAIN_SEPARATOR; + // Mapping of ChainID to domain separators. This is a very gas efficient way + // to not recalculate the domain separator on every call, while still + // automatically detecting ChainID changes. + mapping (uint256 => bytes32) private _domainSeparators; constructor() internal { - updateDomainSeparator(); + _updateDomainSeparator(); } - function updateDomainSeparator() public { - uint256 chainID; - assembly { - chainID := chainid() - } - - DOMAIN_SEPARATOR = keccak256( - abi.encodePacked( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(name())), - keccak256(bytes(_VERSION)), - chainID, - address(this) - ) - ); - } - - function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public override { + /** + * @dev See {IERC2612Permit-permit}. + * + * If https://eips.ethereum.org/EIPS/eip-1344[ChainID] ever changes, the + * EIP712 Domain Separator is automatically recalculated. + */ + function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); bytes32 hash = keccak256( abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, + uint16(0x1901), + _domainSeparator(), keccak256( abi.encodePacked( _PERMIT_TYPEHASH, owner, spender, - value, + amount, _nonces[owner], deadline ) @@ -57,6 +56,49 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { require(signer == owner, "ERC20Permit: invalid signature"); _nonces[owner] += 1; - _approve(owner, spender, value); + _approve(owner, spender, amount); + } + + /** + * @dev See {IERC2612Permit-nonces}. + */ + function nonces(address owner) public view override returns (uint256) { + return _nonces[owner]; + } + + function _updateDomainSeparator() private returns (bytes32) { + uint256 chainID = _chainID(); + + bytes32 newDomainSeparator = keccak256( + abi.encodePacked( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(name())), + keccak256(bytes("1")), // Version + chainID, + address(this) + ) + ); + + _domainSeparators[chainID] = newDomainSeparator; + + return newDomainSeparator; + } + + // Returns the domain separator, updating it if chainID changes + function _domainSeparator() private returns (bytes32) { + bytes32 domainSeparator = _domainSeparators[_chainID()]; + if (domainSeparator != 0x00) { + return domainSeparator; + } else { + return _updateDomainSeparator(); + } + } + + function _chainID() private pure returns (uint256) { + uint256 chainID; + assembly { + chainID := chainid() + } + return chainID; } } diff --git a/contracts/token/ERC20/IERC2612Permit.sol b/contracts/token/ERC20/IERC2612Permit.sol index 7c763b66468..f5a368ae104 100644 --- a/contracts/token/ERC20/IERC2612Permit.sol +++ b/contracts/token/ERC20/IERC2612Permit.sol @@ -1,5 +1,45 @@ pragma solidity ^0.6.0; +/** + * @dev Interface of the ERC2612 standard as defined in the EIP. + * + * Adds the {permit} method, which can be used to change one's + * {IERC20-allowance} without having to send a transaction, by signing a + * message. This allows users to spend tokens without having to hold Ether. + * + * See https://eips.ethereum.org/EIPS/eip-2612. + */ interface IERC2612Permit { - function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; + /** + * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens, + * given `owner`'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; + + /** + * @dev Returns the current ERC2612 nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); } diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index edbd1567efa..9327080c028 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -12,6 +12,7 @@ There a few core contracts that implement the behavior specified in the EIP: Additionally there are multiple custom extensions, including: * designation of addresses that can pause token transfers for all users ({ERC20Pausable}). +* usage of tokens without sending any transactions ({ERC20Permit}). * efficient storage of past token balances to be later queried at any point in time ({ERC20Snapshot}). * destruction of own tokens ({ERC20Burnable}). * enforcement of a cap to the total supply when minting tokens ({ERC20Capped}). @@ -31,14 +32,21 @@ Finally, there are some utilities to interact with ERC20 contracts in various wa {{ERC20Snapshot}} +{{ERC20Permit}} + {{ERC20Pausable}} {{ERC20Burnable}} {{ERC20Capped}} +== Standard Extensions + +{{IERC2612Permit}} + == Utilities {{SafeERC20}} {{TokenTimelock}} + From d1e0f4bbf6e1b18c24af780ac1e62bb36b4bdb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 15 May 2020 20:51:33 -0300 Subject: [PATCH 03/33] Initial sketch for tests --- contracts/mocks/ERC20PermitMock.sol | 14 +++++++ test/token/ERC20/ERC20Permit.test.js | 55 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 contracts/mocks/ERC20PermitMock.sol create mode 100644 test/token/ERC20/ERC20Permit.test.js diff --git a/contracts/mocks/ERC20PermitMock.sol b/contracts/mocks/ERC20PermitMock.sol new file mode 100644 index 00000000000..0051adead74 --- /dev/null +++ b/contracts/mocks/ERC20PermitMock.sol @@ -0,0 +1,14 @@ +pragma solidity ^0.6.0; + +import "../token/ERC20/ERC20Permit.sol"; + +contract ERC20PermitMock is ERC20Permit { + constructor ( + string memory name, + string memory symbol, + address initialAccount, + uint256 initialBalance + ) public payable ERC20(name, symbol) { + _mint(initialAccount, initialBalance); + } +} diff --git a/test/token/ERC20/ERC20Permit.test.js b/test/token/ERC20/ERC20Permit.test.js new file mode 100644 index 00000000000..4dea9f80218 --- /dev/null +++ b/test/token/ERC20/ERC20Permit.test.js @@ -0,0 +1,55 @@ +const { accounts, contract, web3 } = require('@openzeppelin/test-environment'); + +const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { MAX_UINT256, ZERO_ADDRESS } = constants; + +const { keccakFromString, bufferToHex } = require('ethereumjs-util'); + +const ERC20PermitMock = contract.fromArtifact('ERC20PermitMock'); + +describe('ERC20Permit', function () { + const [ initialHolder, spender, recipient, other ] = accounts; + + const name = 'My Token'; + const symbol = 'MTKN'; + + const initialSupply = new BN(100); + + beforeEach(async function () { + this.token = await ERC20PermitMock.new(name, symbol, initialHolder, initialSupply); + }); + + it('initial nonce is 0', async function () { + expect(await this.token.nonces(spender)).to.be.bignumber.equal('0'); + }); + + it('permits', async function () { + const amount = new BN(42); + + console.log(EIP712DomainSeparator(await this.token.name(), '1', await web3.eth.net.getId(), this.token.address)); + + const receipt = await this.token.permit(initialHolder, spender, amount, MAX_UINT256, 0, '0x00000000000000000000000000000000', '0x00000000000000000000000000000000'); + }); +}); + +function EIP712DomainSeparator(name, version, chainID, address) { + return bufferToHex(keccakFromString( + bufferToHex(keccakFromString('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')) + + bufferToHex(keccakFromString(name)) + + bufferToHex(keccakFromString(version)) + + chainID.toString() + + address + )); +} + +function ERC2612StructHash(owner, spender, value, nonce, deadline) { + return bufferToHex(keccakFromString( + bufferToHex(keccakFromString('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)')) + + owner + + spender + + value + + nonce + + deadline + )); +} From cf718ee150800d7d69abba08359498d8a54d4e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 26 May 2020 19:39:13 -0300 Subject: [PATCH 04/33] Fix encoding before hash --- contracts/token/ERC20/ERC20Permit.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol index aac60314f16..b5bc0af97d5 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -40,7 +40,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { uint16(0x1901), _domainSeparator(), keccak256( - abi.encodePacked( + abi.encode( _PERMIT_TYPEHASH, owner, spender, @@ -70,7 +70,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { uint256 chainID = _chainID(); bytes32 newDomainSeparator = keccak256( - abi.encodePacked( + abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name())), keccak256(bytes("1")), // Version From 53516bc555a454862470e7860a9b5254db4d00f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 5 Jun 2020 15:50:20 -0300 Subject: [PATCH 05/33] Implement nonce using Counter --- contracts/token/ERC20/ERC20Permit.sol | 31 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol index b5bc0af97d5..2b448da141a 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -3,6 +3,7 @@ pragma solidity ^0.6.0; import "./ERC20.sol"; import "./IERC2612Permit.sol"; import "../../cryptography/ECDSA.sol"; +import "../../utils/Counters.sol"; /** * @dev Extension of {ERC20} that allows token holders to use their tokens @@ -13,7 +14,9 @@ import "../../cryptography/ECDSA.sol"; * The {permit} signature mechanism conforms to the {IERC2612Permit} interface. */ abstract contract ERC20Permit is ERC20, IERC2612Permit { - mapping (address => uint256) private _nonces; + using Counters for Counters.Counter; + + mapping (address => Counters.Counter) private _nonces; bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); @@ -35,27 +38,29 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + bytes32 hashStruct = keccak256( + abi.encode( + _PERMIT_TYPEHASH, + owner, + spender, + amount, + _nonces[owner].current(), + deadline + ) + ); + bytes32 hash = keccak256( abi.encodePacked( uint16(0x1901), _domainSeparator(), - keccak256( - abi.encode( - _PERMIT_TYPEHASH, - owner, - spender, - amount, - _nonces[owner], - deadline - ) - ) + hashStruct ) ); address signer = ECDSA.recover(hash, v, r, s); require(signer == owner, "ERC20Permit: invalid signature"); - _nonces[owner] += 1; + _nonces[owner].increment(); _approve(owner, spender, amount); } @@ -63,7 +68,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { * @dev See {IERC2612Permit-nonces}. */ function nonces(address owner) public view override returns (uint256) { - return _nonces[owner]; + return _nonces[owner].current(); } function _updateDomainSeparator() private returns (bytes32) { From 743fe9a898c8cbd26ff93f45b69ddb563d47af8e Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 20 Nov 2020 19:37:12 -0300 Subject: [PATCH 06/33] adjust pragma and add license --- contracts/mocks/ERC20PermitMock.sol | 4 +++- contracts/token/ERC20/ERC20Permit.sol | 4 +++- contracts/token/ERC20/IERC2612Permit.sol | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/mocks/ERC20PermitMock.sol b/contracts/mocks/ERC20PermitMock.sol index 0051adead74..4edff3ed539 100644 --- a/contracts/mocks/ERC20PermitMock.sol +++ b/contracts/mocks/ERC20PermitMock.sol @@ -1,4 +1,6 @@ -pragma solidity ^0.6.0; +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; import "../token/ERC20/ERC20Permit.sol"; diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol index 2b448da141a..cff8eee78f5 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -1,4 +1,6 @@ -pragma solidity ^0.6.0; +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.5 <0.8.0; import "./ERC20.sol"; import "./IERC2612Permit.sol"; diff --git a/contracts/token/ERC20/IERC2612Permit.sol b/contracts/token/ERC20/IERC2612Permit.sol index f5a368ae104..9a98a76b3c5 100644 --- a/contracts/token/ERC20/IERC2612Permit.sol +++ b/contracts/token/ERC20/IERC2612Permit.sol @@ -1,4 +1,6 @@ -pragma solidity ^0.6.0; +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; /** * @dev Interface of the ERC2612 standard as defined in the EIP. From 0c02fdc8823e33a0ca463ed9cbece181d136ecae Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 20 Nov 2020 19:37:25 -0300 Subject: [PATCH 07/33] adapt test to buidler --- test/token/ERC20/ERC20Permit.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/token/ERC20/ERC20Permit.test.js b/test/token/ERC20/ERC20Permit.test.js index 4dea9f80218..ab0ee553ff0 100644 --- a/test/token/ERC20/ERC20Permit.test.js +++ b/test/token/ERC20/ERC20Permit.test.js @@ -1,14 +1,12 @@ -const { accounts, contract, web3 } = require('@openzeppelin/test-environment'); - const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { MAX_UINT256, ZERO_ADDRESS } = constants; const { keccakFromString, bufferToHex } = require('ethereumjs-util'); -const ERC20PermitMock = contract.fromArtifact('ERC20PermitMock'); +const ERC20PermitMock = artifacts.require('ERC20PermitMock'); -describe('ERC20Permit', function () { +contract('ERC20Permit', function (accounts) { const [ initialHolder, spender, recipient, other ] = accounts; const name = 'My Token'; From 255e11a1eca4e80f8ef8ec7005f9dff6c1e173bf Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 20 Nov 2020 19:38:03 -0300 Subject: [PATCH 08/33] disable eslint in wip file --- test/token/ERC20/ERC20Permit.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/token/ERC20/ERC20Permit.test.js b/test/token/ERC20/ERC20Permit.test.js index ab0ee553ff0..79f2c10cdeb 100644 --- a/test/token/ERC20/ERC20Permit.test.js +++ b/test/token/ERC20/ERC20Permit.test.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { MAX_UINT256, ZERO_ADDRESS } = constants; From d5037d2b2f3df0bdce0d56409f7ed97ebcd70dfd Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 20 Nov 2020 19:49:05 -0300 Subject: [PATCH 09/33] add solhint exceptions --- contracts/token/ERC20/ERC20Permit.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol index cff8eee78f5..0fbc0317cd0 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -20,6 +20,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { mapping (address => Counters.Counter) private _nonces; + // solhint-disable-next-line var-name-mixedcase bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); // Mapping of ChainID to domain separators. This is a very gas efficient way @@ -38,6 +39,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { * EIP712 Domain Separator is automatically recalculated. */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { + // solhint-disable-next-line not-rely-on-time require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); bytes32 hashStruct = keccak256( @@ -103,6 +105,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { function _chainID() private pure returns (uint256) { uint256 chainID; + // solhint-disable-next-line no-inline-assembly assembly { chainID := chainid() } From b11d7ab69e508003d1b054e9de2b80299fd6700e Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 27 Nov 2020 20:04:34 -0300 Subject: [PATCH 10/33] use a cheaper strategy for domain separator caching --- contracts/token/ERC20/ERC20Permit.sol | 52 ++++++++++++--------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol index 0fbc0317cd0..e346ea2c7c5 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -20,16 +20,19 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { mapping (address => Counters.Counter) private _nonces; - // solhint-disable-next-line var-name-mixedcase + /* solhint-disable var-name-mixedcase */ bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - // Mapping of ChainID to domain separators. This is a very gas efficient way - // to not recalculate the domain separator on every call, while still - // automatically detecting ChainID changes. - mapping (uint256 => bytes32) private _domainSeparators; + // 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. + bytes32 private immutable _DOMAIN_SEPARATOR; + uint256 private immutable _CHAIN_ID; + /* solhint-enable var-name-mixedcase */ constructor() internal { - _updateDomainSeparator(); + uint256 chainId = _getChainId(); + _CHAIN_ID = chainId; + _DOMAIN_SEPARATOR = _buildDomainSeparator(chainId); } /** @@ -56,7 +59,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { bytes32 hash = keccak256( abi.encodePacked( uint16(0x1901), - _domainSeparator(), + _getDomainSeparator(), hashStruct ) ); @@ -75,40 +78,31 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { return _nonces[owner].current(); } - function _updateDomainSeparator() private returns (bytes32) { - uint256 chainID = _chainID(); - bytes32 newDomainSeparator = keccak256( + function _getDomainSeparator() private view returns (bytes32) { + if (_getChainId() == _CHAIN_ID) { + return _DOMAIN_SEPARATOR; + } else { + return _buildDomainSeparator(_getChainId()); + } + } + + function _buildDomainSeparator(uint256 chainId) private view returns (bytes32) { + return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name())), keccak256(bytes("1")), // Version - chainID, + chainId, address(this) ) ); - - _domainSeparators[chainID] = newDomainSeparator; - - return newDomainSeparator; - } - - // Returns the domain separator, updating it if chainID changes - function _domainSeparator() private returns (bytes32) { - bytes32 domainSeparator = _domainSeparators[_chainID()]; - if (domainSeparator != 0x00) { - return domainSeparator; - } else { - return _updateDomainSeparator(); - } } - function _chainID() private pure returns (uint256) { - uint256 chainID; + function _getChainId() private pure returns (uint256 chainId) { // solhint-disable-next-line no-inline-assembly assembly { - chainID := chainid() + chainId := chainid() } - return chainID; } } From 58c7098add7bff4c8760eb28e32e98f22f17d601 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 1 Dec 2020 18:22:19 -0300 Subject: [PATCH 11/33] add DOMAIN_SEPARATOR function --- contracts/token/ERC20/ERC20Permit.sol | 6 ++++++ contracts/token/ERC20/IERC2612Permit.sol | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/token/ERC20/ERC20Permit.sol index e346ea2c7c5..35d8aa643b9 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/token/ERC20/ERC20Permit.sol @@ -78,6 +78,12 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { return _nonces[owner].current(); } + /** + * @dev See {IERC2612Permit-DOMAIN_SEPARATOR}. + */ + function DOMAIN_SEPARATOR() external view override returns (bytes32) { + return _getDomainSeparator(); + } function _getDomainSeparator() private view returns (bytes32) { if (_getChainId() == _CHAIN_ID) { diff --git a/contracts/token/ERC20/IERC2612Permit.sol b/contracts/token/ERC20/IERC2612Permit.sol index 9a98a76b3c5..e61f84dd5b8 100644 --- a/contracts/token/ERC20/IERC2612Permit.sol +++ b/contracts/token/ERC20/IERC2612Permit.sol @@ -44,4 +44,9 @@ interface IERC2612Permit { * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}. + */ + function DOMAIN_SEPARATOR() external view returns (bytes32); } From cb00f8add3c547e7b100ab0a5089b74801068abd Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 1 Dec 2020 18:35:10 -0300 Subject: [PATCH 12/33] add eip712 from #2418 --- contracts/cryptography/README.adoc | 2 + contracts/drafts/EIP712.sol | 105 ++++++++++++++++++ contracts/drafts/README.adoc | 9 ++ contracts/mocks/EIP712External.sol | 31 ++++++ package-lock.json | 167 ++++++++++++++++++++++++----- package.json | 2 + test/drafts/EIP712.test.js | 70 ++++++++++++ 7 files changed, 362 insertions(+), 24 deletions(-) create mode 100644 contracts/drafts/EIP712.sol create mode 100644 contracts/drafts/README.adoc create mode 100644 contracts/mocks/EIP712External.sol create mode 100644 test/drafts/EIP712.test.js diff --git a/contracts/cryptography/README.adoc b/contracts/cryptography/README.adoc index 996e9dadb1f..7f9df74d73d 100644 --- a/contracts/cryptography/README.adoc +++ b/contracts/cryptography/README.adoc @@ -10,3 +10,5 @@ This collection of libraries provides simple and safe ways to use different cryp {{ECDSA}} {{MerkleProof}} + +{{EIP712}} diff --git a/contracts/drafts/EIP712.sol b/contracts/drafts/EIP712.sol new file mode 100644 index 00000000000..8e528767c50 --- /dev/null +++ b/contracts/drafts/EIP712.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. + * + * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, + * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding + * they need in their contracts using a combination of `abi.encode` and `keccak256`. + * + * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding + * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA + * ({_hashTypedDataV4}). + * + * The implementation of the domain separator was designed to be as efficient as possible while still properly updating + * the chain id to protect against replay attacks on an eventual fork of the chain. + * + * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method + * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. + */ +abstract contract EIP712 { + /* 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. + bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; + uint256 private immutable _CACHED_CHAIN_ID; + + bytes32 private immutable _HASHED_NAME; + bytes32 private immutable _HASHED_VERSION; + bytes32 private immutable _TYPE_HASH; + /* solhint-enable var-name-mixedcase */ + + /** + * @dev Initializes the domain separator and parameter caches. + * + * The meaning of `name` and `version` is specified in + * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: + * + * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. + * - `version`: the current major version of the signing domain. + * + * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart + * contract upgrade]. + */ + constructor(string memory name, string memory version) internal { + 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; + _CACHED_CHAIN_ID = _getChainId(); + _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion); + _TYPE_HASH = typeHash; + } + + /** + * @dev Returns the domain separator for the current chain. + */ + function _domainSeparatorV4() internal view returns (bytes32) { + if (_getChainId() == _CACHED_CHAIN_ID) { + return _CACHED_DOMAIN_SEPARATOR; + } else { + return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); + } + } + + function _buildDomainSeparator(bytes32 typeHash, bytes32 name, bytes32 version) private view returns (bytes32) { + return keccak256( + abi.encode( + typeHash, + name, + version, + _getChainId(), + address(this) + ) + ); + } + + /** + * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this + * function returns the hash of the fully encoded EIP712 message for this domain. + * + * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: + * + * ```solidity + * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + * keccak256("Mail(address to,string contents)"), + * mailTo, + * keccak256(bytes(mailContents)) + * ))); + * address signer = ECDSA.recover(digest, signature); + * ``` + */ + function _hashTypedDataV4(bytes32 structHash) internal view returns (bytes32) { + return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash)); + } + + function _getChainId() private pure returns (uint256 chainId) { + // solhint-disable-next-line no-inline-assembly + assembly { + chainId := chainid() + } + } +} diff --git a/contracts/drafts/README.adoc b/contracts/drafts/README.adoc new file mode 100644 index 00000000000..42fdc69609f --- /dev/null +++ b/contracts/drafts/README.adoc @@ -0,0 +1,9 @@ += Draft EIPS + +This directory contains implementations of EIPs that are still in Draft status. + +Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their xref:ROOT:releases-stability.adoc[stability]. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts in this directory, which will be duly announced in the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md[changelog]. The EIPs included here are used by projects in production and this may make them less likely to change significantly. + +== Cryptography + +{{EIP712}} diff --git a/contracts/mocks/EIP712External.sol b/contracts/mocks/EIP712External.sol new file mode 100644 index 00000000000..81e1f5952e2 --- /dev/null +++ b/contracts/mocks/EIP712External.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +import "../drafts/EIP712.sol"; +import "../cryptography/ECDSA.sol"; + +contract EIP712External is EIP712 { + constructor(string memory name, string memory version) public EIP712(name, version) {} + + function domainSeparator() external view returns (bytes32) { + return _domainSeparatorV4(); + } + + function verify(bytes memory signature, address signer, address mailTo, string memory mailContents) external view { + bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + keccak256("Mail(address to,string contents)"), + mailTo, + keccak256(bytes(mailContents)) + ))); + address recoveredSigner = ECDSA.recover(digest, signature); + require(recoveredSigner == signer); + } + + function getChainId() external pure returns (uint256 chainId) { + // solhint-disable-next-line no-inline-assembly + assembly { + chainId := chainid() + } + } +} diff --git a/package-lock.json b/package-lock.json index ecbafce0f37..cd0591f9e8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -555,6 +555,62 @@ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, + "eth-sig-util": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.2.tgz", + "integrity": "sha512-xvDojS/4reXsw8Pz/+p/qcM5rVB61FOdPbEtMZ8FQ0YHnPEzPy5F8zAAaZ+zj5ud0SwRLWPfor2Cacjm7EzMIw==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "elliptic": "^6.4.0", + "ethereumjs-abi": "0.6.5", + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.0", + "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "ethereumjs-abi": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz", + "integrity": "sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE=", + "dev": true, + "requires": { + "bn.js": "^4.10.0", + "ethereumjs-util": "^4.3.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz", + "integrity": "sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==", + "dev": true, + "requires": { + "bn.js": "^4.8.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.0.0" + } + } + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, "ethereumjs-util": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", @@ -1458,12 +1514,74 @@ "web3-utils": "^1.2.1" }, "dependencies": { + "aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", + "dev": true + }, "bignumber.js": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", "dev": true }, + "eth-sig-util": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.2.tgz", + "integrity": "sha512-xvDojS/4reXsw8Pz/+p/qcM5rVB61FOdPbEtMZ8FQ0YHnPEzPy5F8zAAaZ+zj5ud0SwRLWPfor2Cacjm7EzMIw==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "elliptic": "^6.4.0", + "ethereumjs-abi": "0.6.5", + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.0", + "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-abi": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz", + "integrity": "sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE=", + "dev": true, + "requires": { + "bn.js": "^4.10.0", + "ethereumjs-util": "^4.3.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz", + "integrity": "sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==", + "dev": true, + "requires": { + "bn.js": "^4.8.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.0.0" + } + } + } + }, "ethereumjs-tx": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", @@ -1505,6 +1623,23 @@ "ethjs-util": "0.1.6", "rlp": "^2.2.3" } + }, + "ethereumjs-wallet": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-0.6.5.tgz", + "integrity": "sha512-MDwjwB9VQVnpp/Dc1XzA6J1a3wgHQ4hSvA1uWNatdpOrtCbPVuQSKSyRnjLvS0a+KKMw2pvQ9Ybqpb3+eW8oNA==", + "dev": true, + "requires": { + "aes-js": "^3.1.1", + "bs58check": "^2.1.2", + "ethereum-cryptography": "^0.1.3", + "ethereumjs-util": "^6.0.0", + "randombytes": "^2.0.6", + "safe-buffer": "^5.1.2", + "scryptsy": "^1.2.1", + "utf8": "^3.0.0", + "uuid": "^3.3.2" + } } } }, @@ -5440,9 +5575,9 @@ } }, "eth-sig-util": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.2.tgz", - "integrity": "sha512-xvDojS/4reXsw8Pz/+p/qcM5rVB61FOdPbEtMZ8FQ0YHnPEzPy5F8zAAaZ+zj5ud0SwRLWPfor2Cacjm7EzMIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-3.0.0.tgz", + "integrity": "sha512-4eFkMOhpGbTxBQ3AMzVf0haUX2uTur7DpWiHzWyTURa28BVJJtOkcb9Ok5TV0YvEPG61DODPW7ZUATbJTslioQ==", "dev": true, "requires": { "buffer": "^5.2.1", @@ -5824,18 +5959,17 @@ } }, "ethereumjs-wallet": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-0.6.5.tgz", - "integrity": "sha512-MDwjwB9VQVnpp/Dc1XzA6J1a3wgHQ4hSvA1uWNatdpOrtCbPVuQSKSyRnjLvS0a+KKMw2pvQ9Ybqpb3+eW8oNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-1.0.1.tgz", + "integrity": "sha512-3Z5g1hG1das0JWU6cQ9HWWTY2nt9nXCcwj7eXVNAHKbo00XAZO8+NHlwdgXDWrL0SXVQMvTWN8Q/82DRH/JhPw==", "dev": true, "requires": { "aes-js": "^3.1.1", "bs58check": "^2.1.2", "ethereum-cryptography": "^0.1.3", - "ethereumjs-util": "^6.0.0", + "ethereumjs-util": "^7.0.2", "randombytes": "^2.0.6", - "safe-buffer": "^5.1.2", - "scryptsy": "^1.2.1", + "scrypt-js": "^3.0.1", "utf8": "^3.0.0", "uuid": "^3.3.2" }, @@ -5845,21 +5979,6 @@ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", "dev": true - }, - "ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } } } }, diff --git a/package.json b/package.json index fb4befe9b1e..69ccb86db43 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,9 @@ "eslint-plugin-node": "^10.0.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", + "eth-sig-util": "^3.0.0", "ethereumjs-util": "^7.0.7", + "ethereumjs-wallet": "^1.0.1", "lodash.startcase": "^4.4.0", "lodash.zip": "^4.2.0", "micromatch": "^4.0.2", diff --git a/test/drafts/EIP712.test.js b/test/drafts/EIP712.test.js new file mode 100644 index 00000000000..1c9a696a55d --- /dev/null +++ b/test/drafts/EIP712.test.js @@ -0,0 +1,70 @@ +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; + +const EIP712 = artifacts.require('EIP712External'); + +const EIP712Domain = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, +]; + +async function domainSeparator (name, version, chainId, verifyingContract) { + return '0x' + ethSigUtil.TypedDataUtils.hashStruct( + 'EIP712Domain', + { name, version, chainId, verifyingContract }, + { EIP712Domain }, + ).toString('hex'); +} + +contract('EIP712', function (accounts) { + const [mailTo] = accounts; + + const name = 'A Name'; + const version = '1'; + + beforeEach('deploying', async function () { + this.eip712 = await EIP712.new(name, version); + + // We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id + // from within the EVM as from the JSON RPC interface. + // See https://github.com/trufflesuite/ganache-core/issues/515 + this.chainId = await this.eip712.getChainId(); + }); + + it('domain separator', async function () { + expect( + await this.eip712.domainSeparator(), + ).to.equal( + await domainSeparator(name, version, this.chainId, this.eip712.address), + ); + }); + + it('digest', async function () { + const chainId = this.chainId; + const verifyingContract = this.eip712.address; + const message = { + to: mailTo, + contents: 'very interesting', + }; + + const data = { + types: { + EIP712Domain, + Mail: [ + { name: 'to', type: 'address' }, + { name: 'contents', type: 'string' }, + ], + }, + domain: { name, version, chainId, verifyingContract }, + primaryType: 'Mail', + message, + }; + + const wallet = Wallet.generate(); + const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); + + await this.eip712.verify(signature, wallet.getAddressString(), message.to, message.contents); + }); +}); From c5d01e9bb57a485a2fd8d31e1b14798f7f1fdfa8 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 1 Dec 2020 18:40:52 -0300 Subject: [PATCH 13/33] move to drafts directory --- contracts/{token/ERC20 => drafts}/ERC20Permit.sol | 6 +++--- contracts/{token/ERC20 => drafts}/IERC2612Permit.sol | 0 contracts/mocks/ERC20PermitMock.sol | 2 +- test/{token/ERC20 => drafts}/ERC20Permit.test.js | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename contracts/{token/ERC20 => drafts}/ERC20Permit.sol (97%) rename contracts/{token/ERC20 => drafts}/IERC2612Permit.sol (100%) rename test/{token/ERC20 => drafts}/ERC20Permit.test.js (100%) diff --git a/contracts/token/ERC20/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol similarity index 97% rename from contracts/token/ERC20/ERC20Permit.sol rename to contracts/drafts/ERC20Permit.sol index 35d8aa643b9..da80c20ff28 100644 --- a/contracts/token/ERC20/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -2,10 +2,10 @@ pragma solidity >=0.6.5 <0.8.0; -import "./ERC20.sol"; +import "../token/ERC20/ERC20.sol"; import "./IERC2612Permit.sol"; -import "../../cryptography/ECDSA.sol"; -import "../../utils/Counters.sol"; +import "../cryptography/ECDSA.sol"; +import "../utils/Counters.sol"; /** * @dev Extension of {ERC20} that allows token holders to use their tokens diff --git a/contracts/token/ERC20/IERC2612Permit.sol b/contracts/drafts/IERC2612Permit.sol similarity index 100% rename from contracts/token/ERC20/IERC2612Permit.sol rename to contracts/drafts/IERC2612Permit.sol diff --git a/contracts/mocks/ERC20PermitMock.sol b/contracts/mocks/ERC20PermitMock.sol index 4edff3ed539..0aa5944c7f7 100644 --- a/contracts/mocks/ERC20PermitMock.sol +++ b/contracts/mocks/ERC20PermitMock.sol @@ -2,7 +2,7 @@ pragma solidity >=0.6.0 <0.8.0; -import "../token/ERC20/ERC20Permit.sol"; +import "../drafts/ERC20Permit.sol"; contract ERC20PermitMock is ERC20Permit { constructor ( diff --git a/test/token/ERC20/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js similarity index 100% rename from test/token/ERC20/ERC20Permit.test.js rename to test/drafts/ERC20Permit.test.js From aacea0c9d320739b7cbb7354c5dc555f203ba814 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 1 Dec 2020 18:43:06 -0300 Subject: [PATCH 14/33] use EIP712 contract --- contracts/drafts/ERC20Permit.sol | 55 ++++---------------------------- 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/contracts/drafts/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol index da80c20ff28..3745a7d8f60 100644 --- a/contracts/drafts/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -6,6 +6,7 @@ import "../token/ERC20/ERC20.sol"; import "./IERC2612Permit.sol"; import "../cryptography/ECDSA.sol"; import "../utils/Counters.sol"; +import "./EIP712.sol"; /** * @dev Extension of {ERC20} that allows token holders to use their tokens @@ -15,24 +16,15 @@ import "../utils/Counters.sol"; * * The {permit} signature mechanism conforms to the {IERC2612Permit} interface. */ -abstract contract ERC20Permit is ERC20, IERC2612Permit { +abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { using Counters for Counters.Counter; mapping (address => Counters.Counter) private _nonces; - /* solhint-disable var-name-mixedcase */ + // solhint-disable-next-line var-name-mixedcase bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - // 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. - bytes32 private immutable _DOMAIN_SEPARATOR; - uint256 private immutable _CHAIN_ID; - /* solhint-enable var-name-mixedcase */ - - constructor() internal { - uint256 chainId = _getChainId(); - _CHAIN_ID = chainId; - _DOMAIN_SEPARATOR = _buildDomainSeparator(chainId); + constructor() internal EIP712(name(), "1") { } /** @@ -45,7 +37,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { // solhint-disable-next-line not-rely-on-time require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); - bytes32 hashStruct = keccak256( + bytes32 structHash = keccak256( abi.encode( _PERMIT_TYPEHASH, owner, @@ -56,13 +48,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { ) ); - bytes32 hash = keccak256( - abi.encodePacked( - uint16(0x1901), - _getDomainSeparator(), - hashStruct - ) - ); + bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, v, r, s); require(signer == owner, "ERC20Permit: invalid signature"); @@ -82,33 +68,6 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit { * @dev See {IERC2612Permit-DOMAIN_SEPARATOR}. */ function DOMAIN_SEPARATOR() external view override returns (bytes32) { - return _getDomainSeparator(); - } - - function _getDomainSeparator() private view returns (bytes32) { - if (_getChainId() == _CHAIN_ID) { - return _DOMAIN_SEPARATOR; - } else { - return _buildDomainSeparator(_getChainId()); - } - } - - function _buildDomainSeparator(uint256 chainId) private view returns (bytes32) { - return keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(name())), - keccak256(bytes("1")), // Version - chainId, - address(this) - ) - ); - } - - function _getChainId() private pure returns (uint256 chainId) { - // solhint-disable-next-line no-inline-assembly - assembly { - chainId := chainid() - } + return _domainSeparatorV4(); } } From b57f655d7f6bfc9b85bc769ae531003fd20a1617 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 2 Dec 2020 16:49:50 -0300 Subject: [PATCH 15/33] fix test --- contracts/drafts/ERC20Permit.sol | 2 +- contracts/mocks/ERC20PermitMock.sol | 9 +- package-lock.json | 1460 ++++++++++++++++++++++++--- package.json | 3 +- test/drafts/ERC20Permit.test.js | 69 +- 5 files changed, 1362 insertions(+), 181 deletions(-) diff --git a/contracts/drafts/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol index 3745a7d8f60..351412e8093 100644 --- a/contracts/drafts/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -24,7 +24,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { // solhint-disable-next-line var-name-mixedcase bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - constructor() internal EIP712(name(), "1") { + constructor(string memory name) internal EIP712(name, "1") { } /** diff --git a/contracts/mocks/ERC20PermitMock.sol b/contracts/mocks/ERC20PermitMock.sol index 0aa5944c7f7..1477338b38d 100644 --- a/contracts/mocks/ERC20PermitMock.sol +++ b/contracts/mocks/ERC20PermitMock.sol @@ -10,7 +10,14 @@ contract ERC20PermitMock is ERC20Permit { string memory symbol, address initialAccount, uint256 initialBalance - ) public payable ERC20(name, symbol) { + ) public payable ERC20(name, symbol) ERC20Permit(name) { _mint(initialAccount, initialBalance); } + + function getChainId() external pure returns (uint256 chainId) { + // solhint-disable-next-line no-inline-assembly + assembly { + chainId := chainid() + } + } } diff --git a/package-lock.json b/package-lock.json index cd0591f9e8c..093a75d0f8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2520,6 +2520,407 @@ "ethers": "^4.0.32", "source-map-support": "^0.5.19", "web3": "1.2.9" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@types/node": { + "version": "10.17.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.48.tgz", + "integrity": "sha512-Agl6xbYP6FOMDeAsr3QVZ+g7Yzg0uhPHWx0j5g4LFdUBHVtqtU+gH660k/lCEe506jJLOGbEzsnqPDTZGJQLag==", + "dev": true + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "dev": true + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "web3": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.9.tgz", + "integrity": "sha512-Mo5aBRm0JrcNpN/g4VOrDzudymfOnHRC3s2VarhYxRA8aWgF5rnhQ0ziySaugpic1gksbXPe105pUWyRqw8HUA==", + "dev": true, + "requires": { + "web3-bzz": "1.2.9", + "web3-core": "1.2.9", + "web3-eth": "1.2.9", + "web3-eth-personal": "1.2.9", + "web3-net": "1.2.9", + "web3-shh": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-bzz": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.9.tgz", + "integrity": "sha512-ogVQr9jHodu9HobARtvUSmWG22cv2EUQzlPeejGWZ7j5h20HX40EDuWyomGY5VclIj5DdLY76Tmq88RTf/6nxA==", + "dev": true, + "requires": { + "@types/node": "^10.12.18", + "got": "9.6.0", + "swarm-js": "^0.1.40", + "underscore": "1.9.1" + } + }, + "web3-core": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.9.tgz", + "integrity": "sha512-fSYv21IP658Ty2wAuU9iqmW7V+75DOYMVZsDH/c14jcF/1VXnedOcxzxSj3vArsCvXZNe6XC5/wAuGZyQwR9RA==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.4", + "@types/node": "^12.6.1", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-requestmanager": "1.2.9", + "web3-utils": "1.2.9" + }, + "dependencies": { + "@types/node": { + "version": "12.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", + "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", + "dev": true + } + } + }, + "web3-core-helpers": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.9.tgz", + "integrity": "sha512-t0WAG3orLCE3lqi77ZoSRNFok3VQWZXTniZigDQjyOJYMAX7BU3F3js8HKbjVnAxlX3tiKoDxI0KBk9F3AxYuw==", + "dev": true, + "requires": { + "underscore": "1.9.1", + "web3-eth-iban": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-core-method": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.9.tgz", + "integrity": "sha512-bjsIoqP3gs7A/gP8+QeLUCyOKJ8bopteCSNbCX36Pxk6TYfYWNuC6hP+2GzUuqdP3xaZNe+XEElQFUNpR3oyAg==", + "dev": true, + "requires": { + "@ethersproject/transactions": "^5.0.0-beta.135", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9", + "web3-core-promievent": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-core-promievent": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.9.tgz", + "integrity": "sha512-0eAUA2zjgXTleSrnc1wdoKQPPIHU6KHf4fAscu4W9kKrR+mqP1KsjYrxY9wUyjNnXxfQ+5M29ipvbiaK8OqdOw==", + "dev": true, + "requires": { + "eventemitter3": "3.1.2" + } + }, + "web3-core-requestmanager": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.9.tgz", + "integrity": "sha512-1PwKV2m46ALUnIN5VPPgjOj8yMLJhhqZYvYJE34hTN5SErOkwhzx5zScvo5MN7v7KyQGFnpVCZKKGCiEnDmtFA==", + "dev": true, + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9", + "web3-providers-http": "1.2.9", + "web3-providers-ipc": "1.2.9", + "web3-providers-ws": "1.2.9" + } + }, + "web3-core-subscriptions": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.9.tgz", + "integrity": "sha512-Y48TvXPSPxEM33OmXjGVDMzTd0j8X0t2+sDw66haeBS8eYnrEzasWuBZZXDq0zNUsqyxItgBGDn+cszkgEnFqg==", + "dev": true, + "requires": { + "eventemitter3": "3.1.2", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9" + } + }, + "web3-eth": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.9.tgz", + "integrity": "sha512-sIKO4iE9FEBa/CYUd6GdPd7GXt/wISqxUd8PlIld6+hvMJj02lgO7Z7p5T9mZIJcIZJGvZX81ogx8oJ9yif+Ag==", + "dev": true, + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-eth-abi": "1.2.9", + "web3-eth-accounts": "1.2.9", + "web3-eth-contract": "1.2.9", + "web3-eth-ens": "1.2.9", + "web3-eth-iban": "1.2.9", + "web3-eth-personal": "1.2.9", + "web3-net": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-accounts": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.9.tgz", + "integrity": "sha512-jkbDCZoA1qv53mFcRHCinoCsgg8WH+M0YUO1awxmqWXRmCRws1wW0TsuSQ14UThih5Dxolgl+e+aGWxG58LMwg==", + "dev": true, + "requires": { + "crypto-browserify": "3.12.0", + "eth-lib": "^0.2.8", + "ethereumjs-common": "^1.3.2", + "ethereumjs-tx": "^2.1.1", + "scrypt-js": "^3.0.1", + "underscore": "1.9.1", + "uuid": "3.3.2", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-contract": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.9.tgz", + "integrity": "sha512-PYMvJf7EG/HyssUZa+pXrc8IB06K/YFfWYyW4R7ed3sab+9wWUys1TlWxBCBuiBXOokSAyM6H6P6/cKEx8FT8Q==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.4", + "underscore": "1.9.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-promievent": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-eth-abi": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-ens": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.9.tgz", + "integrity": "sha512-kG4+ZRgZ8I1WYyOBGI8QVRHfUSbbJjvJAGA1AF/NOW7JXQ+x7gBGeJw6taDWJhSshMoEKWcsgvsiuoG4870YxQ==", + "dev": true, + "requires": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "underscore": "1.9.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-promievent": "1.2.9", + "web3-eth-abi": "1.2.9", + "web3-eth-contract": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-iban": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.9.tgz", + "integrity": "sha512-RtdVvJE0pyg9dHLy0GzDiqgnLnssSzfz/JYguhC1wsj9+Gnq1M6Diy3NixACWUAp6ty/zafyOaZnNQ+JuH9TjQ==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "web3-utils": "1.2.9" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + } + } + }, + "web3-eth-personal": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.9.tgz", + "integrity": "sha512-cFiNrktxZ1C/rIdJFzQTvFn3/0zcsR3a+Jf8Y3KxeQDHszQtosjLWptP7bsUmDwEh4hzh0Cy3KpOxlYBWB8bJQ==", + "dev": true, + "requires": { + "@types/node": "^12.6.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-net": "1.2.9", + "web3-utils": "1.2.9" + }, + "dependencies": { + "@types/node": { + "version": "12.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", + "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", + "dev": true + } + } + }, + "web3-net": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.9.tgz", + "integrity": "sha512-d2mTn8jPlg+SI2hTj2b32Qan6DmtU9ap/IUlJTeQbZQSkTLf0u9suW8Vjwyr4poJYXTurdSshE7OZsPNn30/ZA==", + "dev": true, + "requires": { + "web3-core": "1.2.9", + "web3-core-method": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-providers-http": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.9.tgz", + "integrity": "sha512-F956tCIj60Ttr0UvEHWFIhx+be3He8msoPzyA44/kfzzYoMAsCFRn5cf0zQG6al0znE75g6HlWVSN6s3yAh51A==", + "dev": true, + "requires": { + "web3-core-helpers": "1.2.9", + "xhr2-cookies": "1.1.0" + } + }, + "web3-providers-ipc": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.9.tgz", + "integrity": "sha512-NQ8QnBleoHA2qTJlqoWu7EJAD/FR5uimf7Ielzk4Z2z+m+6UAuJdJMSuQNj+Umhz9L/Ys6vpS1vHx9NizFl+aQ==", + "dev": true, + "requires": { + "oboe": "2.1.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9" + } + }, + "web3-providers-ws": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.9.tgz", + "integrity": "sha512-6+UpvINeI//dglZoAKStUXqxDOXJy6Iitv2z3dbgInG4zb8tkYl/VBDL80UjUg3ZvzWG0g7EKY2nRPEpON2TFA==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9", + "websocket": "^1.0.31" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + } + } + }, + "web3-shh": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.9.tgz", + "integrity": "sha512-PWa8b/EaxaMinFaxy6cV0i0EOi2M7a/ST+9k9nhyhCjVa2vzXuNoBNo2IUOmeZ0WP2UQB8ByJ2+p4htlJaDOjA==", + "dev": true, + "requires": { + "web3-core": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-net": "1.2.9" + } + } } }, "@truffle/provider": { @@ -2531,6 +2932,405 @@ "@truffle/error": "^0.0.11", "@truffle/interface-adapter": "^0.4.18", "web3": "1.2.9" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@types/node": { + "version": "10.17.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.48.tgz", + "integrity": "sha512-Agl6xbYP6FOMDeAsr3QVZ+g7Yzg0uhPHWx0j5g4LFdUBHVtqtU+gH660k/lCEe506jJLOGbEzsnqPDTZGJQLag==", + "dev": true + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "web3": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.9.tgz", + "integrity": "sha512-Mo5aBRm0JrcNpN/g4VOrDzudymfOnHRC3s2VarhYxRA8aWgF5rnhQ0ziySaugpic1gksbXPe105pUWyRqw8HUA==", + "dev": true, + "requires": { + "web3-bzz": "1.2.9", + "web3-core": "1.2.9", + "web3-eth": "1.2.9", + "web3-eth-personal": "1.2.9", + "web3-net": "1.2.9", + "web3-shh": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-bzz": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.9.tgz", + "integrity": "sha512-ogVQr9jHodu9HobARtvUSmWG22cv2EUQzlPeejGWZ7j5h20HX40EDuWyomGY5VclIj5DdLY76Tmq88RTf/6nxA==", + "dev": true, + "requires": { + "@types/node": "^10.12.18", + "got": "9.6.0", + "swarm-js": "^0.1.40", + "underscore": "1.9.1" + } + }, + "web3-core": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.9.tgz", + "integrity": "sha512-fSYv21IP658Ty2wAuU9iqmW7V+75DOYMVZsDH/c14jcF/1VXnedOcxzxSj3vArsCvXZNe6XC5/wAuGZyQwR9RA==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.4", + "@types/node": "^12.6.1", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-requestmanager": "1.2.9", + "web3-utils": "1.2.9" + }, + "dependencies": { + "@types/node": { + "version": "12.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", + "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", + "dev": true + } + } + }, + "web3-core-helpers": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.9.tgz", + "integrity": "sha512-t0WAG3orLCE3lqi77ZoSRNFok3VQWZXTniZigDQjyOJYMAX7BU3F3js8HKbjVnAxlX3tiKoDxI0KBk9F3AxYuw==", + "dev": true, + "requires": { + "underscore": "1.9.1", + "web3-eth-iban": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-core-method": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.9.tgz", + "integrity": "sha512-bjsIoqP3gs7A/gP8+QeLUCyOKJ8bopteCSNbCX36Pxk6TYfYWNuC6hP+2GzUuqdP3xaZNe+XEElQFUNpR3oyAg==", + "dev": true, + "requires": { + "@ethersproject/transactions": "^5.0.0-beta.135", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9", + "web3-core-promievent": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-core-promievent": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.9.tgz", + "integrity": "sha512-0eAUA2zjgXTleSrnc1wdoKQPPIHU6KHf4fAscu4W9kKrR+mqP1KsjYrxY9wUyjNnXxfQ+5M29ipvbiaK8OqdOw==", + "dev": true, + "requires": { + "eventemitter3": "3.1.2" + } + }, + "web3-core-requestmanager": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.9.tgz", + "integrity": "sha512-1PwKV2m46ALUnIN5VPPgjOj8yMLJhhqZYvYJE34hTN5SErOkwhzx5zScvo5MN7v7KyQGFnpVCZKKGCiEnDmtFA==", + "dev": true, + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9", + "web3-providers-http": "1.2.9", + "web3-providers-ipc": "1.2.9", + "web3-providers-ws": "1.2.9" + } + }, + "web3-core-subscriptions": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.9.tgz", + "integrity": "sha512-Y48TvXPSPxEM33OmXjGVDMzTd0j8X0t2+sDw66haeBS8eYnrEzasWuBZZXDq0zNUsqyxItgBGDn+cszkgEnFqg==", + "dev": true, + "requires": { + "eventemitter3": "3.1.2", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9" + } + }, + "web3-eth": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.9.tgz", + "integrity": "sha512-sIKO4iE9FEBa/CYUd6GdPd7GXt/wISqxUd8PlIld6+hvMJj02lgO7Z7p5T9mZIJcIZJGvZX81ogx8oJ9yif+Ag==", + "dev": true, + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-eth-abi": "1.2.9", + "web3-eth-accounts": "1.2.9", + "web3-eth-contract": "1.2.9", + "web3-eth-ens": "1.2.9", + "web3-eth-iban": "1.2.9", + "web3-eth-personal": "1.2.9", + "web3-net": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-accounts": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.9.tgz", + "integrity": "sha512-jkbDCZoA1qv53mFcRHCinoCsgg8WH+M0YUO1awxmqWXRmCRws1wW0TsuSQ14UThih5Dxolgl+e+aGWxG58LMwg==", + "dev": true, + "requires": { + "crypto-browserify": "3.12.0", + "eth-lib": "^0.2.8", + "ethereumjs-common": "^1.3.2", + "ethereumjs-tx": "^2.1.1", + "scrypt-js": "^3.0.1", + "underscore": "1.9.1", + "uuid": "3.3.2", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-contract": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.9.tgz", + "integrity": "sha512-PYMvJf7EG/HyssUZa+pXrc8IB06K/YFfWYyW4R7ed3sab+9wWUys1TlWxBCBuiBXOokSAyM6H6P6/cKEx8FT8Q==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.4", + "underscore": "1.9.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-promievent": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-eth-abi": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-ens": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.9.tgz", + "integrity": "sha512-kG4+ZRgZ8I1WYyOBGI8QVRHfUSbbJjvJAGA1AF/NOW7JXQ+x7gBGeJw6taDWJhSshMoEKWcsgvsiuoG4870YxQ==", + "dev": true, + "requires": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "underscore": "1.9.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-promievent": "1.2.9", + "web3-eth-abi": "1.2.9", + "web3-eth-contract": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-eth-iban": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.9.tgz", + "integrity": "sha512-RtdVvJE0pyg9dHLy0GzDiqgnLnssSzfz/JYguhC1wsj9+Gnq1M6Diy3NixACWUAp6ty/zafyOaZnNQ+JuH9TjQ==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "web3-utils": "1.2.9" + } + }, + "web3-eth-personal": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.9.tgz", + "integrity": "sha512-cFiNrktxZ1C/rIdJFzQTvFn3/0zcsR3a+Jf8Y3KxeQDHszQtosjLWptP7bsUmDwEh4hzh0Cy3KpOxlYBWB8bJQ==", + "dev": true, + "requires": { + "@types/node": "^12.6.1", + "web3-core": "1.2.9", + "web3-core-helpers": "1.2.9", + "web3-core-method": "1.2.9", + "web3-net": "1.2.9", + "web3-utils": "1.2.9" + }, + "dependencies": { + "@types/node": { + "version": "12.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", + "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", + "dev": true + } + } + }, + "web3-net": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.9.tgz", + "integrity": "sha512-d2mTn8jPlg+SI2hTj2b32Qan6DmtU9ap/IUlJTeQbZQSkTLf0u9suW8Vjwyr4poJYXTurdSshE7OZsPNn30/ZA==", + "dev": true, + "requires": { + "web3-core": "1.2.9", + "web3-core-method": "1.2.9", + "web3-utils": "1.2.9" + } + }, + "web3-providers-http": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.9.tgz", + "integrity": "sha512-F956tCIj60Ttr0UvEHWFIhx+be3He8msoPzyA44/kfzzYoMAsCFRn5cf0zQG6al0znE75g6HlWVSN6s3yAh51A==", + "dev": true, + "requires": { + "web3-core-helpers": "1.2.9", + "xhr2-cookies": "1.1.0" + } + }, + "web3-providers-ipc": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.9.tgz", + "integrity": "sha512-NQ8QnBleoHA2qTJlqoWu7EJAD/FR5uimf7Ielzk4Z2z+m+6UAuJdJMSuQNj+Umhz9L/Ys6vpS1vHx9NizFl+aQ==", + "dev": true, + "requires": { + "oboe": "2.1.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9" + } + }, + "web3-providers-ws": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.9.tgz", + "integrity": "sha512-6+UpvINeI//dglZoAKStUXqxDOXJy6Iitv2z3dbgInG4zb8tkYl/VBDL80UjUg3ZvzWG0g7EKY2nRPEpON2TFA==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.9", + "websocket": "^1.0.31" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + } + } + }, + "web3-shh": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.9.tgz", + "integrity": "sha512-PWa8b/EaxaMinFaxy6cV0i0EOi2M7a/ST+9k9nhyhCjVa2vzXuNoBNo2IUOmeZ0WP2UQB8ByJ2+p4htlJaDOjA==", + "dev": true, + "requires": { + "web3-core": "1.2.9", + "web3-core-method": "1.2.9", + "web3-core-subscriptions": "1.2.9", + "web3-net": "1.2.9" + } + } } }, "@trufflesuite/chromafi": { @@ -13247,27 +14047,56 @@ } }, "web3": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.9.tgz", - "integrity": "sha512-Mo5aBRm0JrcNpN/g4VOrDzudymfOnHRC3s2VarhYxRA8aWgF5rnhQ0ziySaugpic1gksbXPe105pUWyRqw8HUA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.3.0.tgz", + "integrity": "sha512-4q9dna0RecnrlgD/bD1C5S+81Untbd6Z/TBD7rb+D5Bvvc0Wxjr4OP70x+LlnwuRDjDtzBwJbNUblh2grlVArw==", "dev": true, "requires": { - "web3-bzz": "1.2.9", - "web3-core": "1.2.9", - "web3-eth": "1.2.9", - "web3-eth-personal": "1.2.9", - "web3-net": "1.2.9", - "web3-shh": "1.2.9", - "web3-utils": "1.2.9" + "web3-bzz": "1.3.0", + "web3-core": "1.3.0", + "web3-eth": "1.3.0", + "web3-eth-personal": "1.3.0", + "web3-net": "1.3.0", + "web3-shh": "1.3.0", + "web3-utils": "1.3.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } } }, "web3-bzz": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.9.tgz", - "integrity": "sha512-ogVQr9jHodu9HobARtvUSmWG22cv2EUQzlPeejGWZ7j5h20HX40EDuWyomGY5VclIj5DdLY76Tmq88RTf/6nxA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.3.0.tgz", + "integrity": "sha512-ibYAnKab+sgTo/UdfbrvYfWblXjjgSMgyy9/FHa6WXS14n/HVB+HfWqGz2EM3fok8Wy5XoKGMvdqvERQ/mzq1w==", "dev": true, "requires": { - "@types/node": "^10.12.18", + "@types/node": "^12.12.6", "got": "9.6.0", "swarm-js": "^0.1.40", "underscore": "1.9.1" @@ -13280,9 +14109,9 @@ "dev": true }, "@types/node": { - "version": "10.17.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.47.tgz", - "integrity": "sha512-YZ1mMAdUPouBZCdeugjV8y1tqqr28OyL8DYbH5ePCfe9zcXtvbh1wDBy7uzlHkXo3Qi07kpzXfvycvrkby/jXw==", + "version": "12.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", + "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", "dev": true }, "cacheable-request": { @@ -13366,24 +14195,24 @@ } }, "web3-core": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.9.tgz", - "integrity": "sha512-fSYv21IP658Ty2wAuU9iqmW7V+75DOYMVZsDH/c14jcF/1VXnedOcxzxSj3vArsCvXZNe6XC5/wAuGZyQwR9RA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.3.0.tgz", + "integrity": "sha512-BwWvAaKJf4KFG9QsKRi3MNoNgzjI6szyUlgme1qNPxUdCkaS3Rdpa0VKYNHP7M/YTk82/59kNE66mH5vmoaXjA==", "dev": true, "requires": { - "@types/bn.js": "^4.11.4", - "@types/node": "^12.6.1", + "@types/bn.js": "^4.11.5", + "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.2.9", - "web3-core-method": "1.2.9", - "web3-core-requestmanager": "1.2.9", - "web3-utils": "1.2.9" + "web3-core-helpers": "1.3.0", + "web3-core-method": "1.3.0", + "web3-core-requestmanager": "1.3.0", + "web3-utils": "1.3.0" }, "dependencies": { "@types/node": { - "version": "12.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.7.tgz", - "integrity": "sha512-zvjOU1g4CpPilbTDUATnZCUb/6lARMRAqzT7ILwl1P3YvU2leEcZ2+fw9+Jrw/paXB1CgQyXTrN4hWDtqT9O2A==", + "version": "12.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", + "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", "dev": true }, "bignumber.js": { @@ -13391,86 +14220,227 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", "dev": true + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } } } }, "web3-core-helpers": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.9.tgz", - "integrity": "sha512-t0WAG3orLCE3lqi77ZoSRNFok3VQWZXTniZigDQjyOJYMAX7BU3F3js8HKbjVnAxlX3tiKoDxI0KBk9F3AxYuw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.3.0.tgz", + "integrity": "sha512-+MFb1kZCrRctf7UYE7NCG4rGhSXaQJ/KF07di9GVK1pxy1K0+rFi61ZobuV1ky9uQp+uhhSPts4Zp55kRDB5sw==", "dev": true, "requires": { "underscore": "1.9.1", - "web3-eth-iban": "1.2.9", - "web3-utils": "1.2.9" + "web3-eth-iban": "1.3.0", + "web3-utils": "1.3.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } } }, "web3-core-method": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.9.tgz", - "integrity": "sha512-bjsIoqP3gs7A/gP8+QeLUCyOKJ8bopteCSNbCX36Pxk6TYfYWNuC6hP+2GzUuqdP3xaZNe+XEElQFUNpR3oyAg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.3.0.tgz", + "integrity": "sha512-h0yFDrYVzy5WkLxC/C3q+hiMnzxdWm9p1T1rslnuHgOp6nYfqzu/6mUIXrsS4h/OWiGJt+BZ0xVZmtC31HDWtg==", "dev": true, "requires": { "@ethersproject/transactions": "^5.0.0-beta.135", "underscore": "1.9.1", - "web3-core-helpers": "1.2.9", - "web3-core-promievent": "1.2.9", - "web3-core-subscriptions": "1.2.9", - "web3-utils": "1.2.9" + "web3-core-helpers": "1.3.0", + "web3-core-promievent": "1.3.0", + "web3-core-subscriptions": "1.3.0", + "web3-utils": "1.3.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } } }, "web3-core-promievent": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.9.tgz", - "integrity": "sha512-0eAUA2zjgXTleSrnc1wdoKQPPIHU6KHf4fAscu4W9kKrR+mqP1KsjYrxY9wUyjNnXxfQ+5M29ipvbiaK8OqdOw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.3.0.tgz", + "integrity": "sha512-blv69wrXw447TP3iPvYJpllkhW6B18nfuEbrfcr3n2Y0v1Jx8VJacNZFDFsFIcgXcgUIVCtOpimU7w9v4+rtaw==", "dev": true, "requires": { - "eventemitter3": "3.1.2" + "eventemitter3": "4.0.4" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "dev": true + } } }, "web3-core-requestmanager": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.9.tgz", - "integrity": "sha512-1PwKV2m46ALUnIN5VPPgjOj8yMLJhhqZYvYJE34hTN5SErOkwhzx5zScvo5MN7v7KyQGFnpVCZKKGCiEnDmtFA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.3.0.tgz", + "integrity": "sha512-3yMbuGcomtzlmvTVqNRydxsx7oPlw3ioRL6ReF9PeNYDkUsZaUib+6Dp5eBt7UXh5X+SIn/xa1smhDHz5/HpAw==", "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.9", - "web3-providers-http": "1.2.9", - "web3-providers-ipc": "1.2.9", - "web3-providers-ws": "1.2.9" + "web3-core-helpers": "1.3.0", + "web3-providers-http": "1.3.0", + "web3-providers-ipc": "1.3.0", + "web3-providers-ws": "1.3.0" } }, "web3-core-subscriptions": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.9.tgz", - "integrity": "sha512-Y48TvXPSPxEM33OmXjGVDMzTd0j8X0t2+sDw66haeBS8eYnrEzasWuBZZXDq0zNUsqyxItgBGDn+cszkgEnFqg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.3.0.tgz", + "integrity": "sha512-MUUQUAhJDb+Nz3S97ExVWveH4utoUnsbPWP+q1HJH437hEGb4vunIb9KvN3hFHLB+aHJfPeStM/4yYTz5PeuyQ==", "dev": true, "requires": { - "eventemitter3": "3.1.2", + "eventemitter3": "4.0.4", "underscore": "1.9.1", - "web3-core-helpers": "1.2.9" + "web3-core-helpers": "1.3.0" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "dev": true + } } }, "web3-eth": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.9.tgz", - "integrity": "sha512-sIKO4iE9FEBa/CYUd6GdPd7GXt/wISqxUd8PlIld6+hvMJj02lgO7Z7p5T9mZIJcIZJGvZX81ogx8oJ9yif+Ag==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.3.0.tgz", + "integrity": "sha512-/bzJcxXPM9EM18JM5kO2JjZ3nEqVo3HxqU93aWAEgJNqaP/Lltmufl2GpvIB2Hvj+FXAjAXquxUdQ2/xP7BzHQ==", "dev": true, "requires": { "underscore": "1.9.1", - "web3-core": "1.2.9", - "web3-core-helpers": "1.2.9", - "web3-core-method": "1.2.9", - "web3-core-subscriptions": "1.2.9", - "web3-eth-abi": "1.2.9", - "web3-eth-accounts": "1.2.9", - "web3-eth-contract": "1.2.9", - "web3-eth-ens": "1.2.9", - "web3-eth-iban": "1.2.9", - "web3-eth-personal": "1.2.9", - "web3-net": "1.2.9", - "web3-utils": "1.2.9" + "web3-core": "1.3.0", + "web3-core-helpers": "1.3.0", + "web3-core-method": "1.3.0", + "web3-core-subscriptions": "1.3.0", + "web3-eth-abi": "1.3.0", + "web3-eth-accounts": "1.3.0", + "web3-eth-contract": "1.3.0", + "web3-eth-ens": "1.3.0", + "web3-eth-iban": "1.3.0", + "web3-eth-personal": "1.3.0", + "web3-net": "1.3.0", + "web3-utils": "1.3.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-eth-abi": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.3.0.tgz", + "integrity": "sha512-1OrZ9+KGrBeBRd3lO8upkpNua9+7cBsQAgor9wbA25UrcUYSyL8teV66JNRu9gFxaTbkpdrGqM7J/LXpraXWrg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.0.0-beta.153", + "underscore": "1.9.1", + "web3-utils": "1.3.0" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } } }, "web3-eth-abi": { @@ -13485,22 +14455,22 @@ } }, "web3-eth-accounts": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.9.tgz", - "integrity": "sha512-jkbDCZoA1qv53mFcRHCinoCsgg8WH+M0YUO1awxmqWXRmCRws1wW0TsuSQ14UThih5Dxolgl+e+aGWxG58LMwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.3.0.tgz", + "integrity": "sha512-/Q7EVW4L2wWUbNRtOTwAIrYvJid/5UnKMw67x/JpvRMwYC+e+744P536Ja6SG4X3MnzFvd3E/jruV4qa6k+zIw==", "dev": true, "requires": { "crypto-browserify": "3.12.0", - "eth-lib": "^0.2.8", + "eth-lib": "0.2.8", "ethereumjs-common": "^1.3.2", "ethereumjs-tx": "^2.1.1", "scrypt-js": "^3.0.1", "underscore": "1.9.1", "uuid": "3.3.2", - "web3-core": "1.2.9", - "web3-core-helpers": "1.2.9", - "web3-core-method": "1.2.9", - "web3-utils": "1.2.9" + "web3-core": "1.3.0", + "web3-core-helpers": "1.3.0", + "web3-core-method": "1.3.0", + "web3-utils": "1.3.0" }, "dependencies": { "eth-lib": { @@ -13519,145 +14489,329 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } } } }, "web3-eth-contract": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.9.tgz", - "integrity": "sha512-PYMvJf7EG/HyssUZa+pXrc8IB06K/YFfWYyW4R7ed3sab+9wWUys1TlWxBCBuiBXOokSAyM6H6P6/cKEx8FT8Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.3.0.tgz", + "integrity": "sha512-3SCge4SRNCnzLxf0R+sXk6vyTOl05g80Z5+9/B5pERwtPpPWaQGw8w01vqYqsYBKC7zH+dxhMaUgVzU2Dgf7bQ==", "dev": true, "requires": { - "@types/bn.js": "^4.11.4", + "@types/bn.js": "^4.11.5", "underscore": "1.9.1", - "web3-core": "1.2.9", - "web3-core-helpers": "1.2.9", - "web3-core-method": "1.2.9", - "web3-core-promievent": "1.2.9", - "web3-core-subscriptions": "1.2.9", - "web3-eth-abi": "1.2.9", - "web3-utils": "1.2.9" + "web3-core": "1.3.0", + "web3-core-helpers": "1.3.0", + "web3-core-method": "1.3.0", + "web3-core-promievent": "1.3.0", + "web3-core-subscriptions": "1.3.0", + "web3-eth-abi": "1.3.0", + "web3-utils": "1.3.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-eth-abi": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.3.0.tgz", + "integrity": "sha512-1OrZ9+KGrBeBRd3lO8upkpNua9+7cBsQAgor9wbA25UrcUYSyL8teV66JNRu9gFxaTbkpdrGqM7J/LXpraXWrg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.0.0-beta.153", + "underscore": "1.9.1", + "web3-utils": "1.3.0" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } } }, "web3-eth-ens": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.9.tgz", - "integrity": "sha512-kG4+ZRgZ8I1WYyOBGI8QVRHfUSbbJjvJAGA1AF/NOW7JXQ+x7gBGeJw6taDWJhSshMoEKWcsgvsiuoG4870YxQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.3.0.tgz", + "integrity": "sha512-WnOru+EcuM5dteiVYJcHXo/I7Wq+ei8RrlS2nir49M0QpYvUPGbCGgTbifcjJQTWamgORtWdljSA1s2Asdb74w==", "dev": true, "requires": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", "underscore": "1.9.1", - "web3-core": "1.2.9", - "web3-core-helpers": "1.2.9", - "web3-core-promievent": "1.2.9", - "web3-eth-abi": "1.2.9", - "web3-eth-contract": "1.2.9", - "web3-utils": "1.2.9" + "web3-core": "1.3.0", + "web3-core-helpers": "1.3.0", + "web3-core-promievent": "1.3.0", + "web3-eth-abi": "1.3.0", + "web3-eth-contract": "1.3.0", + "web3-utils": "1.3.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-eth-abi": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.3.0.tgz", + "integrity": "sha512-1OrZ9+KGrBeBRd3lO8upkpNua9+7cBsQAgor9wbA25UrcUYSyL8teV66JNRu9gFxaTbkpdrGqM7J/LXpraXWrg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.0.0-beta.153", + "underscore": "1.9.1", + "web3-utils": "1.3.0" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } } }, "web3-eth-iban": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.9.tgz", - "integrity": "sha512-RtdVvJE0pyg9dHLy0GzDiqgnLnssSzfz/JYguhC1wsj9+Gnq1M6Diy3NixACWUAp6ty/zafyOaZnNQ+JuH9TjQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.3.0.tgz", + "integrity": "sha512-v9mZWhR4fPF17/KhHLiWir4YHWLe09O3B/NTdhWqw3fdAMJNztzMHGzgHxA/4fU+rhrs/FhDzc4yt32zMEXBZw==", "dev": true, "requires": { - "bn.js": "4.11.8", - "web3-utils": "1.2.9" + "bn.js": "^4.11.9", + "web3-utils": "1.3.0" }, "dependencies": { - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } } } }, "web3-eth-personal": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.9.tgz", - "integrity": "sha512-cFiNrktxZ1C/rIdJFzQTvFn3/0zcsR3a+Jf8Y3KxeQDHszQtosjLWptP7bsUmDwEh4hzh0Cy3KpOxlYBWB8bJQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.3.0.tgz", + "integrity": "sha512-2czUhElsJdLpuNfun9GeLiClo5O6Xw+bLSjl3f4bNG5X2V4wcIjX2ygep/nfstLLtkz8jSkgl/bV7esANJyeRA==", "dev": true, "requires": { - "@types/node": "^12.6.1", - "web3-core": "1.2.9", - "web3-core-helpers": "1.2.9", - "web3-core-method": "1.2.9", - "web3-net": "1.2.9", - "web3-utils": "1.2.9" + "@types/node": "^12.12.6", + "web3-core": "1.3.0", + "web3-core-helpers": "1.3.0", + "web3-core-method": "1.3.0", + "web3-net": "1.3.0", + "web3-utils": "1.3.0" }, "dependencies": { "@types/node": { - "version": "12.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.7.tgz", - "integrity": "sha512-zvjOU1g4CpPilbTDUATnZCUb/6lARMRAqzT7ILwl1P3YvU2leEcZ2+fw9+Jrw/paXB1CgQyXTrN4hWDtqT9O2A==", + "version": "12.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.8.tgz", + "integrity": "sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg==", "dev": true + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } } } }, "web3-net": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.9.tgz", - "integrity": "sha512-d2mTn8jPlg+SI2hTj2b32Qan6DmtU9ap/IUlJTeQbZQSkTLf0u9suW8Vjwyr4poJYXTurdSshE7OZsPNn30/ZA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.3.0.tgz", + "integrity": "sha512-Xz02KylOyrB2YZzCkysEDrY7RbKxb7LADzx3Zlovfvuby7HBwtXVexXKtoGqksa+ns1lvjQLLQGb+OeLi7Sr7w==", "dev": true, "requires": { - "web3-core": "1.2.9", - "web3-core-method": "1.2.9", - "web3-utils": "1.2.9" + "web3-core": "1.3.0", + "web3-core-method": "1.3.0", + "web3-utils": "1.3.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "web3-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.0.tgz", + "integrity": "sha512-2mS5axFCbkhicmoDRuJeuo0TVGQDgC2sPi/5dblfVC+PMtX0efrb8Xlttv/eGkq7X4E83Pds34FH98TP2WOUZA==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } } }, "web3-providers-http": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.9.tgz", - "integrity": "sha512-F956tCIj60Ttr0UvEHWFIhx+be3He8msoPzyA44/kfzzYoMAsCFRn5cf0zQG6al0znE75g6HlWVSN6s3yAh51A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.3.0.tgz", + "integrity": "sha512-cMKhUI6PqlY/EC+ZDacAxajySBu8AzW8jOjt1Pe/mbRQgS0rcZyvLePGTTuoyaA8C21F8UW+EE5jj7YsNgOuqA==", "dev": true, "requires": { - "web3-core-helpers": "1.2.9", + "web3-core-helpers": "1.3.0", "xhr2-cookies": "1.1.0" } }, "web3-providers-ipc": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.9.tgz", - "integrity": "sha512-NQ8QnBleoHA2qTJlqoWu7EJAD/FR5uimf7Ielzk4Z2z+m+6UAuJdJMSuQNj+Umhz9L/Ys6vpS1vHx9NizFl+aQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.3.0.tgz", + "integrity": "sha512-0CrLuRofR+1J38nEj4WsId/oolwQEM6Yl1sOt41S/6bNI7htdkwgVhSloFIMJMDFHtRw229QIJ6wIaKQz0X1Og==", "dev": true, "requires": { - "oboe": "2.1.4", + "oboe": "2.1.5", "underscore": "1.9.1", - "web3-core-helpers": "1.2.9" + "web3-core-helpers": "1.3.0" + }, + "dependencies": { + "oboe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", + "integrity": "sha1-VVQoTFQ6ImbXo48X4HOCH73jk80=", + "dev": true, + "requires": { + "http-https": "^1.0.0" + } + } } }, "web3-providers-ws": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.9.tgz", - "integrity": "sha512-6+UpvINeI//dglZoAKStUXqxDOXJy6Iitv2z3dbgInG4zb8tkYl/VBDL80UjUg3ZvzWG0g7EKY2nRPEpON2TFA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.3.0.tgz", + "integrity": "sha512-Im5MthhJnJst8nSoq0TgbyOdaiFQFa5r6sHPOVllhgIgViDqzbnlAFW9sNzQ0Q8VXPNfPIQKi9cOrHlSRNPjRw==", "dev": true, "requires": { - "eventemitter3": "^4.0.0", + "eventemitter3": "4.0.4", "underscore": "1.9.1", - "web3-core-helpers": "1.2.9", - "websocket": "^1.0.31" + "web3-core-helpers": "1.3.0", + "websocket": "^1.0.32" }, "dependencies": { "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", "dev": true } } }, "web3-shh": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.9.tgz", - "integrity": "sha512-PWa8b/EaxaMinFaxy6cV0i0EOi2M7a/ST+9k9nhyhCjVa2vzXuNoBNo2IUOmeZ0WP2UQB8ByJ2+p4htlJaDOjA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.3.0.tgz", + "integrity": "sha512-IZTojA4VCwVq+7eEIHuL1tJXtU+LJDhO8Y2QmuwetEWW1iBgWCGPHZasipWP+7kDpSm/5lo5GRxL72FF/Os/tA==", "dev": true, "requires": { - "web3-core": "1.2.9", - "web3-core-method": "1.2.9", - "web3-core-subscriptions": "1.2.9", - "web3-net": "1.2.9" + "web3-core": "1.3.0", + "web3-core-method": "1.3.0", + "web3-core-subscriptions": "1.3.0", + "web3-net": "1.3.0" } }, "web3-utils": { diff --git a/package.json b/package.json index 69ccb86db43..12846a28c42 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "rimraf": "^3.0.2", "solhint": "^3.2.0", "solidity-coverage": "^0.7.11", - "solidity-docgen": "^0.5.3" + "solidity-docgen": "^0.5.3", + "web3": "^1.3.0" }, "dependencies": {} } diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index 79f2c10cdeb..d60e4d06dea 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -4,20 +4,43 @@ const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppeli const { expect } = require('chai'); const { MAX_UINT256, ZERO_ADDRESS } = constants; -const { keccakFromString, bufferToHex } = require('ethereumjs-util'); +const { fromRpcSig } = require('ethereumjs-util'); +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; const ERC20PermitMock = artifacts.require('ERC20PermitMock'); +const EIP712Domain = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, +]; + +const Permit = [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, +]; + contract('ERC20Permit', function (accounts) { const [ initialHolder, spender, recipient, other ] = accounts; const name = 'My Token'; const symbol = 'MTKN'; + const version = '1'; const initialSupply = new BN(100); beforeEach(async function () { this.token = await ERC20PermitMock.new(name, symbol, initialHolder, initialSupply); + + // We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id + // from within the EVM as from the JSON RPC interface. + // See https://github.com/trufflesuite/ganache-core/issues/515 + this.chainId = await this.token.getChainId(); }); it('initial nonce is 0', async function () { @@ -25,31 +48,27 @@ contract('ERC20Permit', function (accounts) { }); it('permits', async function () { - const amount = new BN(42); + const wallet = Wallet.generate(); + + const owner = wallet.getAddressString(); + const value = new BN(42); + const nonce = 0; + const deadline = MAX_UINT256; - console.log(EIP712DomainSeparator(await this.token.name(), '1', await web3.eth.net.getId(), this.token.address)); + const chainId = this.chainId; + const verifyingContract = this.token.address; - const receipt = await this.token.permit(initialHolder, spender, amount, MAX_UINT256, 0, '0x00000000000000000000000000000000', '0x00000000000000000000000000000000'); + const data = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender, value, nonce, deadline }, + }; + + const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); + + const { v, r, s } = fromRpcSig(signature); + + const receipt = await this.token.permit(owner, spender, value, deadline, v, r, s); }); }); - -function EIP712DomainSeparator(name, version, chainID, address) { - return bufferToHex(keccakFromString( - bufferToHex(keccakFromString('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')) + - bufferToHex(keccakFromString(name)) + - bufferToHex(keccakFromString(version)) + - chainID.toString() + - address - )); -} - -function ERC2612StructHash(owner, spender, value, nonce, deadline) { - return bufferToHex(keccakFromString( - bufferToHex(keccakFromString('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)')) + - owner + - spender + - value + - nonce + - deadline - )); -} From 0560ea86b591bfad221002db6953f2c1663f5177 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 2 Dec 2020 16:58:25 -0300 Subject: [PATCH 16/33] emit contract in api/drafts page --- contracts/drafts/README.adoc | 6 ++++++ contracts/token/ERC20/README.adoc | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/contracts/drafts/README.adoc b/contracts/drafts/README.adoc index 42fdc69609f..34a61262fba 100644 --- a/contracts/drafts/README.adoc +++ b/contracts/drafts/README.adoc @@ -7,3 +7,9 @@ Due to their nature as drafts, the details of these contracts may change and we == Cryptography {{EIP712}} + +== ERC 20 + +{{IERC2612Permit}} + +{{ERC20Permit}} diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index 53a930df573..c5de50f3198 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -25,6 +25,11 @@ Finally, there are some utilities to interact with ERC20 contracts in various wa * {SafeERC20} is a wrapper around the interface that eliminates the need to handle boolean return values. * {TokenTimelock} can hold tokens for a beneficiary until a specified time. +The following related EIPs are in draft status and can be found in the drafts directory. + +- {IERC2612Permit} +- {ERC20Permit} + NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc20.adoc#Presets[ERC20 Presets] (such as {ERC20PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. == Core @@ -37,18 +42,12 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{ERC20Snapshot}} -{{ERC20Permit}} - {{ERC20Pausable}} {{ERC20Burnable}} {{ERC20Capped}} -== Standard Extensions - -{{IERC2612Permit}} - == Utilities {{SafeERC20}} From 4552f525627e8234a5502b4d7fc318e7cb6e7535 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 2 Dec 2020 17:05:15 -0300 Subject: [PATCH 17/33] fix api/drafts page title --- contracts/drafts/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/drafts/README.adoc b/contracts/drafts/README.adoc index 34a61262fba..e504173cdc0 100644 --- a/contracts/drafts/README.adoc +++ b/contracts/drafts/README.adoc @@ -1,4 +1,4 @@ -= Draft EIPS += Draft EIPs This directory contains implementations of EIPs that are still in Draft status. From 9a916f70b11638f421ea916a5376368f6153eaf7 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 2 Dec 2020 17:14:06 -0300 Subject: [PATCH 18/33] add constructor documentation --- contracts/drafts/ERC20Permit.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/drafts/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol index 351412e8093..a386174674b 100644 --- a/contracts/drafts/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -24,6 +24,11 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { // solhint-disable-next-line var-name-mixedcase bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + /** + * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. + * + * It's a good idea to use the same `name` that is defined as the ERC20 token name. + */ constructor(string memory name) internal EIP712(name, "1") { } From 91e98a49028d66247269f6e1c22121a3398f508f Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 2 Dec 2020 17:15:48 -0300 Subject: [PATCH 19/33] lint --- contracts/drafts/ERC20Permit.sol | 1 + contracts/drafts/IERC2612Permit.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/drafts/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol index a386174674b..8b225764042 100644 --- a/contracts/drafts/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -72,6 +72,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { /** * @dev See {IERC2612Permit-DOMAIN_SEPARATOR}. */ + // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view override returns (bytes32) { return _domainSeparatorV4(); } diff --git a/contracts/drafts/IERC2612Permit.sol b/contracts/drafts/IERC2612Permit.sol index e61f84dd5b8..b3c4b02a41d 100644 --- a/contracts/drafts/IERC2612Permit.sol +++ b/contracts/drafts/IERC2612Permit.sol @@ -48,5 +48,6 @@ interface IERC2612Permit { /** * @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}. */ + // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); } From e331820927dbfd0420ceb529b8fac3f6d2b5b3c9 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 12:59:37 -0300 Subject: [PATCH 20/33] extract eip712 helpers --- test/drafts/EIP712.test.js | 17 ++--------------- test/helpers/eip712.js | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 test/helpers/eip712.js diff --git a/test/drafts/EIP712.test.js b/test/drafts/EIP712.test.js index 1c9a696a55d..ef7a55e766a 100644 --- a/test/drafts/EIP712.test.js +++ b/test/drafts/EIP712.test.js @@ -1,22 +1,9 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; -const EIP712 = artifacts.require('EIP712External'); - -const EIP712Domain = [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, -]; +const { EIP712Domain, domainSeparator } = require('../helpers/eip712'); -async function domainSeparator (name, version, chainId, verifyingContract) { - return '0x' + ethSigUtil.TypedDataUtils.hashStruct( - 'EIP712Domain', - { name, version, chainId, verifyingContract }, - { EIP712Domain }, - ).toString('hex'); -} +const EIP712 = artifacts.require('EIP712External'); contract('EIP712', function (accounts) { const [mailTo] = accounts; diff --git a/test/helpers/eip712.js b/test/helpers/eip712.js new file mode 100644 index 00000000000..4f8fe7dfb27 --- /dev/null +++ b/test/helpers/eip712.js @@ -0,0 +1,21 @@ +const ethSigUtil = require('eth-sig-util'); + +const EIP712Domain = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, +]; + +async function domainSeparator (name, version, chainId, verifyingContract) { + return '0x' + ethSigUtil.TypedDataUtils.hashStruct( + 'EIP712Domain', + { name, version, chainId, verifyingContract }, + { EIP712Domain }, + ).toString('hex'); +} + +module.exports = { + EIP712Domain, + domainSeparator, +}; From ade3af2a22312e20f1ff516622c820d652a8e3f3 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 13:08:53 -0300 Subject: [PATCH 21/33] test DOMAIN_SEPARATOR function --- test/drafts/ERC20Permit.test.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index d60e4d06dea..607218a5af8 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -10,12 +10,7 @@ const Wallet = require('ethereumjs-wallet').default; const ERC20PermitMock = artifacts.require('ERC20PermitMock'); -const EIP712Domain = [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, -]; +const { EIP712Domain, domainSeparator } = require('../helpers/eip712'); const Permit = [ { name: 'owner', type: 'address' }, @@ -47,6 +42,14 @@ contract('ERC20Permit', function (accounts) { expect(await this.token.nonces(spender)).to.be.bignumber.equal('0'); }); + it('domain separator', async function () { + expect( + await this.token.DOMAIN_SEPARATOR(), + ).to.equal( + await domainSeparator(name, version, this.chainId, this.token.address), + ); + }); + it('permits', async function () { const wallet = Wallet.generate(); From 9a661c99fade88bfc76629553a2efcfdb00ce7b9 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 13:09:40 -0300 Subject: [PATCH 22/33] test permit postconditions --- test/drafts/ERC20Permit.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index 607218a5af8..ae391a66bff 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -73,5 +73,8 @@ contract('ERC20Permit', function (accounts) { const { v, r, s } = fromRpcSig(signature); const receipt = await this.token.permit(owner, spender, value, deadline, v, r, s); + + expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); + expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); }); }); From f4b7f930e72420181821048a680993bb89868714 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 13:09:55 -0300 Subject: [PATCH 23/33] use different account for nonce test --- test/drafts/ERC20Permit.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index ae391a66bff..e245f1b8c99 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -39,7 +39,7 @@ contract('ERC20Permit', function (accounts) { }); it('initial nonce is 0', async function () { - expect(await this.token.nonces(spender)).to.be.bignumber.equal('0'); + expect(await this.token.nonces(initialHolder)).to.be.bignumber.equal('0'); }); it('domain separator', async function () { From 8f7f47c152760498afa1bdd6dce055bcfde863e7 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 13:14:41 -0300 Subject: [PATCH 24/33] add test for rejected transaction --- test/drafts/ERC20Permit.test.js | 41 +++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index e245f1b8c99..b5e93779f8b 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -50,7 +50,7 @@ contract('ERC20Permit', function (accounts) { ); }); - it('permits', async function () { + describe('permit', function () { const wallet = Wallet.generate(); const owner = wallet.getAddressString(); @@ -58,23 +58,34 @@ contract('ERC20Permit', function (accounts) { const nonce = 0; const deadline = MAX_UINT256; - const chainId = this.chainId; - const verifyingContract = this.token.address; - - const data = { + const buildData = (chainId, verifyingContract) => ({ primaryType: 'Permit', types: { EIP712Domain, Permit }, domain: { name, version, chainId, verifyingContract }, message: { owner, spender, value, nonce, deadline }, - }; - - const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); - - const { v, r, s } = fromRpcSig(signature); - - const receipt = await this.token.permit(owner, spender, value, deadline, v, r, s); - - expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); - expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); + }); + + it('accepts owner signature', async function () { + const data = buildData(this.chainId, this.token.address); + const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); + const { v, r, s } = fromRpcSig(signature); + + const receipt = await this.token.permit(owner, spender, value, deadline, v, r, s); + + expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); + expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); + }); + + it('rejects other signature', async function () { + const otherWallet = Wallet.generate(); + const data = buildData(this.chainId, this.token.address); + const signature = ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data }); + const { v, r, s } = fromRpcSig(signature); + + await expectRevert( + this.token.permit(owner, spender, value, deadline, v, r, s), + 'ERC20Permit: invalid signature', + ); + }); }); }); From b7b5abd325f5888a9337d3f8dfeff117c768afab Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 13:20:04 -0300 Subject: [PATCH 25/33] test expired permit --- test/drafts/ERC20Permit.test.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index b5e93779f8b..ea089431c91 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -2,7 +2,7 @@ const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS } = constants; +const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants; const { fromRpcSig } = require('ethereumjs-util'); const ethSigUtil = require('eth-sig-util'); @@ -87,5 +87,14 @@ contract('ERC20Permit', function (accounts) { 'ERC20Permit: invalid signature', ); }); + + it('rejects expired permit', async function () { + const deadline = await time.latest(); + + await expectRevert( + this.token.permit(owner, spender, value, deadline, 0, ZERO_BYTES32, ZERO_BYTES32), + 'ERC20Permit: expired deadline', + ); + }); }); }); From 24e4582af50c6da230c9cf6654148ebcf7f84705 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 13:20:28 -0300 Subject: [PATCH 26/33] remove note about domain separator and chain id --- contracts/drafts/ERC20Permit.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/drafts/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol index 8b225764042..3a8e6b54ce1 100644 --- a/contracts/drafts/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -34,9 +34,6 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { /** * @dev See {IERC2612Permit-permit}. - * - * If https://eips.ethereum.org/EIPS/eip-1344[ChainID] ever changes, the - * EIP712 Domain Separator is automatically recalculated. */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { // solhint-disable-next-line not-rely-on-time From f410734f41f632b42ab4fc4e8695f47d800de709 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 4 Dec 2020 13:57:01 -0300 Subject: [PATCH 27/33] ensure deadline is before next block timestamp --- test/drafts/ERC20Permit.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index ea089431c91..ecb0e79908f 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -89,7 +89,7 @@ contract('ERC20Permit', function (accounts) { }); it('rejects expired permit', async function () { - const deadline = await time.latest(); + const deadline = (await time.latest()) - time.duration.weeks(1); await expectRevert( this.token.permit(owner, spender, value, deadline, 0, ZERO_BYTES32, ZERO_BYTES32), From 9d60a71273e4a536543584883fc2c79e82712ac1 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 9 Dec 2020 16:42:28 -0300 Subject: [PATCH 28/33] Update contracts/token/ERC20/README.adoc Co-authored-by: Santiago Palladino --- contracts/token/ERC20/README.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index c5de50f3198..3bd68950541 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -15,7 +15,7 @@ There a few core contracts that implement the behavior specified in the EIP: Additionally there are multiple custom extensions, including: * designation of addresses that can pause token transfers for all users ({ERC20Pausable}). -* usage of tokens without sending any transactions ({ERC20Permit}). +* gasless approval of tokens ({ERC20Permit}). * efficient storage of past token balances to be later queried at any point in time ({ERC20Snapshot}). * destruction of own tokens ({ERC20Burnable}). * enforcement of a cap to the total supply when minting tokens ({ERC20Capped}). @@ -53,4 +53,3 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{SafeERC20}} {{TokenTimelock}} - From 3beaf0120a031ecb4407a809a63cbaa326496af2 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 9 Dec 2020 16:44:18 -0300 Subject: [PATCH 29/33] add test that reused signature is rejected --- test/drafts/ERC20Permit.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index ecb0e79908f..2cd3bc9f4b5 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -76,6 +76,19 @@ contract('ERC20Permit', function (accounts) { expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); }); + it('rejects reused signature', async function () { + const data = buildData(this.chainId, this.token.address); + const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); + const { v, r, s } = fromRpcSig(signature); + + await this.token.permit(owner, spender, value, deadline, v, r, s); + + await expectRevert( + this.token.permit(owner, spender, value, deadline, v, r, s), + 'ERC20Permit: invalid signature', + ); + }); + it('rejects other signature', async function () { const otherWallet = Wallet.generate(); const data = buildData(this.chainId, this.token.address); From 837586dac35188237a8e70c51b23d728b4d1ba72 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 9 Dec 2020 16:47:53 -0300 Subject: [PATCH 30/33] use valid signature when testing expired deadline --- test/drafts/ERC20Permit.test.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/drafts/ERC20Permit.test.js b/test/drafts/ERC20Permit.test.js index 2cd3bc9f4b5..2ba38e37c3c 100644 --- a/test/drafts/ERC20Permit.test.js +++ b/test/drafts/ERC20Permit.test.js @@ -56,9 +56,9 @@ contract('ERC20Permit', function (accounts) { const owner = wallet.getAddressString(); const value = new BN(42); const nonce = 0; - const deadline = MAX_UINT256; + const maxDeadline = MAX_UINT256; - const buildData = (chainId, verifyingContract) => ({ + const buildData = (chainId, verifyingContract, deadline = maxDeadline) => ({ primaryType: 'Permit', types: { EIP712Domain, Permit }, domain: { name, version, chainId, verifyingContract }, @@ -70,7 +70,7 @@ contract('ERC20Permit', function (accounts) { const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); const { v, r, s } = fromRpcSig(signature); - const receipt = await this.token.permit(owner, spender, value, deadline, v, r, s); + const receipt = await this.token.permit(owner, spender, value, maxDeadline, v, r, s); expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); @@ -81,10 +81,10 @@ contract('ERC20Permit', function (accounts) { const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); const { v, r, s } = fromRpcSig(signature); - await this.token.permit(owner, spender, value, deadline, v, r, s); + await this.token.permit(owner, spender, value, maxDeadline, v, r, s); await expectRevert( - this.token.permit(owner, spender, value, deadline, v, r, s), + this.token.permit(owner, spender, value, maxDeadline, v, r, s), 'ERC20Permit: invalid signature', ); }); @@ -96,7 +96,7 @@ contract('ERC20Permit', function (accounts) { const { v, r, s } = fromRpcSig(signature); await expectRevert( - this.token.permit(owner, spender, value, deadline, v, r, s), + this.token.permit(owner, spender, value, maxDeadline, v, r, s), 'ERC20Permit: invalid signature', ); }); @@ -104,8 +104,12 @@ contract('ERC20Permit', function (accounts) { it('rejects expired permit', async function () { const deadline = (await time.latest()) - time.duration.weeks(1); + const data = buildData(this.chainId, this.token.address, deadline); + const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); + const { v, r, s } = fromRpcSig(signature); + await expectRevert( - this.token.permit(owner, spender, value, deadline, 0, ZERO_BYTES32, ZERO_BYTES32), + this.token.permit(owner, spender, value, deadline, v, r, s), 'ERC20Permit: expired deadline', ); }); From f26f0e3cc1aec308c6dfbd5d0afbef1306908a1c Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 9 Dec 2020 17:44:45 -0300 Subject: [PATCH 31/33] rename IERC2612Permit to IERC20Permit --- contracts/drafts/ERC20Permit.sol | 12 ++++++------ .../drafts/{IERC2612Permit.sol => IERC20Permit.sol} | 2 +- contracts/drafts/README.adoc | 2 +- contracts/token/ERC20/README.adoc | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename contracts/drafts/{IERC2612Permit.sol => IERC20Permit.sol} (98%) diff --git a/contracts/drafts/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol index 3a8e6b54ce1..b97e5adfc95 100644 --- a/contracts/drafts/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -3,7 +3,7 @@ pragma solidity >=0.6.5 <0.8.0; import "../token/ERC20/ERC20.sol"; -import "./IERC2612Permit.sol"; +import "./IERC20Permit.sol"; import "../cryptography/ECDSA.sol"; import "../utils/Counters.sol"; import "./EIP712.sol"; @@ -14,9 +14,9 @@ import "./EIP712.sol"; * signature using the {permit} method, and then spend them via * {IERC20-transferFrom}. * - * The {permit} signature mechanism conforms to the {IERC2612Permit} interface. + * The {permit} signature mechanism conforms to the {IERC20Permit} interface. */ -abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { +abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { using Counters for Counters.Counter; mapping (address => Counters.Counter) private _nonces; @@ -33,7 +33,7 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { } /** - * @dev See {IERC2612Permit-permit}. + * @dev See {IERC20Permit-permit}. */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { // solhint-disable-next-line not-rely-on-time @@ -60,14 +60,14 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit, EIP712 { } /** - * @dev See {IERC2612Permit-nonces}. + * @dev See {IERC20Permit-nonces}. */ function nonces(address owner) public view override returns (uint256) { return _nonces[owner].current(); } /** - * @dev See {IERC2612Permit-DOMAIN_SEPARATOR}. + * @dev See {IERC20Permit-DOMAIN_SEPARATOR}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view override returns (bytes32) { diff --git a/contracts/drafts/IERC2612Permit.sol b/contracts/drafts/IERC20Permit.sol similarity index 98% rename from contracts/drafts/IERC2612Permit.sol rename to contracts/drafts/IERC20Permit.sol index b3c4b02a41d..2a375e81072 100644 --- a/contracts/drafts/IERC2612Permit.sol +++ b/contracts/drafts/IERC20Permit.sol @@ -11,7 +11,7 @@ pragma solidity >=0.6.0 <0.8.0; * * See https://eips.ethereum.org/EIPS/eip-2612. */ -interface IERC2612Permit { +interface IERC20Permit { /** * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens, * given `owner`'s signed approval. diff --git a/contracts/drafts/README.adoc b/contracts/drafts/README.adoc index e504173cdc0..3efcef91d61 100644 --- a/contracts/drafts/README.adoc +++ b/contracts/drafts/README.adoc @@ -10,6 +10,6 @@ Due to their nature as drafts, the details of these contracts may change and we == ERC 20 -{{IERC2612Permit}} +{{IERC20Permit}} {{ERC20Permit}} diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index 3bd68950541..bf4e040bc92 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -27,7 +27,7 @@ Finally, there are some utilities to interact with ERC20 contracts in various wa The following related EIPs are in draft status and can be found in the drafts directory. -- {IERC2612Permit} +- {IERC20Permit} - {ERC20Permit} NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc20.adoc#Presets[ERC20 Presets] (such as {ERC20PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. From acd82cd357505b8526b32e4b66f31fbbc5ca49f9 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 9 Dec 2020 18:02:55 -0300 Subject: [PATCH 32/33] review documentation --- contracts/drafts/ERC20Permit.sol | 10 +++++----- contracts/drafts/IERC20Permit.sol | 14 ++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/contracts/drafts/ERC20Permit.sol b/contracts/drafts/ERC20Permit.sol index b97e5adfc95..7edee3fd14a 100644 --- a/contracts/drafts/ERC20Permit.sol +++ b/contracts/drafts/ERC20Permit.sol @@ -9,12 +9,12 @@ import "../utils/Counters.sol"; import "./EIP712.sol"; /** - * @dev Extension of {ERC20} that allows token holders to use their tokens - * without sending any transactions by setting {IERC20-allowance} with a - * signature using the {permit} method, and then spend them via - * {IERC20-transferFrom}. + * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * - * The {permit} signature mechanism conforms to the {IERC20Permit} interface. + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. */ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { using Counters for Counters.Counter; diff --git a/contracts/drafts/IERC20Permit.sol b/contracts/drafts/IERC20Permit.sol index 2a375e81072..ccdf81d952b 100644 --- a/contracts/drafts/IERC20Permit.sol +++ b/contracts/drafts/IERC20Permit.sol @@ -3,13 +3,12 @@ pragma solidity >=0.6.0 <0.8.0; /** - * @dev Interface of the ERC2612 standard as defined in the EIP. + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * - * Adds the {permit} method, which can be used to change one's - * {IERC20-allowance} without having to send a transaction, by signing a - * message. This allows users to spend tokens without having to hold Ether. - * - * See https://eips.ethereum.org/EIPS/eip-2612. + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. */ interface IERC20Permit { /** @@ -23,7 +22,6 @@ interface IERC20Permit { * * Requirements: * - * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` @@ -37,7 +35,7 @@ interface IERC20Permit { function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; /** - * @dev Returns the current ERC2612 nonce for `owner`. This value must be + * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This From 71c9e3ddde9f79f77936881b85e5c592ec65e1ec Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Fri, 11 Dec 2020 13:33:12 -0300 Subject: [PATCH 33/33] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f461f43c7b..678f75d671f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * `BeaconProxy`: added new kind of proxy that allows simultaneous atomic upgrades. ([#2411](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2411)) * `EIP712`: added helpers to verify EIP712 typed data signatures on chain. ([#2418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2418)) + * `ERC20Permit`: added an implementation of the ERC20 permit extension for gasless token approvals. ([#2237](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237)) * Presets: added token presets with preminted fixed supply `ERC20PresetFixedSupply` and `ERC777PresetFixedSupply`. ([#2399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2399)) * `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))