Skip to content

Commit 378acad

Browse files
committed
demo: add permit2 flow
1 parent 9e8e460 commit 378acad

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

src/IEIP712.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
interface IEIP712 {
5+
function DOMAIN_SEPARATOR() external view returns (bytes32);
6+
}

src/ISignatureTransfer.sol

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {IEIP712} from "./IEIP712.sol";
5+
6+
/// @title SignatureTransfer
7+
/// @notice Handles ERC20 token transfers through signature based actions
8+
/// @dev Requires user's token approval on the Permit2 contract
9+
interface ISignatureTransfer is IEIP712 {
10+
/// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount
11+
/// @param maxAmount The maximum amount a spender can request to transfer
12+
error InvalidAmount(uint256 maxAmount);
13+
14+
/// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred
15+
/// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred
16+
error LengthMismatch();
17+
18+
/// @notice Emits an event when the owner successfully invalidates an unordered nonce.
19+
event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);
20+
21+
/// @notice The token and amount details for a transfer signed in the permit transfer signature
22+
struct TokenPermissions {
23+
// ERC20 token address
24+
address token;
25+
// the maximum amount that can be spent
26+
uint256 amount;
27+
}
28+
29+
/// @notice The signed permit message for a single token transfer
30+
struct PermitTransferFrom {
31+
TokenPermissions permitted;
32+
// a unique value for every token owner's signature to prevent signature replays
33+
uint256 nonce;
34+
// deadline on the permit signature
35+
uint256 deadline;
36+
}
37+
38+
/// @notice Specifies the recipient address and amount for batched transfers.
39+
/// @dev Recipients and amounts correspond to the index of the signed token permissions array.
40+
/// @dev Reverts if the requested amount is greater than the permitted signed amount.
41+
struct SignatureTransferDetails {
42+
// recipient address
43+
address to;
44+
// spender requested amount
45+
uint256 requestedAmount;
46+
}
47+
48+
/// @notice Used to reconstruct the signed permit message for multiple token transfers
49+
/// @dev Do not need to pass in spender address as it is required that it is msg.sender
50+
/// @dev Note that a user still signs over a spender address
51+
struct PermitBatchTransferFrom {
52+
// the tokens and corresponding amounts permitted for a transfer
53+
TokenPermissions[] permitted;
54+
// a unique value for every token owner's signature to prevent signature replays
55+
uint256 nonce;
56+
// deadline on the permit signature
57+
uint256 deadline;
58+
}
59+
60+
/// @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
61+
/// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order
62+
/// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce
63+
/// @dev It returns a uint256 bitmap
64+
/// @dev The index, or wordPosition is capped at type(uint248).max
65+
function nonceBitmap(address, uint256) external view returns (uint256);
66+
67+
/// @notice Transfers a token using a signed permit message
68+
/// @dev Reverts if the requested amount is greater than the permitted signed amount
69+
/// @param permit The permit data signed over by the owner
70+
/// @param owner The owner of the tokens to transfer
71+
/// @param transferDetails The spender's requested transfer details for the permitted token
72+
/// @param signature The signature to verify
73+
function permitTransferFrom(
74+
PermitTransferFrom memory permit,
75+
SignatureTransferDetails calldata transferDetails,
76+
address owner,
77+
bytes calldata signature
78+
) external;
79+
80+
/// @notice Transfers a token using a signed permit message
81+
/// @notice Includes extra data provided by the caller to verify signature over
82+
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
83+
/// @dev Reverts if the requested amount is greater than the permitted signed amount
84+
/// @param permit The permit data signed over by the owner
85+
/// @param owner The owner of the tokens to transfer
86+
/// @param transferDetails The spender's requested transfer details for the permitted token
87+
/// @param witness Extra data to include when checking the user signature
88+
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
89+
/// @param signature The signature to verify
90+
function permitWitnessTransferFrom(
91+
PermitTransferFrom memory permit,
92+
SignatureTransferDetails calldata transferDetails,
93+
address owner,
94+
bytes32 witness,
95+
string calldata witnessTypeString,
96+
bytes calldata signature
97+
) external;
98+
99+
/// @notice Transfers multiple tokens using a signed permit message
100+
/// @param permit The permit data signed over by the owner
101+
/// @param owner The owner of the tokens to transfer
102+
/// @param transferDetails Specifies the recipient and requested amount for the token transfer
103+
/// @param signature The signature to verify
104+
function permitTransferFrom(
105+
PermitBatchTransferFrom memory permit,
106+
SignatureTransferDetails[] calldata transferDetails,
107+
address owner,
108+
bytes calldata signature
109+
) external;
110+
111+
/// @notice Transfers multiple tokens using a signed permit message
112+
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
113+
/// @notice Includes extra data provided by the caller to verify signature over
114+
/// @param permit The permit data signed over by the owner
115+
/// @param owner The owner of the tokens to transfer
116+
/// @param transferDetails Specifies the recipient and requested amount for the token transfer
117+
/// @param witness Extra data to include when checking the user signature
118+
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
119+
/// @param signature The signature to verify
120+
function permitWitnessTransferFrom(
121+
PermitBatchTransferFrom memory permit,
122+
SignatureTransferDetails[] calldata transferDetails,
123+
address owner,
124+
bytes32 witness,
125+
string calldata witnessTypeString,
126+
bytes calldata signature
127+
) external;
128+
129+
/// @notice Invalidates the bits specified in mask for the bitmap at the word position
130+
/// @dev The wordPos is maxed at type(uint248).max
131+
/// @param wordPos A number to index the nonceBitmap at
132+
/// @param mask A bitmap masked against msg.sender's current bitmap at the word position
133+
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;
134+
}

