Skip to content

Commit 7ce91a5

Browse files
authored
Merge branch 'main' into fix/audit-april-2025-4.1
2 parents 000f446 + 5befce7 commit 7ce91a5

File tree

2 files changed

+142
-15
lines changed

2 files changed

+142
-15
lines changed

src/helpers/DelegationMetaSwapAdapter.sol

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,20 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
422422

423423
////////////////////////////// Private/Internal Methods //////////////////////////////
424424

425+
/**
426+
* @dev Validates the expiration and signature of the provided apiData.
427+
* @param _signatureData Contains the apiData, the expiration and signature.
428+
*/
429+
function _validateSignature(SignatureData memory _signatureData) internal view {
430+
if (block.timestamp >= _signatureData.expiration) revert SignatureExpired();
431+
432+
bytes32 messageHash_ = keccak256(abi.encode(_signatureData.apiData, _signatureData.expiration));
433+
bytes32 ethSignedMessageHash_ = MessageHashUtils.toEthSignedMessageHash(messageHash_);
434+
435+
address recoveredSigner_ = ECDSA.recover(ethSignedMessageHash_, _signatureData.signature);
436+
if (recoveredSigner_ != swapApiSigner) revert InvalidApiSignature();
437+
}
438+
425439
/**
426440
* @notice Sends tokens or native token to a specified recipient.
427441
* @param _token ERC20 token to send or address(0) for native token.
@@ -529,18 +543,4 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
529543

530544
return _token.balanceOf(address(this));
531545
}
532-
533-
/**
534-
* @dev Validates the expiration and signature of the provided apiData.
535-
* @param _signatureData Contains the apiData, the expiration and signature.
536-
*/
537-
function _validateSignature(SignatureData memory _signatureData) private view {
538-
if (block.timestamp >= _signatureData.expiration) revert SignatureExpired();
539-
540-
bytes32 messageHash_ = keccak256(abi.encodePacked(_signatureData.apiData, _signatureData.expiration));
541-
bytes32 ethSignedMessageHash_ = MessageHashUtils.toEthSignedMessageHash(messageHash_);
542-
543-
address recoveredSigner_ = ECDSA.recover(ethSignedMessageHash_, _signatureData.signature);
544-
if (recoveredSigner_ != swapApiSigner) revert InvalidApiSignature();
545-
}
546546
}

test/helpers/DelegationMetaSwapAdapter.t.sol

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
66
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
77
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
88
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
9+
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
910

