-
Notifications
You must be signed in to change notification settings - Fork 157
/
SignatureTransfer.sol
222 lines (194 loc) Β· 8.81 KB
/
SignatureTransfer.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {SignatureVerification} from "./libraries/SignatureVerification.sol";
import {
PermitTransfer,
PermitBatchTransfer,
InvalidNonce,
LengthMismatch,
NotSpender,
InvalidAmount,
SignatureExpired,
SignedDetailsLengthMismatch,
AmountsLengthMismatch,
RecipientLengthMismatch
} from "./Permit2Utils.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {DomainSeparator} from "./DomainSeparator.sol";
contract SignatureTransfer is DomainSeparator {
using SignatureVerification for bytes;
using SafeTransferLib for ERC20;
bytes32 public constant _PERMIT_TRANSFER_TYPEHASH =
keccak256("PermitTransferFrom(address token,address spender,uint256 maxAmount,uint256 nonce,uint256 deadline)");
bytes32 public constant _PERMIT_BATCH_TRANSFER_TYPEHASH = keccak256(
"PermitBatchTransferFrom(address[] tokens,address spender,uint256[] maxAmounts,uint256 nonce,uint256 deadline)"
);
string public constant _PERMIT_TRANSFER_WITNESS_TYPEHASH_STUB =
"PermitWitnessTransferFrom(address token,address spender,uint256 maxAmount,uint256 nonce,uint256 deadline,";
event InvalidateUnorderedNonces(address indexed owner, uint256 word, uint256 mask);
mapping(address => mapping(uint256 => uint256)) public nonceBitmap;
/// @notice Transfers a token using a signed permit message.
/// @dev If to is the zero address, the tokens are sent to the spender.
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param to The recipient of the tokens
/// @param requestedAmount The amount of tokens to transfer
/// @param signature The signature to verify
function permitTransferFrom(
PermitTransfer calldata permit,
address owner,
address to,
uint256 requestedAmount,
bytes calldata signature
) external {
bytes32 dataHash = keccak256(
abi.encode(
_PERMIT_TRANSFER_TYPEHASH,
permit.token,
permit.spender,
permit.signedAmount,
permit.nonce,
permit.deadline
)
);
_permitTransferFrom(permit, dataHash, owner, to, requestedAmount, signature);
}
/// @notice Transfers a token using a signed permit message.
/// @notice Includes extra data provided by the caller to verify signature over.
/// @dev If to is the zero address, the tokens are sent to the spender.
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param to The recipient of the tokens
/// @param requestedAmount The amount of tokens to transfer
/// @param witness Extra data to include when checking the user signature
/// @param witnessTypeName The name of the witness type
/// @param witnessType The EIP-712 type definition for the witness type
/// @param signature The signature to verify
function permitWitnessTransferFrom(
PermitTransfer calldata permit,
address owner,
address to,
uint256 requestedAmount,
bytes32 witness,
string calldata witnessTypeName,
string calldata witnessType,
bytes calldata signature
) public {
bytes32 typeHash = keccak256(
abi.encodePacked(_PERMIT_TRANSFER_WITNESS_TYPEHASH_STUB, witnessTypeName, " witness)", witnessType)
);
bytes32 dataHash = keccak256(
abi.encode(
typeHash, permit.token, permit.spender, permit.signedAmount, permit.nonce, permit.deadline, witness
)
);
_permitTransferFrom(permit, dataHash, owner, to, requestedAmount, signature);
}
/// @notice Transfers a token using a signed permit message.
/// @dev If to is the zero address, the tokens are sent to the spender.
/// @param permit The permit data signed over by the owner
/// @param dataHash The EIP-712 hash of permit data to include when checking signature
/// @param owner The owner of the tokens to transfer
/// @param to The recipient of the tokens
/// @param requestedAmount The amount of tokens to transfer
/// @param signature The signature to verify
function _permitTransferFrom(
PermitTransfer calldata permit,
bytes32 dataHash,
address owner,
address to,
uint256 requestedAmount,
bytes calldata signature
) internal {
_validatePermit(permit.spender, permit.deadline);
if (requestedAmount > permit.signedAmount) {
revert InvalidAmount();
}
_useUnorderedNonce(owner, permit.nonce);
signature.verify(keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash)), owner);
// send to spender if the inputted to address is 0
address recipient = to == address(0) ? permit.spender : to;
ERC20(permit.token).safeTransferFrom(owner, recipient, requestedAmount);
}
/// @notice Transfers tokens using a signed permit message.
/// @dev If to is the zero address, the tokens are sent to the spender.
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param to The recipients of the tokens
/// @param requestedAmounts The amount of tokens to transfer
/// @param signature The signature to verify
function permitBatchTransferFrom(
PermitBatchTransfer calldata permit,
address owner,
address[] calldata to,
uint256[] calldata requestedAmounts,
bytes calldata signature
) external {
_validatePermit(permit.spender, permit.deadline);
_validateInputLengths(permit.tokens.length, to.length, permit.signedAmounts.length, requestedAmounts.length);
unchecked {
for (uint256 i = 0; i < permit.tokens.length; ++i) {
if (requestedAmounts[i] > permit.signedAmounts[i]) revert InvalidAmount();
}
}
_useUnorderedNonce(owner, permit.nonce);
signature.verify(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
_PERMIT_BATCH_TRANSFER_TYPEHASH,
keccak256(abi.encodePacked(permit.tokens)),
permit.spender,
keccak256(abi.encodePacked(permit.signedAmounts)),
permit.nonce,
permit.deadline
)
)
)
),
owner
);
unchecked {
for (uint256 i = 0; i < permit.tokens.length; ++i) {
ERC20(permit.tokens[i]).safeTransferFrom(owner, to[i], requestedAmounts[i]);
}
}
}
/// @notice Returns the index of the bitmap and the bit position within the bitmap. Used for unordered nonces.
/// @dev The first 248 bits of the nonce value is the index of the desired bitmap.
/// The last 8 bits of the nonce value is the position of the bit in the bitmap.
function bitmapPositions(uint256 nonce) public pure returns (uint248 wordPos, uint8 bitPos) {
wordPos = uint248(nonce >> 8);
bitPos = uint8(nonce & 255);
}
/// @notice Invalidates the bits specified in `mask` for the bitmap at `wordPos`.
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external {
nonceBitmap[msg.sender][wordPos] |= mask;
emit InvalidateUnorderedNonces(msg.sender, wordPos, mask);
}
/// @notice Checks whether a nonce is taken. Then sets the bit at the bitPos in the bitmap at the wordPos.
function _useUnorderedNonce(address from, uint256 nonce) private {
(uint248 wordPos, uint8 bitPos) = bitmapPositions(nonce);
uint256 bitmap = nonceBitmap[from][wordPos];
if ((bitmap >> bitPos) & 1 == 1) revert InvalidNonce();
nonceBitmap[from][wordPos] = bitmap | (1 << bitPos);
}
function _validatePermit(address spender, uint256 deadline) private view {
if (msg.sender != spender) revert NotSpender();
if (block.timestamp > deadline) revert SignatureExpired();
}
function _validateInputLengths(
uint256 signedTokensLen,
uint256 recipientLen,
uint256 signedAmountsLen,
uint256 requestedAmountsLen
) private pure {
if (signedAmountsLen != signedTokensLen) revert SignedDetailsLengthMismatch();
if (requestedAmountsLen != signedAmountsLen) revert AmountsLengthMismatch();
if (recipientLen != 1 && recipientLen != signedTokensLen) revert RecipientLengthMismatch();
}
}