Skip to content

Commit e6406e4

Browse files
committed
Audit April 2025 - 4.5 - Changed abi.encodePacked to abi.encode for the signature
1 parent 0f8e128 commit e6406e4

2 files changed

Lines changed: 143 additions & 16 deletions

File tree

src/helpers/DelegationMetaSwapAdapter.sol

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
274274
* @param _tokenTo The output token of the swap.
275275
* @param _recipient The address that will receive the swapped tokens.
276276
* @param _amountFrom The amount of tokens to be swapped.
277-
* @param _balanceFromBefore The contracts balance of _tokenFrom before the incoming token transfer is credited.
277+
* @param _balanceFromBefore The contract's balance of _tokenFrom before the incoming token transfer is credited.
278278
* @param _swapData Arbitrary data required by the aggregator (e.g. encoded swap params).
279279
*/
280280
function swapTokens(
@@ -411,6 +411,20 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
411411

412412
////////////////////////////// Private/Internal Methods //////////////////////////////
413413

414+
/**
415+
* @dev Validates the expiration and signature of the provided apiData.
416+
* @param _signatureData Contains the apiData, the expiration and signature.
417+
*/
418+
function _validateSignature(SignatureData memory _signatureData) internal view {
419+
if (block.timestamp > _signatureData.expiration) revert SignatureExpired();
420+
421+
bytes32 messageHash_ = keccak256(abi.encode(_signatureData.apiData, _signatureData.expiration));
422+
bytes32 ethSignedMessageHash_ = MessageHashUtils.toEthSignedMessageHash(messageHash_);
423+
424+
address recoveredSigner_ = ECDSA.recover(ethSignedMessageHash_, _signatureData.signature);
425+
if (recoveredSigner_ != swapApiSigner) revert InvalidApiSignature();
426+
}
427+
414428
/**
415429
* @notice Sends tokens or native token to a specified recipient.
416430
* @param _token ERC20 token to send or address(0) for native token.
@@ -518,18 +532,4 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
518532

519533
return _token.balanceOf(address(this));
520534
}
521-
522-
/**
523-
* @dev Validates the expiration and signature of the provided apiData.
524-
* @param _signatureData Contains the apiData, the expiration and signature.
525-
*/
526-
function _validateSignature(SignatureData memory _signatureData) private view {
527-
if (block.timestamp > _signatureData.expiration) revert SignatureExpired();
528-
529-
bytes32 messageHash_ = keccak256(abi.encodePacked(_signatureData.apiData, _signatureData.expiration));
530-
bytes32 ethSignedMessageHash_ = MessageHashUtils.toEthSignedMessageHash(messageHash_);
531-
532-
address recoveredSigner_ = ECDSA.recover(ethSignedMessageHash_, _signatureData.signature);
533-
if (recoveredSigner_ != swapApiSigner) revert InvalidApiSignature();
534-
}
535535
}

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 tokens can be swapped by delegations in a purely local environment (using a MetaSwapMock).
305410
*/
@@ -1444,3 +1549,25 @@ contract MetaSwapMock {
14441549
}
14451550
}
14461551
}
1552+
1553+
contract DelegationMetaSwapAdapterSignatureTest is DelegationMetaSwapAdapter {
1554+
constructor(
1555+
address _owner,
1556+
address _swapApiSigner,
1557+
address _delegationManager,
1558+
address _metaSwap,
1559+
address _argsEqualityCheckEnforcer
1560+
)
1561+
DelegationMetaSwapAdapter(
1562+
_owner,
1563+
_swapApiSigner,
1564+
IDelegationManager(_delegationManager),
1565+
IMetaSwap(_metaSwap),
1566+
_argsEqualityCheckEnforcer
1567+
)
1568+
{ }
1569+
1570+
function exposedValidateSignature(SignatureData memory _signatureData) public view {
1571+
_validateSignature(_signatureData);
1572+
}
1573+
}

0 commit comments

Comments
 (0)