@@ -7,6 +7,8 @@ import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IER
7
7
import { Address } from "@openzeppelin/contracts/utils/Address.sol " ;
8
8
import { MultiCaller } from "@uma/core/contracts/common/implementation/MultiCaller.sol " ;
9
9
import { Lockable } from "./Lockable.sol " ;
10
+ import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol " ;
11
+ import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol " ;
10
12
import { V3SpokePoolInterface } from "./interfaces/V3SpokePoolInterface.sol " ;
11
13
import { IERC20Auth } from "./external/interfaces/IERC20Auth.sol " ;
12
14
import { WETH9Interface } from "./external/interfaces/WETH9Interface.sol " ;
@@ -98,7 +100,7 @@ contract SpokePoolPeripheryProxy is SpokePoolV3PeripheryProxyInterface, Lockable
98
100
* contract may be deployed deterministically at the same address across different networks.
99
101
* @custom:security-contact bugs@across.to
100
102
*/
101
- contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface , Lockable , MultiCaller {
103
+ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface , Lockable , MultiCaller , EIP712 {
102
104
using SafeERC20 for IERC20 ;
103
105
using Address for address ;
104
106
@@ -157,6 +159,7 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
157
159
error InvalidProxy ();
158
160
error InvalidSwapToken ();
159
161
error NotProxy ();
162
+ error InvalidSignature ();
160
163
161
164
/**
162
165
* @notice Construct a new Proxy contract.
@@ -165,7 +168,7 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
165
168
* across different networks is the same. Constructor parameters affect the bytecode so we can only
166
169
* add parameters here that are consistent across networks.
167
170
*/
168
- constructor () {}
171
+ constructor () EIP712 ( " ACROSS-V3-PERIPHERY " , " 1.0.0 " ) {}
169
172
170
173
/**
171
174
* @notice Initializes the SwapAndBridgeBase contract.
@@ -282,14 +285,18 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
282
285
* @notice Swaps an EIP-2612 token on this chain via specified router before submitting Across deposit atomically.
283
286
* Caller can specify their slippage tolerance for the swap and Across deposit params.
284
287
* @dev If the swapToken in swapData does not implement `permit` to the specifications of EIP-2612, this function will fail.
288
+ * @param signatureOwner The owner of the permit signature and swapAndDepositData signature. Assumed to be the depositor for the Across spoke pool.
285
289
* @param swapAndDepositData Specifies the params we need to perform a swap on a generic exchange.
286
290
* @param deadline Deadline before which the permit signature is valid.
287
291
* @param permitSignature Permit signature encoded as (bytes32 r, bytes32 s, uint8 v).
292
+ * @param swapAndDepositDataSignature The signature against the input swapAndDepositData encoded as (bytes32 r, bytes32 s, uint8 v).
288
293
*/
289
294
function swapAndBridgeWithPermit (
295
+ address signatureOwner ,
290
296
SwapAndDepositData calldata swapAndDepositData ,
291
297
uint256 deadline ,
292
- bytes calldata permitSignature
298
+ bytes calldata permitSignature ,
299
+ bytes calldata swapAndDepositDataSignature
293
300
) external override nonReentrant {
294
301
(bytes32 r , bytes32 s , uint8 v ) = PeripherySigningLib.deserializeSignature (permitSignature);
295
302
// Load variables used in this function onto the stack.
@@ -299,9 +306,17 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
299
306
// For permit transactions, we wrap the call in a try/catch block so that the transaction will continue even if the call to
300
307
// permit fails. For example, this may be useful if the permit signature, which can be redeemed by anyone, is executed by somebody
301
308
// other than this contract.
302
- try IERC20Permit (_swapToken).permit (msg .sender , address (this ), _swapTokenAmount, deadline, v, r, s) {} catch {}
303
- IERC20 (_swapToken).safeTransferFrom (msg .sender , address (this ), _swapTokenAmount);
309
+ try
310
+ IERC20Permit (_swapToken).permit (signatureOwner, address (this ), _swapTokenAmount, deadline, v, r, s)
311
+ {} catch {}
312
+ IERC20 (_swapToken).safeTransferFrom (signatureOwner, address (this ), _swapTokenAmount);
304
313
314
+ // Verify that the signatureOwner signed the input swapAndDepositData.
315
+ _validateSignature (
316
+ signatureOwner,
317
+ PeripherySigningLib.hashSwapAndDepositData (swapAndDepositData),
318
+ swapAndDepositDataSignature
319
+ );
305
320
_swapAndBridge (swapAndDepositData);
306
321
}
307
322
@@ -342,25 +357,29 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
342
357
* @notice Swaps an EIP-3009 token on this chain via specified router before submitting Across deposit atomically.
343
358
* Caller can specify their slippage tolerance for the swap and Across deposit params.
344
359
* @dev If swapToken does not implement `receiveWithAuthorization` to the specifications of EIP-3009, this call will revert.
360
+ * @param signatureOwner The owner of the EIP3009 signature and swapAndDepositData signature. Assumed to be the depositor for the Across spoke pool.
345
361
* @param swapAndDepositData Specifies the params we need to perform a swap on a generic exchange.
346
362
* @param validAfter The unix time after which the `receiveWithAuthorization` signature is valid.
347
363
* @param validBefore The unix time before which the `receiveWithAuthorization` signature is valid.
348
364
* @param nonce Unique nonce used in the `receiveWithAuthorization` signature.
349
- * @param receiveWithAuthSignature EIP3009 signature encoded adepositors (bytes32 r, bytes32 s, uint8 v).
365
+ * @param receiveWithAuthSignature EIP3009 signature encoded as (bytes32 r, bytes32 s, uint8 v).
366
+ * @param swapAndDepositDataSignature The signature against the input swapAndDepositData encoded as (bytes32 r, bytes32 s, uint8 v).
350
367
*/
351
368
function swapAndBridgeWithAuthorization (
369
+ address signatureOwner ,
352
370
SwapAndDepositData calldata swapAndDepositData ,
353
371
uint256 validAfter ,
354
372
uint256 validBefore ,
355
373
bytes32 nonce ,
356
- bytes calldata receiveWithAuthSignature
374
+ bytes calldata receiveWithAuthSignature ,
375
+ bytes calldata swapAndDepositDataSignature
357
376
) external override nonReentrant {
358
377
(bytes32 r , bytes32 s , uint8 v ) = PeripherySigningLib.deserializeSignature (receiveWithAuthSignature);
359
378
// While any contract can vacuously implement `transferWithAuthorization` (or just have a fallback),
360
379
// if tokens were not sent to this contract, by this call to swapData.swapToken, this function will revert
361
380
// when attempting to swap tokens it does not own.
362
381
IERC20Auth (address (swapAndDepositData.swapToken)).receiveWithAuthorization (
363
- msg . sender ,
382
+ signatureOwner ,
364
383
address (this ),
365
384
swapAndDepositData.swapTokenAmount,
366
385
validAfter,
@@ -371,20 +390,30 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
371
390
s
372
391
);
373
392
393
+ // Verify that the signatureOwner signed the input swapAndDepositData.
394
+ _validateSignature (
395
+ signatureOwner,
396
+ PeripherySigningLib.hashSwapAndDepositData (swapAndDepositData),
397
+ swapAndDepositDataSignature
398
+ );
374
399
_swapAndBridge (swapAndDepositData);
375
400
}
376
401
377
402
/**
378
403
* @notice Deposits an EIP-2612 token Across input token into the Spoke Pool contract.
379
404
* @dev If `acrossInputToken` does not implement `permit` to the specifications of EIP-2612, this function will fail.
405
+ * @param signatureOwner The owner of the permit signature and depositData signature. Assumed to be the depositor for the Across spoke pool.
380
406
* @param depositData Specifies the Across deposit params to send.
381
407
* @param deadline Deadline before which the permit signature is valid.
382
408
* @param permitSignature Permit signature encoded as (bytes32 r, bytes32 s, uint8 v).
409
+ * @param depositDataSignature The signature against the input depositData encoded as (bytes32 r, bytes32 s, uint8 v).
383
410
*/
384
411
function depositWithPermit (
412
+ address signatureOwner ,
385
413
DepositData calldata depositData ,
386
414
uint256 deadline ,
387
- bytes calldata permitSignature
415
+ bytes calldata permitSignature ,
416
+ bytes calldata depositDataSignature
388
417
) external override nonReentrant {
389
418
(bytes32 r , bytes32 s , uint8 v ) = PeripherySigningLib.deserializeSignature (permitSignature);
390
419
// Load variables used in this function onto the stack.
@@ -394,9 +423,11 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
394
423
// For permit transactions, we wrap the call in a try/catch block so that the transaction will continue even if the call to
395
424
// permit fails. For example, this may be useful if the permit signature, which can be redeemed by anyone, is executed by somebody
396
425
// other than this contract.
397
- try IERC20Permit (_inputToken).permit (msg . sender , address (this ), _inputAmount, deadline, v, r, s) {} catch {}
398
- IERC20 (_inputToken).safeTransferFrom (msg . sender , address (this ), _inputAmount);
426
+ try IERC20Permit (_inputToken).permit (signatureOwner , address (this ), _inputAmount, deadline, v, r, s) {} catch {}
427
+ IERC20 (_inputToken).safeTransferFrom (signatureOwner , address (this ), _inputAmount);
399
428
429
+ // Verify that the signatureOwner signed the input depositData.
430
+ _validateSignature (signatureOwner, PeripherySigningLib.hashDepositData (depositData), depositDataSignature);
400
431
_depositV3 (
401
432
depositData.baseDepositData.depositor,
402
433
depositData.baseDepositData.recipient,
@@ -461,26 +492,30 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
461
492
/**
462
493
* @notice Deposits an EIP-3009 compliant Across input token into the Spoke Pool contract.
463
494
* @dev If `acrossInputToken` does not implement `receiveWithAuthorization` to the specifications of EIP-3009, this call will revert.
495
+ * @param signatureOwner The owner of the EIP3009 signature and depositData signature. Assumed to be the depositor for the Across spoke pool.
464
496
* @param depositData Specifies the Across deposit params to send.
465
497
* @param validAfter The unix time after which the `receiveWithAuthorization` signature is valid.
466
498
* @param validBefore The unix time before which the `receiveWithAuthorization` signature is valid.
467
499
* @param nonce Unique nonce used in the `receiveWithAuthorization` signature.
468
500
* @param receiveWithAuthSignature EIP3009 signature encoded as (bytes32 r, bytes32 s, uint8 v).
501
+ * @param depositDataSignature The signature against the input depositData encoded as (bytes32 r, bytes32 s, uint8 v).
469
502
*/
470
503
function depositWithAuthorization (
504
+ address signatureOwner ,
471
505
DepositData calldata depositData ,
472
506
uint256 validAfter ,
473
507
uint256 validBefore ,
474
508
bytes32 nonce ,
475
- bytes calldata receiveWithAuthSignature
509
+ bytes calldata receiveWithAuthSignature ,
510
+ bytes calldata depositDataSignature
476
511
) external override nonReentrant {
477
512
// Load variables used multiple times onto the stack.
478
513
uint256 _inputAmount = depositData.inputAmount;
479
514
480
515
// Redeem the receiveWithAuthSignature.
481
516
(bytes32 r , bytes32 s , uint8 v ) = PeripherySigningLib.deserializeSignature (receiveWithAuthSignature);
482
517
IERC20Auth (depositData.baseDepositData.inputToken).receiveWithAuthorization (
483
- msg . sender ,
518
+ signatureOwner ,
484
519
address (this ),
485
520
_inputAmount,
486
521
validAfter,
@@ -491,6 +526,8 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
491
526
s
492
527
);
493
528
529
+ // Verify that the signatureOwner signed the input depositData.
530
+ _validateSignature (signatureOwner, PeripherySigningLib.hashDepositData (depositData), depositDataSignature);
494
531
_depositV3 (
495
532
depositData.baseDepositData.depositor,
496
533
depositData.baseDepositData.recipient,
@@ -519,6 +556,28 @@ contract SpokePoolV3Periphery is SpokePoolV3PeripheryInterface, Lockable, MultiC
519
556
: EIP1271_INVALID_SIGNATURE;
520
557
}
521
558
559
+ /**
560
+ * @notice Returns the contract's EIP712 domain separator, used to sign hashed depositData/swapAndDepositData types.
561
+ */
562
+ function domainSeparator () external view returns (bytes32 ) {
563
+ return _domainSeparatorV4 ();
564
+ }
565
+
566
+ /**
567
+ * @notice Validates that the typed data hash corresponds to the input signature owner and corresponding signature.
568
+ * @param signatureOwner The alledged signer of the input hash.
569
+ * @param typedDataHash The EIP712 data hash to check the signature against.
570
+ * @param signature The signature to validate.
571
+ */
572
+ function _validateSignature (
573
+ address signatureOwner ,
574
+ bytes32 typedDataHash ,
575
+ bytes calldata signature
576
+ ) private {
577
+ if (! SignatureChecker.isValidSignatureNow (signatureOwner, _hashTypedDataV4 (typedDataHash), signature))
578
+ revert InvalidSignature ();
579
+ }
580
+
522
581
/**
523
582
* @notice Approves the spoke pool and calls `depositV3` function with the specified input parameters.
524
583
* @param depositor The address on the origin chain which should be treated as the depositor by Across, and will therefore receive refunds if this deposit
0 commit comments