Skip to content

Commit

Permalink
start testing deposit
Browse files Browse the repository at this point in the history
  • Loading branch information
sanbir committed Dec 10, 2024
1 parent fc98f0f commit ae51395
Show file tree
Hide file tree
Showing 8 changed files with 524 additions and 11 deletions.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ via_ir = true
optimizer = true
optimizer-runs = 2000

rpc_endpoints = { mainnet = "https://rpc.ankr.com/eth", holesky = "https://rpc.ankr.com/eth_holesky" }
rpc_endpoints = { mainnet = "https://eth.drpc.org", holesky = "https://rpc.ankr.com/eth_holesky" }
134 changes: 134 additions & 0 deletions src/@permit2/interfaces/ISignatureTransfer.sol
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;
}
134 changes: 134 additions & 0 deletions src/@permit2/libraries/PermitHash.sol
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));
}
}
47 changes: 47 additions & 0 deletions src/@permit2/libraries/SignatureVerification.sol
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();
}
}
}
39 changes: 39 additions & 0 deletions src/mocks/IMorphoEthereumBundlerV2.sol
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;
}
Loading

0 comments on commit ae51395

Please sign in to comment.