Skip to content

Commit

Permalink
add v1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
gorgos committed Oct 10, 2020
1 parent a7b337d commit f7a23f8
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 229 deletions.
67 changes: 66 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,67 @@
# ERC20-permit
Package for implementing the ERC20 permit (EIP-2612)

Package for implementing the ERC20 permit (EIP-2612). Unaudited, use at own risk.

## Installation

1. Install the package via NPM:

```bash
$ npm install @soliditylabs/erc20-permit --save-dev
```

Or Yarn:

```bash
$ yarn add @soliditylabs/erc20-permit --dev
```

2. Import it into your ERC-20 contract:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import {ERC20, ERC20Permit} from "@soliditylabs/erc20-permit/contracts/ERC20Permit.sol";
contract ERC20PermitToken is ERC20Permit {
constructor (uint256 initialSupply) ERC20("ERC20Permit-Token", "EPT") {
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
function burn(address from, uint256 amount) public {
_burn(from, amount);
}
}
```

## Running tests

1. Clone the repository

```bash
$ git clone https://github.com/soliditylabs/ERC20-Permit
```

2. Install the dependencies

```bash
$ cd ERC20-Permit
$ npm install
```

3. Run Buidler Node

```bash
$ npx buidler node
```

4. Run tests

```bash
$ npm test
```
142 changes: 87 additions & 55 deletions contracts/ERC20Permit.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

import {ECDSA} from "./lib/ECDSA.sol";
import {IERC2612Permit} from "./IERC2612Permit.sol";

import "@nomiclabs/buidler/console.sol";
Expand All @@ -22,14 +21,10 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit {

mapping(address => Counters.Counter) private _nonces;

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;
mapping(uint256 => bytes32) public domainSeparators;

constructor() {
_updateDomainSeparator();
Expand All @@ -52,59 +47,62 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit {
) public virtual override {
require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

bytes32 hashStruct = keccak256(
abi.encode(
_PERMIT_TYPEHASH,
owner,
spender,
amount,
_nonces[owner].current(),
deadline
)
);
// Assembly for more efficiently computing:
// bytes32 hashStruct = keccak256(
// abi.encode(
// _PERMIT_TYPEHASH,
// owner,
// spender,
// amount,
// _nonces[owner].current(),
// deadline
// )
// );

bytes32 hashStruct;
uint256 nonce = _nonces[owner].current();

bytes32 hash = keccak256(
abi.encodePacked(uint16(0x1901), _domainSeparator(), hashStruct)
);
assembly {
// Load free memory pointer
let memPtr := mload(64)

// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
mstore(memPtr, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9)
mstore(add(memPtr, 32), owner)
mstore(add(memPtr, 64), spender)
mstore(add(memPtr, 96), amount)
mstore(add(memPtr, 128), nonce)
mstore(add(memPtr, 160), deadline)

hashStruct := keccak256(memPtr, 192)
}

address signer = ECDSA.recover(hash, v, r, s);
bytes32 eip712DomainHash = _domainSeparator();

console.log("owner", owner);
console.log("signer", signer);
// Assembly for more efficient computing:
// bytes32 hash = keccak256(
// abi.encodePacked(uint16(0x1901), eip712DomainHash, hashStruct)
// );

require(signer == owner, "ERC20Permit: invalid signature");
bytes32 hash;

_nonces[owner].increment();
_approve(owner, spender, amount);
}
assembly {
// Load free memory pointer
let memPtr := mload(64)

function testSig(
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public view returns(address) {
require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header
mstore(add(memPtr, 2), eip712DomainHash) // EIP712 domain hash
mstore(add(memPtr, 34), hashStruct) // Hash of struct

bytes32 hashStruct = keccak256(
abi.encode(
_PERMIT_TYPEHASH,
owner,
spender,
amount,
_nonces[owner].current(),
deadline
)
);
hash := keccak256(memPtr, 66)
}

bytes32 hash = keccak256(
abi.encodePacked(uint16(0x1901), _domainSeparators[_chainID()], hashStruct)
);
address signer = _recover(hash, v, r, s);

return ECDSA.recover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");

_nonces[owner].increment();
_approve(owner, spender, amount);
}

/**
Expand All @@ -117,26 +115,27 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit {
function _updateDomainSeparator() private returns (bytes32) {
uint256 chainID = _chainID();

// no need for assembly, running very rarely
bytes32 newDomainSeparator = keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name())),
keccak256(bytes("1")), // Version
keccak256(bytes(name())), // ERC-20 Name
keccak256(bytes("1")), // Version
chainID,
address(this)
)
);

_domainSeparators[chainID] = newDomainSeparator;
domainSeparators[chainID] = newDomainSeparator;

return newDomainSeparator;
}

// Returns the domain separator, updating it if chainID changes
function _domainSeparator() private returns (bytes32) {
bytes32 domainSeparator = _domainSeparators[_chainID()];
bytes32 domainSeparator = domainSeparators[_chainID()];

if (domainSeparator != 0x00) {
return domainSeparator;
Expand All @@ -153,4 +152,37 @@ abstract contract ERC20Permit is ERC20, IERC2612Permit {

return chainID;
}

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
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (
uint256(s) >
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
) {
revert("ECDSA: invalid signature 's' value");
}

if (v != 27 && v != 28) {
revert("ECDSA: invalid signature 'v' value");
}

// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");

return signer;
}
}
109 changes: 0 additions & 109 deletions contracts/lib/ECDSA.sol

This file was deleted.

8 changes: 0 additions & 8 deletions contracts/test/ERC20PermitMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,4 @@ contract ERC20PermitMock is ERC20Permit {
constructor (uint256 initialSupply) ERC20("ERC20Permit-Token", "EPT") {
_mint(msg.sender, initialSupply);
}

function mint(address to, uint256 amount) public {
_mint(to, amount);
}

function burn(address from, uint256 amount) public {
_burn(from, amount);
}
}
Loading

0 comments on commit f7a23f8

Please sign in to comment.