-
Notifications
You must be signed in to change notification settings - Fork 165
/
Permit2Vault.t.sol
408 lines (382 loc) · 31.6 KB
/
Permit2Vault.t.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import "solmate/tokens/ERC20.sol";
import "../patterns/permit2/Permit2Vault.sol";
import "./TestUtils.sol";
contract Permit2VaultTest is TestUtils {
bytes32 constant TOKEN_PERMISSIONS_TYPEHASH =
keccak256("TokenPermissions(address token,uint256 amount)");
bytes32 constant PERMIT_TRANSFER_FROM_TYPEHASH = keccak256(
"PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)"
);
bytes32 constant PERMIT_BATCH_TRANSFER_FROM_TYPEHASH = keccak256(
"PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)"
);
Permit2Clone permit2 = new Permit2Clone();
TestERC20 token1 = new TestERC20();
TestERC20 token2 = new TestERC20();
ReenteringERC20 badToken = new ReenteringERC20();
Permit2Vault vault;
uint256 ownerKey;
address owner;
constructor() {
vm.chainId(1);
vault = new Permit2Vault(permit2);
ownerKey = _randomUint256();
owner = vm.addr(ownerKey);
// Set up unlimited token approvals from the user onto the permit2 contract.
vm.prank(owner);
token1.approve(address(permit2), type(uint256).max);
vm.prank(owner);
token2.approve(address(permit2), type(uint256).max);
}
function test_canDeposit() external {
uint256 amount = _randomUint256() % 1e18 + 1;
token1.mint(owner, amount);
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: IERC20(address(token1)),
amount: amount
}),
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
vm.prank(owner);
vault.depositERC20(
IERC20(address(token1)),
amount,
permit.nonce,
permit.deadline,
sig
);
assertEq(vault.tokenBalancesByUser(owner, IERC20(address(token1))), amount);
assertEq(token1.balanceOf(address(vault)), amount);
assertEq(token1.balanceOf(owner), 0);
}
function test_cannotReusePermit() external {
uint256 amount = _randomUint256() % 1e18 + 1;
token1.mint(owner, amount);
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: IERC20(address(token1)),
amount: amount
}),
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
vm.prank(owner);
vault.depositERC20(
IERC20(address(token1)),
amount,
permit.nonce,
permit.deadline,
sig
);
vm.expectRevert(abi.encodeWithSelector(Permit2Clone.InvalidNonce.selector));
vm.prank(owner);
vault.depositERC20(
IERC20(address(token1)),
amount,
permit.nonce,
permit.deadline,
sig
);
}
function test_cannotUseOthersPermit() external {
uint256 amount = _randomUint256() % 1e18 + 1;
token1.mint(owner, amount);
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: IERC20(address(token1)),
amount: amount
}),
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
vm.expectRevert(abi.encodeWithSelector(Permit2Clone.InvalidSigner.selector));
vm.prank(_randomAddress());
vault.depositERC20(
IERC20(address(token1)),
amount,
permit.nonce,
permit.deadline,
sig
);
}
function test_cannotUseOtherTokenPermit() external {
vm.prank(owner);
token2.approve(address(permit2), type(uint256).max);
uint256 amount = _randomUint256() % 1e18 + 1;
token1.mint(owner, amount);
token2.mint(owner, amount);
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: IERC20(address(token2)),
amount: amount
}),
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
vm.prank(owner);
vm.expectRevert(Permit2Clone.InvalidSigner.selector);
vault.depositERC20(
IERC20(address(token1)),
amount,
permit.nonce,
permit.deadline,
sig
);
}
function test_canWithdraw() external {
uint256 amount = _randomUint256() % 1e18 + 2;
token1.mint(owner, amount);
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: IERC20(address(token1)),
amount: amount
}),
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
vm.prank(owner);
vault.depositERC20(
IERC20(address(token1)),
amount,
permit.nonce,
permit.deadline,
sig
);
vm.prank(owner);
vault.withdrawERC20(IERC20(address(token1)), amount - 1);
assertEq(token1.balanceOf(owner), amount - 1);
assertEq(token1.balanceOf(address(vault)), 1);
}
function test_cannotWithdrawOthers() external {
uint256 amount = _randomUint256() % 1e18 + 1;
token1.mint(owner, amount);
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: IERC20(address(token1)),
amount: amount
}),
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
vm.prank(owner);
vault.depositERC20(
IERC20(address(token1)),
amount,
permit.nonce,
permit.deadline,
sig
);
vm.expectRevert();
vm.prank(_randomAddress());
vault.withdrawERC20(IERC20(address(token1)), amount);
}
function test_cannotReenter() external {
IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: IERC20(address(badToken)),
amount: 0
}),
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
// Reenter by calling withdrawERC20() in transferFrom()
badToken.setReentrantCall(
address(vault),
abi.encodeCall(vault.withdrawERC20, (IERC20(address(badToken)), 0))
);
// Will manifest as a TRANSFER_FROM_FAILED
vm.expectRevert('TRANSFER_FROM_FAILED');
vm.prank(owner);
vault.depositERC20(
IERC20(address(badToken)),
0,
permit.nonce,
permit.deadline,
sig
);
}
function test_canBatchDeposit() external {
uint256 amount1 = _randomUint256() % 1e18 + 1;
uint256 amount2 = _randomUint256() % 1e18 + 1;
token1.mint(owner, amount1);
token2.mint(owner, amount2);
IPermit2.TokenPermissions[] memory permitted = new IPermit2.TokenPermissions[](2);
permitted[0] = IPermit2.TokenPermissions({
token: IERC20(address(token1)),
amount: amount1
});
permitted[1] = IPermit2.TokenPermissions({
token: IERC20(address(token2)),
amount: amount2
});
IPermit2.PermitBatchTransferFrom memory permit = IPermit2.PermitBatchTransferFrom({
permitted: permitted,
nonce: _randomUint256(),
deadline: block.timestamp
});
bytes memory sig = _signPermit(permit, address(vault), ownerKey);
vm.prank(owner);
{
IERC20[] memory tokens = new IERC20[](permitted.length);
uint256[] memory amounts = new uint256[](permitted.length);
for (uint256 i; i < permitted.length; ++i) {
(tokens[i], amounts[i]) = (permitted[i].token, permitted[i].amount);
}
vault.depositBatchERC20(
tokens,
amounts,
permit.nonce,
permit.deadline,
sig
);
}
assertEq(vault.tokenBalancesByUser(owner, IERC20(address(token1))), amount1);
assertEq(vault.tokenBalancesByUser(owner, IERC20(address(token2))), amount2);
assertEq(token1.balanceOf(address(vault)), amount1);
assertEq(token2.balanceOf(address(vault)), amount2);
assertEq(token1.balanceOf(owner), 0);
assertEq(token2.balanceOf(owner), 0);
}
// Generate a signature for a permit message.
function _signPermit(
IPermit2.PermitTransferFrom memory permit,
address spender,
uint256 signerKey
)
internal
view
returns (bytes memory sig)
{
(uint8 v, bytes32 r, bytes32 s) =
vm.sign(signerKey, _getEIP712Hash(permit, spender));
return abi.encodePacked(r, s, v);
}
// Generate a signature for a batch permit message.
function _signPermit(
IPermit2.PermitBatchTransferFrom memory permit,
address spender,
uint256 signerKey
)
internal
view
returns (bytes memory sig)
{
(uint8 v, bytes32 r, bytes32 s) =
vm.sign(signerKey, _getEIP712Hash(permit, spender));
return abi.encodePacked(r, s, v);
}
// Compute the EIP712 hash of the permit object.
// Normally this would be implemented off-chain.
function _getEIP712Hash(IPermit2.PermitTransferFrom memory permit, address spender)
internal
view
returns (bytes32 h)
{
return keccak256(abi.encodePacked(
"\x19\x01",
permit2.DOMAIN_SEPARATOR(),
keccak256(abi.encode(
PERMIT_TRANSFER_FROM_TYPEHASH,
keccak256(abi.encode(
TOKEN_PERMISSIONS_TYPEHASH,
permit.permitted.token,
permit.permitted.amount
)),
spender,
permit.nonce,
permit.deadline
))
));
}
// Compute the EIP712 hash of the batch permit object.
// Normally this would be implemented off-chain.
function _getEIP712Hash(IPermit2.PermitBatchTransferFrom memory permit, address spender)
internal
view
returns (bytes32 h)
{
bytes32 permittedHash;
{
uint256 n = permit.permitted.length;
bytes32[] memory contentHashes = new bytes32[](n);
for (uint256 i; i < n; ++i) {
contentHashes[i] = keccak256(abi.encode(
TOKEN_PERMISSIONS_TYPEHASH,
permit.permitted[i].token,
permit.permitted[i].amount
));
}
permittedHash = keccak256(abi.encodePacked(contentHashes));
}
return keccak256(abi.encodePacked(
"\x19\x01",
permit2.DOMAIN_SEPARATOR(),
keccak256(abi.encode(
PERMIT_BATCH_TRANSFER_FROM_TYPEHASH,
permittedHash,
spender,
permit.nonce,
permit.deadline
))
));
}
}
contract TestERC20 is ERC20 {
constructor() ERC20("Test", "TST", 18) {}
function mint(address owner, uint256 amount) external {
_mint(owner, amount);
}
}
contract ReenteringERC20 {
address _reentrantCallTarget;
bytes _reentrantCallData;
function setReentrantCall(address target, bytes calldata callData)
external
{
_reentrantCallTarget = target;
_reentrantCallData = callData;
}
function transferFrom(address, address, uint256) external returns (bool) {
(bool s, bytes memory r) = _reentrantCallTarget.call(_reentrantCallData);
if (!s) {
assembly { revert(add(r, 0x20), mload(r)) }
}
return true;
}
}
// Local bytecode clone of the canonical Permit2 contract deployed to mainnet.
contract Permit2Clone is IPermit2 {
error InvalidNonce();
error InvalidSigner();
constructor() {
// Deployed Permit2 bytecode at
// https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3
bytes memory bytecode = hex"6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000103611b69577f866a5aba21966af95d6c7ab78eb2b2fc913915c28be3b9aa07cc04ff903e3f2890565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a";
assembly { return(add(bytecode, 0x20), mload(bytecode)) }
}
///// STUBS /////
function DOMAIN_SEPARATOR() external view returns (bytes32) {}
function permitTransferFrom(
PermitTransferFrom calldata permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes calldata signature
) external {}
function permitTransferFrom(
PermitBatchTransferFrom calldata permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes calldata signature
) external {}
}