-
Notifications
You must be signed in to change notification settings - Fork 157
/
SignatureTransfer.sol
198 lines (174 loc) Β· 7.99 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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {ISignatureTransfer} from "./interfaces/ISignatureTransfer.sol";
import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {SignatureVerification} from "./libraries/SignatureVerification.sol";
import {PermitHash} from "./libraries/PermitHash.sol";
import {EIP712} from "./EIP712.sol";
contract SignatureTransfer is ISignatureTransfer, EIP712 {
using SignatureVerification for bytes;
using SafeTransferLib for ERC20;
using PermitHash for PermitTransfer;
using PermitHash for PermitBatchTransfer;
event InvalidateUnorderedNonces(address indexed owner, uint256 word, uint256 mask);
/// @inheritdoc ISignatureTransfer
mapping(address => mapping(uint256 => uint256)) public nonceBitmap;
/// @inheritdoc ISignatureTransfer
function permitTransferFrom(
PermitTransfer calldata permit,
address owner,
address to,
uint256 requestedAmount,
bytes calldata signature
) external {
_permitTransferFrom(permit, permit.hash(), owner, to, requestedAmount, signature);
}
/// @inheritdoc ISignatureTransfer
function permitWitnessTransferFrom(
PermitTransfer calldata permit,
address owner,
address to,
uint256 requestedAmount,
bytes32 witness,
string calldata witnessTypeName,
string calldata witnessType,
bytes calldata signature
) external {
_permitTransferFrom(
permit, permit.hashWithWitness(witness, witnessTypeName, witnessType), 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(_hashTypedData(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);
}
/// @inheritdoc ISignatureTransfer
function permitBatchTransferFrom(
PermitBatchTransfer calldata permit,
address owner,
address[] calldata to,
uint256[] calldata requestedAmounts,
bytes calldata signature
) external {
_permitBatchTransferFrom(permit, permit.hash(), owner, to, requestedAmounts, signature);
}
/// @inheritdoc ISignatureTransfer
function permitBatchWitnessTransferFrom(
PermitBatchTransfer calldata permit,
address owner,
address[] calldata to,
uint256[] calldata requestedAmounts,
bytes32 witness,
string calldata witnessTypeName,
string calldata witnessType,
bytes calldata signature
) external {
_permitBatchTransferFrom(
permit,
permit.hashWithWitness(witness, witnessTypeName, witnessType),
owner,
to,
requestedAmounts,
signature
);
}
/// @notice Transfers tokens using a signed permit messages
/// @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 An array of the recipients of the tokens
/// @param requestedAmounts An array with the amount of each token to transfer
/// @param signature The signature to verify
function _permitBatchTransferFrom(
PermitBatchTransfer calldata permit,
bytes32 dataHash,
address owner,
address[] calldata to,
uint256[] calldata requestedAmounts,
bytes calldata signature
) internal {
_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(_hashTypedData(dataHash), owner);
unchecked {
for (uint256 i = 0; i < permit.tokens.length; ++i) {
ERC20(permit.tokens[i]).safeTransferFrom(owner, to[i], requestedAmounts[i]);
}
}
}
/// @inheritdoc ISignatureTransfer
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external {
nonceBitmap[msg.sender][wordPos] |= mask;
emit InvalidateUnorderedNonces(msg.sender, wordPos, mask);
}
/// @notice Returns the index of the bitmap and the bit position within the bitmap. Used for unordered nonces
/// @param nonce The nonce to get the associated word and bit positions
/// @return wordPos The word position or index into the nonceBitmap
/// @return bitPos The bit position
/// @dev The first 248 bits of the nonce value is the index of the desired bitmap
/// @dev The last 8 bits of the nonce value is the position of the bit in the bitmap
function bitmapPositions(uint256 nonce) private pure returns (uint248 wordPos, uint8 bitPos) {
wordPos = uint248(nonce >> 8);
bitPos = uint8(nonce & 255);
}
/// @notice Checks whether a nonce is taken and sets the bit at the bit position in the bitmap at the word position
/// @param from The address to use the nonce at
/// @param nonce The nonce to spend
function _useUnorderedNonce(address from, uint256 nonce) internal {
(uint248 wordPos, uint8 bitPos) = bitmapPositions(nonce);
uint256 bitmap = nonceBitmap[from][wordPos];
if ((bitmap >> bitPos) & 1 == 1) revert InvalidNonce();
nonceBitmap[from][wordPos] = bitmap | (1 << bitPos);
}
/// @notice Ensures that the caller is the signed permit spender and the deadline has not passed
/// @param spender The expected spender
/// @param deadline The user-provided deadline of the signed permit
function _validatePermit(address spender, uint256 deadline) private view {
if (msg.sender != spender) revert NotSpender();
if (block.timestamp > deadline) revert SignatureExpired();
}
/// @notice Ensures that permit token arrays are valid with regard to the tokens being spent
/// @param signedTokensLen The length of the tokens array signed by the user
/// @param recipientLen The length of the given recipients array
/// @param signedAmountsLen The length of the amounts length signed by the user
/// @param requestedAmountsLen The length of the given amounts array
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();
}
}