Skip to content

EIP2612 permit #618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/ERC677BridgeToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ contract ERC677BridgeToken is IBurnableMintableERC677Token, DetailedERC20, Burna
}

function getTokenInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) {
return (2, 4, 0);
return (2, 5, 0);
}

function superTransfer(address _to, uint256 _value) internal returns (bool) {
Expand Down
132 changes: 102 additions & 30 deletions contracts/PermittableToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ contract PermittableToken is ERC677BridgeToken {

// EIP712 niceties
bytes32 public DOMAIN_SEPARATOR;
// bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
// bytes32 public constant PERMIT_TYPEHASH_LEGACY = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
bytes32 public constant PERMIT_TYPEHASH_LEGACY = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
// bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

mapping(address => uint256) public nonces;
mapping(address => mapping(address => uint256)) public expirations;
Expand Down Expand Up @@ -56,7 +58,7 @@ contract PermittableToken is ERC677BridgeToken {
} else {
// If allowance is unlimited by `permit`, `approve`, or `increaseAllowance`
// function, don't adjust it. But the expiration date must be empty or in the future
require(expirations[_sender][msg.sender] == 0 || expirations[_sender][msg.sender] >= _now());
require(expirations[_sender][msg.sender] == 0 || expirations[_sender][msg.sender] >= now);
}
} else {
// If `_sender` is `msg.sender`,
Expand All @@ -71,20 +73,16 @@ contract PermittableToken is ERC677BridgeToken {
/// @param _to The address which will spend the funds.
/// @param _value The amount of tokens to be spent.
function approve(address _to, uint256 _value) public returns (bool result) {
result = super.approve(_to, _value);
if (_value == uint256(-1)) {
delete expirations[msg.sender][_to];
}
_approveAndResetExpirations(msg.sender, _to, _value);
return true;
}

/// @dev Atomically increases the allowance granted to spender by the caller.
/// @param _to The address which will spend the funds.
/// @param _addedValue The amount of tokens to increase the allowance by.
function increaseAllowance(address _to, uint256 _addedValue) public returns (bool result) {
result = super.increaseAllowance(_to, _addedValue);
if (allowed[msg.sender][_to] == uint256(-1)) {
delete expirations[msg.sender][_to];
}
_approveAndResetExpirations(msg.sender, _to, allowed[msg.sender][_to].add(_addedValue));
return true;
}

/// @dev An alias for `transfer` function.
Expand Down Expand Up @@ -134,33 +132,107 @@ contract PermittableToken is ERC677BridgeToken {
bytes32 _r,
bytes32 _s
) external {
require(_holder != address(0));
require(_spender != address(0));
require(_expiry == 0 || _now() <= _expiry);

require(_v == 27 || _v == 28);
require(uint256(_s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0);

bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, _holder, _spender, _nonce, _expiry, _allowed))
)
);
require(_expiry == 0 || now <= _expiry);

require(_holder == ecrecover(digest, _v, _r, _s));
bytes32 digest = _digest(abi.encode(PERMIT_TYPEHASH_LEGACY, _holder, _spender, _nonce, _expiry, _allowed));

require(_holder == _recover(digest, _v, _r, _s));
require(_nonce == nonces[_holder]++);

uint256 amount = _allowed ? uint256(-1) : 0;

allowed[_holder][_spender] = amount;
expirations[_holder][_spender] = _allowed ? _expiry : 0;

emit Approval(_holder, _spender, amount);
_approve(_holder, _spender, amount);
}

function _now() internal view returns (uint256) {
return now;
/** @dev Allows to spend holder's unlimited amount by the specified spender according to EIP2612.
* The function can be called by anyone, but requires having allowance parameters
* signed by the holder according to EIP712.
* @param _holder The holder's address.
* @param _spender The spender's address.
* @param _value Allowance value to set as a result of the call.
* @param _deadline The deadline timestamp to call the permit function. Must be a timestamp in the future.
* Note that timestamps are not precise, malicious miner/validator can manipulate them to some extend.
* Assume that there can be a 900 seconds time delta between the desired timestamp and the actual expiration.
* @param _v A final byte of signature (ECDSA component).
* @param _r The first 32 bytes of signature (ECDSA component).
* @param _s The second 32 bytes of signature (ECDSA component).
*/
function permit(
address _holder,
address _spender,
uint256 _value,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external {
require(now <= _deadline);

uint256 nonce = nonces[_holder]++;
bytes32 digest = _digest(abi.encode(PERMIT_TYPEHASH, _holder, _spender, _value, nonce, _deadline));

require(_holder == _recover(digest, _v, _r, _s));

_approveAndResetExpirations(_holder, _spender, _value);
}

/**
* @dev Sets a new allowance value for the given owner and spender addresses.
* Resets expiration timestamp in case of unlimited approval.
* @param _owner address tokens holder.
* @param _spender address of tokens spender.
* @param _amount amount of approved tokens.
*/
function _approveAndResetExpirations(address _owner, address _spender, uint256 _amount) internal {
_approve(_owner, _spender, _amount);

// it is not necessary to reset _expirations in other cases, since it is only used together with infinite allowance
if (_amount == uint256(-1)) {
delete expirations[_owner][_spender];
}
}

/**
* @dev Internal function for issuing an allowance.
* @param _owner address of the tokens owner.
* @param _spender address of the approved tokens spender.
* @param _amount amount of the approved tokens.
*/
function _approve(address _owner, address _spender, uint256 _amount) internal {
require(_owner != address(0), "ERC20: approve from the zero address");
require(_spender != address(0), "ERC20: approve to the zero address");

allowed[_owner][_spender] = _amount;
emit Approval(_owner, _spender, _amount);
}

/**
* @dev Calculates the message digest for encoded EIP712 typed struct.
* @param _typedStruct encoded payload.
*/
function _digest(bytes memory _typedStruct) internal view returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, keccak256(_typedStruct)));
}

/**
* @dev Derives the signer address for the given message digest and ECDSA signature params.
* @param _digest signed message digest.
* @param _v a final byte of signature (ECDSA component).
* @param _r the first 32 bytes of the signature (ECDSA component).
* @param _s the second 32 bytes of the signature (ECDSA component).
*/
function _recover(bytes32 _digest, uint8 _v, bytes32 _r, bytes32 _s) internal pure returns (address) {
require(_v == 27 || _v == 28, "ECDSA: invalid signature 'v' value");
require(
uint256(_s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,
"ECDSA: invalid signature 's' value"
);

address signer = ecrecover(_digest, _v, _r, _s);
require(signer != address(0), "ECDSA: invalid signature");

return signer;
}
}
9 changes: 0 additions & 9 deletions contracts/mocks/ERC677BridgeTokenRewardableMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,4 @@ contract ERC677BridgeTokenRewardableMock is ERC677BridgeTokenRewardable {
function setStakingContractMock(address _stakingContract) public {
stakingContract = _stakingContract;
}

function setNow(uint256 _timestamp) public {
_blockTimestamp = _timestamp;
}

function _now() internal view returns (uint256) {
return _blockTimestamp != 0 ? _blockTimestamp : now;
}

}
23 changes: 0 additions & 23 deletions contracts/mocks/PermittableTokenMock.sol

This file was deleted.

26 changes: 0 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-prettier": "^3.0.1",
"eth-gas-reporter": "^0.2.11",
"ethereumjs-abi": "0.6.8",
"ethereumjs-util": "5.2.0",
"nodemon": "^1.17.3",
"prettier": "^1.18.2",
Expand Down
119 changes: 0 additions & 119 deletions test/helpers/eip712.sign.permit.js

This file was deleted.

Loading