-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
524 additions
and
11 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
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,134 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.27; | ||
|
||
import {IEIP712} from "./IEIP712.sol"; | ||
|
||
/// @title SignatureTransfer | ||
/// @notice Handles ERC20 token transfers through signature based actions | ||
/// @dev Requires user's token approval on the Permit2 contract | ||
interface ISignatureTransfer is IEIP712 { | ||
/// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount | ||
/// @param maxAmount The maximum amount a spender can request to transfer | ||
error InvalidAmount(uint256 maxAmount); | ||
|
||
/// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred | ||
/// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred | ||
error LengthMismatch(); | ||
|
||
/// @notice Emits an event when the owner successfully invalidates an unordered nonce. | ||
event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask); | ||
|
||
/// @notice The token and amount details for a transfer signed in the permit transfer signature | ||
struct TokenPermissions { | ||
// ERC20 token address | ||
address token; | ||
// the maximum amount that can be spent | ||
uint256 amount; | ||
} | ||
|
||
/// @notice The signed permit message for a single token transfer | ||
struct PermitTransferFrom { | ||
TokenPermissions permitted; | ||
// a unique value for every token owner's signature to prevent signature replays | ||
uint256 nonce; | ||
// deadline on the permit signature | ||
uint256 deadline; | ||
} | ||
|
||
/// @notice Specifies the recipient address and amount for batched transfers. | ||
/// @dev Recipients and amounts correspond to the index of the signed token permissions array. | ||
/// @dev Reverts if the requested amount is greater than the permitted signed amount. | ||
struct SignatureTransferDetails { | ||
// recipient address | ||
address to; | ||
// spender requested amount | ||
uint256 requestedAmount; | ||
} | ||
|
||
/// @notice Used to reconstruct the signed permit message for multiple token transfers | ||
/// @dev Do not need to pass in spender address as it is required that it is msg.sender | ||
/// @dev Note that a user still signs over a spender address | ||
struct PermitBatchTransferFrom { | ||
// the tokens and corresponding amounts permitted for a transfer | ||
TokenPermissions[] permitted; | ||
// a unique value for every token owner's signature to prevent signature replays | ||
uint256 nonce; | ||
// deadline on the permit signature | ||
uint256 deadline; | ||
} | ||
|
||
/// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection | ||
/// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order | ||
/// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce | ||
/// @dev It returns a uint256 bitmap | ||
/// @dev The index, or wordPosition is capped at type(uint248).max | ||
function nonceBitmap(address, uint256) external view returns (uint256); | ||
|
||
/// @notice Transfers a token using a signed permit message | ||
/// @dev Reverts if the requested amount is greater than the permitted signed amount | ||
/// @param permit The permit data signed over by the owner | ||
/// @param owner The owner of the tokens to transfer | ||
/// @param transferDetails The spender's requested transfer details for the permitted token | ||
/// @param signature The signature to verify | ||
function permitTransferFrom( | ||
PermitTransferFrom memory permit, | ||
SignatureTransferDetails calldata transferDetails, | ||
address owner, | ||
bytes calldata signature | ||
) external; | ||
|
||
/// @notice Transfers a token using a signed permit message | ||
/// @notice Includes extra data provided by the caller to verify signature over | ||
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition | ||
/// @dev Reverts if the requested amount is greater than the permitted signed amount | ||
/// @param permit The permit data signed over by the owner | ||
/// @param owner The owner of the tokens to transfer | ||
/// @param transferDetails The spender's requested transfer details for the permitted token | ||
/// @param witness Extra data to include when checking the user signature | ||
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash | ||
/// @param signature The signature to verify | ||
function permitWitnessTransferFrom( | ||
PermitTransferFrom memory permit, | ||
SignatureTransferDetails calldata transferDetails, | ||
address owner, | ||
bytes32 witness, | ||
string calldata witnessTypeString, | ||
bytes calldata signature | ||
) external; | ||
|
||
/// @notice Transfers multiple tokens using a signed permit message | ||
/// @param permit The permit data signed over by the owner | ||
/// @param owner The owner of the tokens to transfer | ||
/// @param transferDetails Specifies the recipient and requested amount for the token transfer | ||
/// @param signature The signature to verify | ||
function permitTransferFrom( | ||
PermitBatchTransferFrom memory permit, | ||
SignatureTransferDetails[] calldata transferDetails, | ||
address owner, | ||
bytes calldata signature | ||
) external; | ||
|
||
/// @notice Transfers multiple tokens using a signed permit message | ||
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition | ||
/// @notice Includes extra data provided by the caller to verify signature over | ||
/// @param permit The permit data signed over by the owner | ||
/// @param owner The owner of the tokens to transfer | ||
/// @param transferDetails Specifies the recipient and requested amount for the token transfer | ||
/// @param witness Extra data to include when checking the user signature | ||
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash | ||
/// @param signature The signature to verify | ||
function permitWitnessTransferFrom( | ||
PermitBatchTransferFrom memory permit, | ||
SignatureTransferDetails[] calldata transferDetails, | ||
address owner, | ||
bytes32 witness, | ||
string calldata witnessTypeString, | ||
bytes calldata signature | ||
) external; | ||
|
||
/// @notice Invalidates the bits specified in mask for the bitmap at the word position | ||
/// @dev The wordPos is maxed at type(uint248).max | ||
/// @param wordPos A number to index the nonceBitmap at | ||
/// @param mask A bitmap masked against msg.sender's current bitmap at the word position | ||
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external; | ||
} |
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,134 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.27; | ||
|
||
import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; | ||
import {ISignatureTransfer} from "../interfaces/ISignatureTransfer.sol"; | ||
|
||
library PermitHash { | ||
bytes32 public constant _PERMIT_DETAILS_TYPEHASH = | ||
keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"); | ||
|
||
bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256( | ||
"PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" | ||
); | ||
|
||
bytes32 public constant _PERMIT_BATCH_TYPEHASH = keccak256( | ||
"PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" | ||
); | ||
|
||
bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)"); | ||
|
||
bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = keccak256( | ||
"PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" | ||
); | ||
|
||
bytes32 public constant _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH = keccak256( | ||
"PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" | ||
); | ||
|
||
string public constant _TOKEN_PERMISSIONS_TYPESTRING = "TokenPermissions(address token,uint256 amount)"; | ||
|
||
string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB = | ||
"PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,"; | ||
|
||
string public constant _PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB = | ||
"PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,"; | ||
|
||
function hash(IAllowanceTransfer.PermitSingle memory permitSingle) internal pure returns (bytes32) { | ||
bytes32 permitHash = _hashPermitDetails(permitSingle.details); | ||
return | ||
keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)); | ||
} | ||
|
||
function hash(IAllowanceTransfer.PermitBatch memory permitBatch) internal pure returns (bytes32) { | ||
uint256 numPermits = permitBatch.details.length; | ||
bytes32[] memory permitHashes = new bytes32[](numPermits); | ||
for (uint256 i = 0; i < numPermits; ++i) { | ||
permitHashes[i] = _hashPermitDetails(permitBatch.details[i]); | ||
} | ||
return keccak256( | ||
abi.encode( | ||
_PERMIT_BATCH_TYPEHASH, | ||
keccak256(abi.encodePacked(permitHashes)), | ||
permitBatch.spender, | ||
permitBatch.sigDeadline | ||
) | ||
); | ||
} | ||
|
||
function hash(ISignatureTransfer.PermitTransferFrom memory permit) internal view returns (bytes32) { | ||
bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted); | ||
return keccak256( | ||
abi.encode(_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline) | ||
); | ||
} | ||
|
||
function hash(ISignatureTransfer.PermitBatchTransferFrom memory permit) internal view returns (bytes32) { | ||
uint256 numPermitted = permit.permitted.length; | ||
bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted); | ||
|
||
for (uint256 i = 0; i < numPermitted; ++i) { | ||
tokenPermissionHashes[i] = _hashTokenPermissions(permit.permitted[i]); | ||
} | ||
|
||
return keccak256( | ||
abi.encode( | ||
_PERMIT_BATCH_TRANSFER_FROM_TYPEHASH, | ||
keccak256(abi.encodePacked(tokenPermissionHashes)), | ||
msg.sender, | ||
permit.nonce, | ||
permit.deadline | ||
) | ||
); | ||
} | ||
|
||
function hashWithWitness( | ||
ISignatureTransfer.PermitTransferFrom memory permit, | ||
bytes32 witness, | ||
string calldata witnessTypeString | ||
) internal view returns (bytes32) { | ||
bytes32 typeHash = keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString)); | ||
|
||
bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted); | ||
return keccak256(abi.encode(typeHash, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline, witness)); | ||
} | ||
|
||
function hashWithWitness( | ||
ISignatureTransfer.PermitBatchTransferFrom memory permit, | ||
bytes32 witness, | ||
string calldata witnessTypeString | ||
) internal view returns (bytes32) { | ||
bytes32 typeHash = | ||
keccak256(abi.encodePacked(_PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB, witnessTypeString)); | ||
|
||
uint256 numPermitted = permit.permitted.length; | ||
bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted); | ||
|
||
for (uint256 i = 0; i < numPermitted; ++i) { | ||
tokenPermissionHashes[i] = _hashTokenPermissions(permit.permitted[i]); | ||
} | ||
|
||
return keccak256( | ||
abi.encode( | ||
typeHash, | ||
keccak256(abi.encodePacked(tokenPermissionHashes)), | ||
msg.sender, | ||
permit.nonce, | ||
permit.deadline, | ||
witness | ||
) | ||
); | ||
} | ||
|
||
function _hashPermitDetails(IAllowanceTransfer.PermitDetails memory details) private pure returns (bytes32) { | ||
return keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, details)); | ||
} | ||
|
||
function _hashTokenPermissions(ISignatureTransfer.TokenPermissions memory permitted) | ||
private | ||
pure | ||
returns (bytes32) | ||
{ | ||
return keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permitted)); | ||
} | ||
} |
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,47 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.27; | ||
|
||
import "../../@openzeppelin/contracts/interfaces/IERC1271.sol"; | ||
|
||
library SignatureVerification { | ||
/// @notice Thrown when the passed in signature is not a valid length | ||
error InvalidSignatureLength(); | ||
|
||
/// @notice Thrown when the recovered signer is equal to the zero address | ||
error InvalidSignature(); | ||
|
||
/// @notice Thrown when the recovered signer does not equal the claimedSigner | ||
error InvalidSigner(); | ||
|
||
/// @notice Thrown when the recovered contract signature is incorrect | ||
error InvalidContractSignature(); | ||
|
||
bytes32 constant UPPER_BIT_MASK = (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); | ||
|
||
function verify(bytes calldata signature, bytes32 hash, address claimedSigner) internal view { | ||
bytes32 r; | ||
bytes32 s; | ||
uint8 v; | ||
|
||
if (claimedSigner.code.length == 0) { | ||
if (signature.length == 65) { | ||
(r, s) = abi.decode(signature, (bytes32, bytes32)); | ||
v = uint8(signature[64]); | ||
} else if (signature.length == 64) { | ||
// EIP-2098 | ||
bytes32 vs; | ||
(r, vs) = abi.decode(signature, (bytes32, bytes32)); | ||
s = vs & UPPER_BIT_MASK; | ||
v = uint8(uint256(vs >> 255)) + 27; | ||
} else { | ||
revert InvalidSignatureLength(); | ||
} | ||
address signer = ecrecover(hash, v, r, s); | ||
if (signer == address(0)) revert InvalidSignature(); | ||
if (signer != claimedSigner) revert InvalidSigner(); | ||
} else { | ||
bytes4 magicValue = IERC1271(claimedSigner).isValidSignature(hash, signature); | ||
if (magicValue != IERC1271.isValidSignature.selector) revert InvalidContractSignature(); | ||
} | ||
} | ||
} |
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,39 @@ | ||
// SPDX-FileCopyrightText: 2024 P2P Validator <[email protected]> | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.27; | ||
|
||
import "../@permit2/interfaces/IAllowanceTransfer.sol"; | ||
|
||
interface IMorphoEthereumBundlerV2 { | ||
/// @notice Approves the given `amount` of `asset` from the initiator to be spent by `permitSingle.spender` via | ||
/// Permit2 with the given `deadline` & EIP-712 `signature`. | ||
/// @param permitSingle The `PermitSingle` struct. | ||
/// @param signature The signature, serialized. | ||
/// @param skipRevert Whether to avoid reverting the call in case the signature is frontrunned. | ||
function approve2(IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature, bool skipRevert) | ||
external | ||
payable; | ||
|
||
/// @notice Transfers the given `amount` of `asset` from the initiator to the bundler via Permit2. | ||
/// @param asset The address of the ERC20 token to transfer. | ||
/// @param amount The amount of `asset` to transfer from the initiator. Capped at the initiator's balance. | ||
function transferFrom2(address asset, uint256 amount) external payable; | ||
|
||
/// @notice Deposits the given amount of `assets` on the given ERC4626 `vault`, on behalf of `receiver`. | ||
/// @dev Initiator must have previously transferred their assets to the bundler. | ||
/// @dev Assumes the given `vault` implements EIP-4626. | ||
/// @param vault The address of the vault. | ||
/// @param assets The amount of assets to deposit. Capped at the bundler's assets. | ||
/// @param minShares The minimum amount of shares to mint in exchange for `assets`. This parameter is proportionally | ||
/// scaled down in case there are fewer assets than `assets` on the bundler. | ||
/// @param receiver The address to which shares will be minted. | ||
function erc4626Deposit(address vault, uint256 assets, uint256 minShares, address receiver) | ||
external | ||
payable; | ||
|
||
/// @notice Executes a series of delegate calls to the contract itself. | ||
/// @dev Locks the initiator so that the sender can uniquely be identified in callbacks. | ||
/// @dev All functions delegatecalled must be `payable` if `msg.value` is non-zero. | ||
function multicall(bytes[] memory data) external payable; | ||
} |
Oops, something went wrong.