From 870cf4f520919262bbc3f379ffa1bf684a84446c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Thu, 14 Jan 2021 13:30:23 +0000 Subject: [PATCH 1/2] feat: transferFromWithSignature drafted --- contracts/ERC20Permit.sol | 85 +++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/contracts/ERC20Permit.sol b/contracts/ERC20Permit.sol index 03daa2a..98fc78f 100644 --- a/contracts/ERC20Permit.sol +++ b/contracts/ERC20Permit.sol @@ -3,21 +3,18 @@ pragma solidity ^0.6.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "./IERC2612.sol"; /** - * @author Georgios Konstantopoulos - * @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}. + * @author Alberto Cuesta Cañada + * @dev Extension of {ERC20} that allows token holders set allowances or approve a single `transferFrom` using off-chain signatures. * - * The {permit} signature mechanism conforms to the {IERC2612} interface. + * The mechanisms don't conform to the {IERC2612} interface. */ -abstract contract ERC20Permit is ERC20, IERC2612 { - mapping (address => uint256) public override nonces; +abstract contract ERC20Permit is ERC20 { + mapping (address => uint256) public nonces; bytes32 public immutable PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public immutable TRANSFERFROM_TYPEHASH = keccak256("TransferFrom(address sender,address recipient,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public immutable DOMAIN_SEPARATOR; constructor(string memory name_, string memory symbol_) internal ERC20(name_, symbol_) { @@ -38,12 +35,18 @@ abstract contract ERC20Permit is ERC20, IERC2612 { } /** - * @dev See {IERC2612-permit}. + * @dev Similar to {IERC2612-permit}, but with a packed signature. * * In cases where the free option is not a concern, deadline can simply be - * set to uint(-1), so it should be seen as an optional parameter + * set to uint(-1), so it should be seen as an optional parameter. */ - function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { + function permit( + address owner, + address spender, + uint256 amount, + uint256 deadline, + bytes memory signature + ) public virtual { require(deadline >= block.timestamp, "ERC20Permit: expired deadline"); bytes32 hashStruct = keccak256( @@ -65,12 +68,68 @@ abstract contract ERC20Permit is ERC20, IERC2612 { ) ); + (bytes32 r, bytes32 s, uint8 v) = unpack(signature); address signer = ecrecover(hash, v, r, s); require( signer != address(0) && signer == owner, - "ERC20Permit: invalid signature" + "ERC20Permit::permit: invalid signature" ); _approve(owner, spender, amount); } + + /** + * @dev Similar to {IERC20-transferFrom}, but with a packed signature. It doesn't check or change allowances. + * + * In cases where the free option is not a concern, deadline can simply be + * set to uint(-1), so it should be seen as an optional parameter. + */ + function transferFromWithSignature( + address sender, + address recipient, + uint256 amount, + uint256 deadline, + bytes memory signature + ) public virtual returns(bool) { + require(deadline >= block.timestamp, "ERC20Permit: expired deadline"); + + bytes32 hashStruct = keccak256( + abi.encode( + TRANSFERFROM_TYPEHASH, + sender, + recipient, + amount, + nonces[sender]++, + deadline + ) + ); + + bytes32 hash = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + hashStruct + ) + ); + + (bytes32 r, bytes32 s, uint8 v) = unpack(signature); + address signer = ecrecover(hash, v, r, s); + require( + signer != address(0) && signer == sender, + "ERC20Permit::transferFrom: invalid signature" + ); + + _transfer(sender, recipient, amount); + return true; + } + + /// @dev Unpack r, s and v from a `bytes` signature. + /// @param signature A packed signature. + function unpack(bytes memory signature) internal pure returns (bytes32 r, bytes32 s, uint8 v) { + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + } } From cf39ced95a2d83105c035d9381b21cbe87c086fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Sat, 23 Jan 2021 17:06:31 +0000 Subject: [PATCH 2/2] transferFromWithSignature drafted --- buidler.config.ts | 2 +- contracts/ERC20Permit.sol | 4 ++-- contracts/IERC2612.sol | 2 +- contracts/Migrations.sol | 25 ------------------------- contracts/TestERC20.sol | 2 +- package.json | 6 +++--- yarn.lock | 10 +++++----- 7 files changed, 13 insertions(+), 38 deletions(-) delete mode 100644 contracts/Migrations.sol diff --git a/buidler.config.ts b/buidler.config.ts index 55f6015..e5f5a9a 100644 --- a/buidler.config.ts +++ b/buidler.config.ts @@ -7,7 +7,7 @@ usePlugin("buidler-gas-reporter"); export default { defaultNetwork: "buidlerevm", solc: { - version: "0.6.10", + version: "0.8.0", optimizer: { enabled: true, runs: 200 diff --git a/contracts/ERC20Permit.sol b/contracts/ERC20Permit.sol index 98fc78f..bbe235e 100644 --- a/contracts/ERC20Permit.sol +++ b/contracts/ERC20Permit.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol -pragma solidity ^0.6.0; +pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "acc-erc20/contracts/ERC20.sol"; /** * @author Alberto Cuesta Cañada diff --git a/contracts/IERC2612.sol b/contracts/IERC2612.sol index c87c73b..f190abf 100644 --- a/contracts/IERC2612.sol +++ b/contracts/IERC2612.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/ -pragma solidity ^0.6.0; +pragma solidity ^0.8.0; /** * @dev Interface of the ERC2612 standard as defined in the EIP. diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol deleted file mode 100644 index 54e3879..0000000 --- a/contracts/Migrations.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.6.10; - -contract Migrations { - address public owner; - uint public last_completed_migration; - - constructor() public { - owner = msg.sender; - } - - modifier restricted() { - if (msg.sender == owner) - _; - } - - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } - - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/contracts/TestERC20.sol b/contracts/TestERC20.sol index 0c9d766..27de5e9 100644 --- a/contracts/TestERC20.sol +++ b/contracts/TestERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.6.10; +pragma solidity ^0.8.0; import "./ERC20Permit.sol"; diff --git a/package.json b/package.json index 0bfbebc..e01fd40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "erc20permit", - "version": "0.0.4", + "version": "0.0.5", "description": "Solidity contracts implementing ERC2612", "author": "Alberto Cuesta Cañada", "engines": { @@ -30,16 +30,16 @@ "lint:sol": "solhint -f table contracts/**/*.sol" }, "dependencies": { - "@openzeppelin/contracts": "3.1.0" + "acc-erc20": "^0.5.1" }, "devDependencies": { "@nomiclabs/buidler": "^1.3.8", "@nomiclabs/buidler-truffle5": "^1.3.4", "@nomiclabs/buidler-web3": "^1.3.4", - "@openzeppelin/contracts": "3.1.0", "@openzeppelin/test-helpers": "^0.5.6", "@truffle/hdwallet-provider": "^1.0.40", "@types/mocha": "^8.0.0", + "acc-erc20": "^0.5.1", "buidler-gas-reporter": "0.1.4-beta.4", "chai": "4.2.0", "ethereumjs-util": "^7.0.3", diff --git a/yarn.lock b/yarn.lock index 56e507c..81083a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -639,11 +639,6 @@ fs-extra "^8.1.0" try-require "^1.2.1" -"@openzeppelin/contracts@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.1.0.tgz#bcea457ef89069fbe5a617f50b25b6a8272895d5" - integrity sha512-dVXDnUKxrAKLzPdCRkz+N8qsVkK1XxJ6kk3zuI6zaQmcKxN7CkizoDP7lXxcs/Mi2I0mxceTRjJBqlzFffLJrQ== - "@openzeppelin/test-helpers@^0.5.6": version "0.5.6" resolved "https://registry.yarnpkg.com/@openzeppelin/test-helpers/-/test-helpers-0.5.6.tgz#cafa3fdb741be9e3ff525916257d6cdce45ed86a" @@ -1115,6 +1110,11 @@ abstract-leveldown@~2.7.1: dependencies: xtend "~4.0.0" +acc-erc20@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/acc-erc20/-/acc-erc20-0.5.0.tgz#01aef88ba75b5401998925179eae586da34d8b8e" + integrity sha512-+LpOR2SvvNtoTVBxw0VuRIEkiS7nibe1mBmhNb/x8k0YhRK/Y7XYe+sKjnf33sjIlbyu8Xj5V8rj1p3sz/zHEA== + accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"