-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* introduce: permit2 * update nonce mechanism * add permit2 deployment file * add SIP args & onchain test * add unit test for signature exploit pervention * consider new function to implement permit2 * add MocIntegration deployment file testnet * remove nonce in the permit2 integrated contract * add testnet deployment files * add mainnet deployment for MocIntegration * .prettierignore permit2 folder contracts * update dist
- Loading branch information
Showing
72 changed files
with
5,219 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ tmp | |
temp | ||
coverage* | ||
gasReporterOutput.json | ||
contracts/permit2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import { ERC20 } from "./ERC20.sol"; | ||
import { SafeTransferLib } from "./libraries/SafeTransferLib.sol"; | ||
import { PermitHash } from "./libraries/PermitHash.sol"; | ||
import { SignatureVerification } from "./libraries/SignatureVerification.sol"; | ||
import { EIP712 } from "./EIP712.sol"; | ||
import { IAllowanceTransfer } from "./interfaces/IAllowanceTransfer.sol"; | ||
import { SignatureExpired, InvalidNonce } from "./PermitErrors.sol"; | ||
import { Allowance } from "./libraries/Allowance.sol"; | ||
|
||
contract AllowanceTransfer is IAllowanceTransfer, EIP712 { | ||
using SignatureVerification for bytes; | ||
using SafeTransferLib for ERC20; | ||
using PermitHash for PermitSingle; | ||
using PermitHash for PermitBatch; | ||
using Allowance for PackedAllowance; | ||
|
||
/// @notice Maps users to tokens to spender addresses and information about the approval on the token | ||
/// @dev Indexed in the order of token owner address, token address, spender address | ||
/// @dev The stored word saves the allowed amount, expiration on the allowance, and nonce | ||
mapping(address => mapping(address => mapping(address => PackedAllowance))) public allowance; | ||
|
||
/// @inheritdoc IAllowanceTransfer | ||
function approve(address token, address spender, uint160 amount, uint48 expiration) external { | ||
PackedAllowance storage allowed = allowance[msg.sender][token][spender]; | ||
allowed.updateAmountAndExpiration(amount, expiration); | ||
emit Approval(msg.sender, token, spender, amount, expiration); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransfer | ||
function permit( | ||
address owner, | ||
PermitSingle memory permitSingle, | ||
bytes calldata signature | ||
) external { | ||
if (block.timestamp > permitSingle.sigDeadline) | ||
revert SignatureExpired(permitSingle.sigDeadline); | ||
|
||
// Verify the signer address from the signature. | ||
signature.verify(_hashTypedData(permitSingle.hash()), owner); | ||
|
||
_updateApproval(permitSingle.details, owner, permitSingle.spender); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransfer | ||
function permit( | ||
address owner, | ||
PermitBatch memory permitBatch, | ||
bytes calldata signature | ||
) external { | ||
if (block.timestamp > permitBatch.sigDeadline) | ||
revert SignatureExpired(permitBatch.sigDeadline); | ||
|
||
// Verify the signer address from the signature. | ||
signature.verify(_hashTypedData(permitBatch.hash()), owner); | ||
|
||
address spender = permitBatch.spender; | ||
unchecked { | ||
uint256 length = permitBatch.details.length; | ||
for (uint256 i = 0; i < length; ++i) { | ||
_updateApproval(permitBatch.details[i], owner, spender); | ||
} | ||
} | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransfer | ||
function transferFrom(address from, address to, uint160 amount, address token) external { | ||
_transfer(from, to, amount, token); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransfer | ||
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external { | ||
unchecked { | ||
uint256 length = transferDetails.length; | ||
for (uint256 i = 0; i < length; ++i) { | ||
AllowanceTransferDetails memory transferDetail = transferDetails[i]; | ||
_transfer( | ||
transferDetail.from, | ||
transferDetail.to, | ||
transferDetail.amount, | ||
transferDetail.token | ||
); | ||
} | ||
} | ||
} | ||
|
||
/// @notice Internal function for transferring tokens using stored allowances | ||
/// @dev Will fail if the allowed timeframe has passed | ||
function _transfer(address from, address to, uint160 amount, address token) private { | ||
PackedAllowance storage allowed = allowance[from][token][msg.sender]; | ||
|
||
if (block.timestamp > allowed.expiration) revert AllowanceExpired(allowed.expiration); | ||
|
||
uint256 maxAmount = allowed.amount; | ||
if (maxAmount != type(uint160).max) { | ||
if (amount > maxAmount) { | ||
revert InsufficientAllowance(maxAmount); | ||
} else { | ||
unchecked { | ||
allowed.amount = uint160(maxAmount) - amount; | ||
} | ||
} | ||
} | ||
|
||
// Transfer the tokens from the from address to the recipient. | ||
ERC20(token).safeTransferFrom(from, to, amount); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransfer | ||
function lockdown(TokenSpenderPair[] calldata approvals) external { | ||
address owner = msg.sender; | ||
// Revoke allowances for each pair of spenders and tokens. | ||
unchecked { | ||
uint256 length = approvals.length; | ||
for (uint256 i = 0; i < length; ++i) { | ||
address token = approvals[i].token; | ||
address spender = approvals[i].spender; | ||
|
||
allowance[owner][token][spender].amount = 0; | ||
emit Lockdown(owner, token, spender); | ||
} | ||
} | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransfer | ||
function invalidateNonces(address token, address spender, uint48 newNonce) external { | ||
uint48 oldNonce = allowance[msg.sender][token][spender].nonce; | ||
|
||
if (newNonce <= oldNonce) revert InvalidNonce(); | ||
|
||
// Limit the amount of nonces that can be invalidated in one transaction. | ||
unchecked { | ||
uint48 delta = newNonce - oldNonce; | ||
if (delta > type(uint16).max) revert ExcessiveInvalidation(); | ||
} | ||
|
||
allowance[msg.sender][token][spender].nonce = newNonce; | ||
emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce); | ||
} | ||
|
||
/// @notice Sets the new values for amount, expiration, and nonce. | ||
/// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1. | ||
/// @dev Emits a Permit event. | ||
function _updateApproval( | ||
PermitDetails memory details, | ||
address owner, | ||
address spender | ||
) private { | ||
uint48 nonce = details.nonce; | ||
address token = details.token; | ||
uint160 amount = details.amount; | ||
uint48 expiration = details.expiration; | ||
PackedAllowance storage allowed = allowance[owner][token][spender]; | ||
|
||
if (allowed.nonce != nonce) revert InvalidNonce(); | ||
|
||
allowed.updateAll(amount, expiration, nonce); | ||
emit Permit(owner, token, spender, amount, expiration, nonce); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import { IEIP712 } from "./interfaces/IEIP712.sol"; | ||
|
||
/// @notice EIP712 helpers for permit2 | ||
/// @dev Maintains cross-chain replay protection in the event of a fork | ||
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol | ||
contract EIP712 is IEIP712 { | ||
// 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 constant _HASHED_NAME = keccak256("Permit2"); | ||
bytes32 private constant _TYPE_HASH = | ||
keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); | ||
|
||
constructor() { | ||
_CACHED_CHAIN_ID = block.chainid; | ||
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); | ||
} | ||
|
||
/// @notice Returns the domain separator for the current chain. | ||
/// @dev Uses cached version if chainid and address are unchanged from construction. | ||
function DOMAIN_SEPARATOR() public view override returns (bytes32) { | ||
return | ||
block.chainid == _CACHED_CHAIN_ID | ||
? _CACHED_DOMAIN_SEPARATOR | ||
: _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); | ||
} | ||
|
||
/// @notice Builds a domain separator using the current chainId and contract address. | ||
function _buildDomainSeparator( | ||
bytes32 typeHash, | ||
bytes32 nameHash | ||
) private view returns (bytes32) { | ||
return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this))); | ||
} | ||
|
||
/// @notice Creates an EIP-712 typed data hash | ||
function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) { | ||
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash)); | ||
} | ||
} |
Oops, something went wrong.