1011
import { BasicERC20 } from "../utils/BasicERC20.t.sol";
1112
import { BaseTest } from "../utils/BaseTest.t.sol";
@@ -92,7 +93,7 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest {
9293
* @dev Generates a valid signature for _apiData with a given _expiration.
9394
*/
9495
function _getValidSignature(bytes memory _apiData, uint256 _expiration) internal returns (bytes memory) {
95-
bytes32 messageHash = keccak256(abi.encodePacked(_apiData, _expiration));
96+
bytes32 messageHash = keccak256(abi.encode(_apiData, _expiration));
9697
bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(messageHash);
9798
(uint8 v, bytes32 r, bytes32 s) = vm.sign(swapSignerPrivateKey, ethSignedMessageHash);
9899
return abi.encodePacked(r, s, v);
@@ -296,10 +297,114 @@ abstract contract DelegationMetaSwapAdapterBaseTest is BaseTest {
296297
* @notice These tests run in a purely local environment. No fork is created.
297298
*/
298299
contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest {
300+
DelegationMetaSwapAdapterSignatureTest public adapter;
301+
address public swapApiSigner;
302+
uint256 private _swapSignerPrivateKey = 12345;
303+
299304
function setUp() public override {
300305
super.setUp();
306+
swapApiSigner = vm.addr(_swapSignerPrivateKey);
307+
adapter =
308+
new DelegationMetaSwapAdapterSignatureTest(address(this), swapApiSigner, address(0x123), address(0x456), address(0x789));
309+
}
310+
311+
////////////////////////////// Signature validation tests //////////////////////////////
312+
313+
/**
314+
* @notice Verifies that a valid signature is accepted.
315+
*/
316+
function test_validateSignature_valid() public view {
317+
bytes memory apiData_ = hex"1234";
318+
uint256 expiration_ = block.timestamp + 1 hours;
319+
bytes32 messageHash_ = keccak256(abi.encode(apiData_, expiration_));
320+
bytes32 ethSignedMessageHash_ = MessageHashUtils.toEthSignedMessageHash(messageHash_);
321+
(uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(_swapSignerPrivateKey, ethSignedMessageHash_);
322+
bytes memory signature_ = abi.encodePacked(r_, s_, v_);
323+
324+
DelegationMetaSwapAdapter.SignatureData memory sigData_ =
325+
DelegationMetaSwapAdapter.SignatureData({ apiData: apiData_, expiration: expiration_, signature: signature_ });
326+
327+
adapter.exposedValidateSignature(sigData_);
328+
}
329+
330+
/**
331+
* @notice Verifies that an expired signature is rejected.
332+
*/
333+
function test_validateSignature_expired() public {
334+
bytes memory apiData_ = hex"1234";
335+
uint256 expiration_ = block.timestamp - 1;
336+
bytes32 messageHash_ = keccak256(abi.encode(apiData_, expiration_));
337+
bytes32 ethSignedMessageHash_ = MessageHashUtils.toEthSignedMessageHash(messageHash_);
338+
(uint8 v_, bytes32 r_, bytes32 s_) = vm.sign(_swapSignerPrivateKey, ethSignedMessageHash_);
339+
bytes memory signature_ = abi.encodePacked(r_, s_, v_);
340+
341+
DelegationMetaSwapAdapter.SignatureData memory sigData_ =
342+
DelegationMetaSwapAdapter.SignatureData({ apiData: apiData_, expiration: expiration_, signature: signature_ });
343+
344+
vm.expectRevert(DelegationMetaSwapAdapter.SignatureExpired.selector);
345+
adapter.exposedValidateSignature(sigData_);
346+
}
347+
348+
/**
349+
* @notice Verifies that an invalid signature is rejected.
350+
*/
351+
function test_validateSignature_invalidSigner() public {
352+
bytes memory apiData = hex"1234";
353+
uint256 expiration = block.timestamp + 1 hours;
354+
bytes32 messageHash = keccak256(abi.encode(apiData, expiration));
355+
// Use a different private key to generate an invalid signature
356+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_swapSignerPrivateKey + 1, messageHash);
357+
bytes memory signature = abi.encodePacked(r, s, v);
358+
359+
DelegationMetaSwapAdapter.SignatureData memory sigData =
360+
DelegationMetaSwapAdapter.SignatureData({ apiData: apiData, expiration: expiration, signature: signature });
361+
362+
vm.expectRevert(DelegationMetaSwapAdapter.InvalidApiSignature.selector);
363+
adapter.exposedValidateSignature(sigData);
301364
}
302365

366+
/**
367+
* @notice Verifies that an empty signature is rejected.
368+
*/
369+
function test_validateSignature_emptySignature() public {
370+
bytes memory apiData = hex"1234";
371+
uint256 expiration = block.timestamp + 1 hours;
372+
bytes memory emptySignature = "";
373+
374+
DelegationMetaSwapAdapter.SignatureData memory sigData =
375+
DelegationMetaSwapAdapter.SignatureData({ apiData: apiData, expiration: expiration, signature: emptySignature });
376+
377+
vm.expectRevert(abi.encodeWithSelector(ECDSA.ECDSAInvalidSignatureLength.selector, 0));
378+
adapter.exposedValidateSignature(sigData);
379+
}
380+
381+
/**
382+
* @notice Verifies that a hardcoded valid signature works
383+
*/
384+
function test_validateSignature_hardcodedSignature() public {
385+
// Taken from the swaps api
386+
address swapApiSigner_ = 0x533FbF047Ed13C20e263e2576e41c747206d1348;
387+
388+
vm.prank(address(this));
389+
adapter.setSwapApiSigner(swapApiSigner_);
390+
391+
bytes memory apiData_ =
392+
hex"5f5755290000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000001c616972737761704c696768743446656544796e616d696346697865640000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000196652ed3350000000000000000000000000000000000000000000000000000000068098586000000000000000000000000111bb8c3542f2b92fb41b8d913c01d37884311110000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000001eb87e2999f2f8380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000000000000000001c427cdd17278850f9344bb9b4940a6ce83afbb34b58410cdcdf1ff8b27ea8b7eb338693a44fa8d73a0878779e2f6b41c6af69f42510d98f1bfd19d7675e1b3a9d00000000000000000000000000000000000000000000000000009f295cd5f000000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f19150000000000000000000000000000000000000000000000000000000000000000007f";
393+
uint256 expiration_ = 1745454591251;
394+
395+
// This signature was generated with the test private key for the above data and expiration
396+
bytes memory signature =
397+
hex"fccc4800a4a9d9aa6a8cf933ca759f3974d8eed02e47b12a739601ef1e83617a08c7597d0dd875f955511248da6cf4cfb92be67c0d7241104c061a3c4d45f3b51b";
398+
399+
DelegationMetaSwapAdapter.SignatureData memory sigData_ =
400+
DelegationMetaSwapAdapter.SignatureData({ apiData: apiData_, expiration: expiration_, signature: signature });
401+
402+
// Should not revert since signature is valid
403+
adapter.exposedValidateSignature(sigData_);
404+
}
405+
406+
////////////////////////////// Swap tests //////////////////////////////
407+
303408
/**
304409
* @notice Verifies that the contract reverts when the zero address is used as an input.
305410
*/
@@ -1501,3 +1606,25 @@ contract MetaSwapMock {
15011606
}
15021607
}
15031608
}
1609+
1610+
contract DelegationMetaSwapAdapterSignatureTest is DelegationMetaSwapAdapter {
1611+
constructor(
1612+
address _owner,
1613+
address _swapApiSigner,
1614+
address _delegationManager,
1615+
address _metaSwap,
1616+
address _argsEqualityCheckEnforcer
1617+
)
1618+
DelegationMetaSwapAdapter(
1619+
_owner,
1620+
_swapApiSigner,
1621+
IDelegationManager(_delegationManager),
1622+
IMetaSwap(_metaSwap),
1623+
_argsEqualityCheckEnforcer
1624+
)
1625+
{ }
1626+
1627+
function exposedValidateSignature(SignatureData memory _signatureData) public view {
1628+
_validateSignature(_signatureData);
1629+
}
1630+
}

0 commit comments

Comments
 (0)