Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion audit/auditLog.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@
"auditorGitHandle": "sujithsomraaj",
"auditReportPath": "./audit/reports/2025.04.13_DexManagerFacet(v1.0.2).pdf",
"auditCommitHash": "299df50b5d44d4d59a7ccc34bbfe94584acb0c81"
},
"audit20250415": {
"auditCompletedOn": "15.04.2025",
"auditedBy": "Sujith Somraaj (individual security researcher)",
"auditorGitHandle": "sujithsomraaj",
"auditReportPath": "./audit/reports/2025.04.15_Permit2Proxy(v1.0.3).pdf",
"auditCommitHash": "5b212bd302a7ec0c027c51753d6d5a9937f6769e"
}
},
"auditedContracts": {
Expand Down Expand Up @@ -289,7 +296,8 @@
"Permit2Proxy": {
"1.0.0": ["audit20241122"],
"1.0.1": ["audit20250110_1"],
"1.0.2": ["audit20250109_3"]
"1.0.2": ["audit20250109_3"],
"1.0.3": ["audit20250415"]
},
"Receiver": {
"2.0.3": ["audit20250109_3"],
Expand Down
Binary file added audit/reports/2025.04.15_Permit2Proxy(v1.0.3).pdf
Binary file not shown.
10 changes: 9 additions & 1 deletion src/Periphery/Permit2Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.17;

import { ISignatureTransfer } from "permit2/interfaces/ISignatureTransfer.sol";
import { LibAsset, IERC20 } from "lifi/Libraries/LibAsset.sol";
import { LibUtil } from "lifi/Libraries/LibUtil.sol";
import { PermitHash } from "permit2/libraries/PermitHash.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol";
Expand All @@ -12,7 +13,7 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
/// @author LI.FI (https://li.fi)
/// @notice Proxy contract allowing gasless calls via Permit2 as well as making
/// token approvals via ERC20 Permit (EIP-2612) to our diamond contract
/// @custom:version 1.0.2
/// @custom:version 1.0.3
contract Permit2Proxy is WithdrawablePeriphery {
/// Storage ///

Expand Down Expand Up @@ -100,6 +101,13 @@ contract Permit2Proxy is WithdrawablePeriphery {
) {
revert(reason);
}
} catch (bytes memory reason) {
if (
IERC20(tokenAddress).allowance(msg.sender, address(this)) <
amount
) {
LibUtil.revertWith(reason);
}
}

// deposit assets
Expand Down
246 changes: 244 additions & 2 deletions test/solidity/Periphery/Permit2Proxy.t.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,91 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { TestBase, ERC20 } from "../utils/TestBase.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { TestBase } from "../utils/TestBase.sol";
import { Permit2Proxy } from "lifi/Periphery/Permit2Proxy.sol";
import { ISignatureTransfer } from "permit2/interfaces/ISignatureTransfer.sol";
import { PermitHash } from "permit2/libraries/PermitHash.sol";
import { PolygonBridgeFacet } from "lifi/Facets/PolygonBridgeFacet.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { stdError } from "forge-std/Test.sol";

abstract contract BaseMockPermitToken is ERC20, ERC20Permit {
constructor() ERC20("Mock", "MCK") ERC20Permit("Mock") {}

function permit(
// solhint-disable-next-line no-unused-vars
address owner,
// solhint-disable-next-line no-unused-vars
address spender,
// solhint-disable-next-line no-unused-vars
uint256 value,
// solhint-disable-next-line no-unused-vars
uint256 deadline,
// solhint-disable-next-line no-unused-vars
uint8 v,
// solhint-disable-next-line no-unused-vars
bytes32 r,
// solhint-disable-next-line no-unused-vars
bytes32 s
) public pure override {
_permit();
}

function _permit() internal pure virtual;
}

contract MockPermitToken is BaseMockPermitToken {
function _permit() internal pure override {
revert CustomPermitError();
}

error CustomPermitError();
}

contract MockPermitTokenPanic is BaseMockPermitToken {
// solhint-disable-next-line reason-string
function _permit() internal pure override {
assert(true == false);
}
}

contract MockPermitTokenEmptyRevert is BaseMockPermitToken {
function _permit() internal pure override {
// solhint-disable-next-line gas-custom-errors,reason-string
revert();
}
}

contract MockPermitTokenStringRevert is BaseMockPermitToken {
function _permit() internal pure override {
// solhint-disable-next-line gas-custom-errors
revert("Custom error message");
}
}

contract MockPermitTokenRequireRevert is BaseMockPermitToken {
function _permit() internal pure override {
// solhint-disable-next-line gas-custom-errors
require(true == false, "Test revert message");
}
}

contract MockPermitTokenAdditionOverflow is BaseMockPermitToken {
function _permit() internal pure override {
// solhint-disable-next-line no-unused-vars
uint256 test = type(uint256).max + 1;
}
}

contract MockPermitTokenDivisionByZero is BaseMockPermitToken {
function _permit() internal pure override {
// solhint-disable-next-line no-unused-vars
uint256 x = 0;
// solhint-disable-next-line no-unused-vars
uint256 y = 1 / x; // This will cause a division by zero at runtime
}
}

contract Permit2ProxyTest is TestBase {
using PermitHash for ISignatureTransfer.PermitTransferFrom;
Expand Down Expand Up @@ -542,6 +621,169 @@ contract Permit2ProxyTest is TestBase {
return testdata;
}

function testRevert_FailsOnCustomErrorAndAllowance() public {
// deploy a mock ERC20Permit token that reverts with custom error on permit
MockPermitToken token = new MockPermitToken();
address tokenAddress = address(token);

vm.startPrank(permit2User);

bytes memory callData = _getCalldataForBridging();

// we don't care about the signature since permit will revert anyway
vm.expectRevert(MockPermitToken.CustomPermitError.selector);
permit2Proxy.callDiamondWithEIP2612Signature(
tokenAddress,
defaultUSDCAmount,
block.timestamp + 1000,
27, // dummy v
bytes32(0), // dummy r
bytes32(0), // dummy s
callData
);

vm.stopPrank();
}

function testRevert_FailsOnPanicDuringAllowance() public {
MockPermitTokenPanic token = new MockPermitTokenPanic();

address tokenAddress = address(token);

vm.startPrank(permit2User);

bytes memory callData = _getCalldataForBridging();

// expect panic error (0x01 for assertion failure)
vm.expectRevert(stdError.assertionError);
permit2Proxy.callDiamondWithEIP2612Signature(
tokenAddress,
defaultUSDCAmount,
block.timestamp + 1000,
27, // dummy v
bytes32(0), // dummy r
bytes32(0), // dummy s
callData
);

vm.stopPrank();
}

function testRevert_FailsOnEmptyRevert() public {
MockPermitTokenEmptyRevert token = new MockPermitTokenEmptyRevert();
address tokenAddress = address(token);

vm.startPrank(permit2User);
bytes memory callData = _getCalldataForBridging();

// Expect empty revert
vm.expectRevert();

permit2Proxy.callDiamondWithEIP2612Signature(
tokenAddress,
defaultUSDCAmount,
block.timestamp + 1000,
27, // dummy v
bytes32(0), // dummy r
bytes32(0), // dummy s
callData
);

vm.stopPrank();
}

function testRevert_FailsOnStringRevert() public {
MockPermitTokenStringRevert token = new MockPermitTokenStringRevert();
address tokenAddress = address(token);

vm.startPrank(permit2User);
bytes memory callData = _getCalldataForBridging();

// Expect string revert
vm.expectRevert("Custom error message");

permit2Proxy.callDiamondWithEIP2612Signature(
tokenAddress,
defaultUSDCAmount,
block.timestamp + 1000,
27, // dummy v
bytes32(0), // dummy r
bytes32(0), // dummy s
callData
);

vm.stopPrank();
}

function testRevert_FailsOnRequireRevert() public {
MockPermitTokenRequireRevert token = new MockPermitTokenRequireRevert();
address tokenAddress = address(token);

vm.startPrank(permit2User);
bytes memory callData = _getCalldataForBridging();

// Expect require revert
vm.expectRevert("Test revert message");

permit2Proxy.callDiamondWithEIP2612Signature(
tokenAddress,
500, // Amount less than 1000 to trigger require
block.timestamp + 1000,
27, // dummy v
bytes32(0), // dummy r
bytes32(0), // dummy s
callData
);

vm.stopPrank();
}

function testRevert_FailsOnAdditionOverflow() public {
MockPermitTokenAdditionOverflow token = new MockPermitTokenAdditionOverflow();
address tokenAddress = address(token);

vm.startPrank(permit2User);
bytes memory callData = _getCalldataForBridging();

// Expect arithmetic overflow panic
vm.expectRevert(stdError.arithmeticError);

permit2Proxy.callDiamondWithEIP2612Signature(
tokenAddress,
defaultUSDCAmount,
block.timestamp + 1000,
27, // dummy v
bytes32(0), // dummy r
bytes32(0), // dummy s
callData
);

vm.stopPrank();
}

function testRevert_FailsOnDivisionByZero() public {
MockPermitTokenDivisionByZero token = new MockPermitTokenDivisionByZero();
address tokenAddress = address(token);

vm.startPrank(permit2User);
bytes memory callData = _getCalldataForBridging();

// Expect arithmetic error (division by zero)
vm.expectRevert(stdError.divisionError);

permit2Proxy.callDiamondWithEIP2612Signature(
tokenAddress,
defaultUSDCAmount,
block.timestamp + 1000,
27, // dummy v
bytes32(0), // dummy r
bytes32(0), // dummy s
callData
);

vm.stopPrank();
}

/// Helper Functions ///

function _getPermit2TransferFromParamsSignedBypermit2User()
Expand Down
Loading