src/Orders.sol

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
pragma solidity ^0.8.24;
33

44
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
5+
import {ISignatureTransfer} from "./ISignatureTransfer.sol";
6+
7+
address constant permit2 = address(0);
58

69
/// @notice Tokens sent by the swapper as inputs to the order
710
/// @dev From ERC-7683
@@ -54,6 +57,36 @@ abstract contract OrderDestination {
5457
// emit
5558
emit Filled(outputs);
5659
}
60+
61+
function fillPermit2(
62+
uint32[] memory chainIds,
63+
ISignatureTransfer.PermitBatchTransferFrom memory permit,
64+
ISignatureTransfer.SignatureTransferDetails[] memory transferDetails,
65+
bytes calldata signature
66+
) external {
67+
// generate Input structs from the TransferDetails
68+
Output[] memory outputs = _generateOutputs(chainIds, permit.permitted, transferDetails);
69+
70+
// via permit2 contract, transfer all tokens to this contract && check the permit deadline
71+
ISignatureTransfer(permit2).permitTransferFrom(permit, transferDetails, msg.sender, signature);
72+
73+
// emit
74+
emit Filled(outputs);
75+
}
76+
77+
function _generateOutputs(
78+
uint32[] memory chainIds,
79+
ISignatureTransfer.TokenPermissions[] memory permitted,
80+
ISignatureTransfer.SignatureTransferDetails[] memory transferDetails
81+
) internal view returns (Output[] memory outputs) {
82+
require(chainIds.length == transferDetails.length);
83+
outputs = new Output[](transferDetails.length);
84+
// generate an Input for each permitted transfer
85+
for (uint256 i; i < transferDetails.length; i++) {
86+
outputs[i] =
87+
Output(permitted[i].token, transferDetails[i].requestedAmount, transferDetails[i].to, chainIds[i]);
88+
}
89+
}
5790
}
5891

5992
/// @notice Contract capable of registering initiation of intent-based Orders.
@@ -110,6 +143,36 @@ abstract contract OrderOrigin {
110143
}
111144
}
112145

146+
function initiatePermit2(
147+
Output[] memory outputs,
148+
ISignatureTransfer.PermitBatchTransferFrom memory permit,
149+
ISignatureTransfer.SignatureTransferDetails[] memory transferDetails,
150+
bytes calldata signature
151+
) external {
152+
// generate Input structs from the TransferDetails
153+
Input[] memory inputs = _generateInputs(permit.permitted, transferDetails);
154+
155+
// via permit2 contract, transfer all tokens to this contract && check the permit deadline
156+
ISignatureTransfer(permit2).permitTransferFrom(permit, transferDetails, msg.sender, signature);
157+
158+
// emit
159+
emit Order(permit.deadline, inputs, outputs);
160+
}
161+
162+
function _generateInputs(
163+
ISignatureTransfer.TokenPermissions[] memory permitted,
164+
ISignatureTransfer.SignatureTransferDetails[] memory transferDetails
165+
) internal view returns (Input[] memory inputs) {
166+
inputs = new Input[](transferDetails.length);
167+
// generate an Input for each permitted transfer
168+
for (uint256 i; i < transferDetails.length; i++) {
169+
// ensure the tokens are being transferred to this address
170+
require(transferDetails[i].to == address(this));
171+
// construct an Input
172+
inputs[i] = Input(permitted[i].token, transferDetails[i].requestedAmount);
173+
}
174+
}
175+
113176
/// @notice Transfer the entire balance of ERC20 tokens to the recipient.
114177
/// @dev Called by the Builder within the same block as users' `initiate` transactions
115178
/// to claim the `inputs`.

0 commit comments

Comments
 (0)