Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit fixes [GasZipFacet v2.0.2] #910

Merged
merged 12 commits into from
Jan 11, 2025
10 changes: 6 additions & 4 deletions src/Facets/GasZipFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ import { InvalidCallData, CannotBridgeToSameNetwork, InvalidAmount } from "lifi/
/// @title GasZipFacet
/// @author LI.FI (https://li.fi)
/// @notice Provides functionality to swap ERC20 tokens to native and deposit them to the gas.zip protocol (https://www.gas.zip/)
/// @custom:version 2.0.0
/// @custom:version 2.0.1
contract GasZipFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {
using SafeTransferLib for address;

uint256 internal constant MAX_CHAINID_LENGTH_ALLOWED = 32;

error OnlyNativeAllowed();
error TooManyChainIds();

/// State ///
address public constant NON_EVM_RECEIVER_IDENTIFIER =
address public constant NON_EVM_ADDRESS =
0x11f111f111f111F111f111f111F111f111f111F1;
IGasZip public immutable gasZipRouter;

Expand Down Expand Up @@ -104,7 +106,7 @@ contract GasZipFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {

// validate that receiverAddress matches with bridgeData in case of EVM target chain
if (
_bridgeData.receiver != NON_EVM_RECEIVER_IDENTIFIER &&
_bridgeData.receiver != NON_EVM_ADDRESS &&
_gasZipData.receiverAddress !=
bytes32(uint256(uint160(_bridgeData.receiver)))
) revert InvalidCallData();
Expand All @@ -130,7 +132,7 @@ contract GasZipFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {
) external pure returns (uint256 destinationChains) {
uint256 length = _chainIds.length;

if (length > 32) revert TooManyChainIds();
if (length > MAX_CHAINID_LENGTH_ALLOWED) revert TooManyChainIds();

for (uint256 i; i < length; ++i) {
// Shift destinationChains left by 8 bits and add the next chainID
Expand Down
5 changes: 2 additions & 3 deletions src/Libraries/LibAsset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { LibSwap } from "./LibSwap.sol";

/// @title LibAsset
/// @custom:version 1.0.1
/// @custom:version 1.0.2
/// @notice This library contains helpers for dealing with onchain transfers
/// of assets, including accounting for the native asset `assetId`
/// conventions and any noncompliant ERC20 transfers
Expand Down Expand Up @@ -67,8 +67,7 @@ library LibAsset {
}

if (assetId.allowance(address(this), spender) < amount) {
SafeERC20.safeApprove(IERC20(assetId), spender, 0);
SafeERC20.safeApprove(IERC20(assetId), spender, MAX_UINT);
SafeERC20.forceApprove(IERC20(assetId), spender, MAX_UINT);
}
}

Expand Down
18 changes: 5 additions & 13 deletions src/Periphery/GasZipPeriphery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,21 @@ import { IGasZip } from "../Interfaces/IGasZip.sol";
import { LibSwap } from "../Libraries/LibSwap.sol";
import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol";
import { LibUtil } from "../Libraries/LibUtil.sol";
import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol";
import { SwapperV2 } from "../Helpers/SwapperV2.sol";
import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol";
import { Validatable } from "../Helpers/Validatable.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { InvalidCallData } from "../Errors/GenericErrors.sol";

/// @title GasZipPeriphery
/// @author LI.FI (https://li.fi)
/// @notice Provides functionality to swap ERC20 tokens to use the gas.zip protocol as a pre-bridge step (https://www.gas.zip/)
/// @custom:version 1.0.0
contract GasZipPeriphery is
ILiFi,
ReentrancyGuard,
SwapperV2,
Validatable,
WithdrawablePeriphery
{
/// @custom:version 1.0.1
contract GasZipPeriphery is ILiFi, WithdrawablePeriphery {
using SafeTransferLib for address;

/// State ///
IGasZip public immutable gasZipRouter;
address public immutable liFiDEXAggregator;
uint256 internal constant MAX_CHAINID_LENGTH_ALLOWED = 32;

/// Errors ///
error TooManyChainIds();
Expand Down Expand Up @@ -59,7 +51,7 @@ contract GasZipPeriphery is
LibAsset.maxApproveERC20(
IERC20(_swapData.sendingAssetId),
liFiDEXAggregator,
type(uint256).max
_swapData.fromAmount
);

// execute swap using LiFiDEXAggregator
Expand Down Expand Up @@ -110,7 +102,7 @@ contract GasZipPeriphery is
) external pure returns (uint256 destinationChains) {
uint256 length = _chainIds.length;

if (length > 32) revert TooManyChainIds();
if (length > MAX_CHAINID_LENGTH_ALLOWED) revert TooManyChainIds();

for (uint256 i; i < length; ++i) {
// Shift destinationChains left by 8 bits and add the next chainID
Expand Down
35 changes: 23 additions & 12 deletions src/Periphery/Permit2Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.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.0
/// @custom:version 1.0.1
contract Permit2Proxy is WithdrawablePeriphery {
/// Storage ///

Expand Down Expand Up @@ -82,15 +82,24 @@ contract Permit2Proxy is WithdrawablePeriphery {
bytes calldata diamondCalldata
) public payable returns (bytes memory) {
// call permit on token contract to register approval using signature
ERC20Permit(tokenAddress).permit(
msg.sender, // Ensure msg.sender is same wallet that signed permit
address(this),
amount,
deadline,
v,
r,
s
);
try
ERC20Permit(tokenAddress).permit(
msg.sender, // Ensure msg.sender is same wallet that signed permit
address(this),
amount,
deadline,
v,
r,
s
)
{} catch Error(string memory reason) {
if (
IERC20(tokenAddress).allowance(msg.sender, address(this)) <
amount
) {
revert(reason);
}
}

// deposit assets
LibAsset.transferFromERC20(
Expand Down Expand Up @@ -201,7 +210,9 @@ contract Permit2Proxy is WithdrawablePeriphery {
_assetId,
_amount
);
bytes32 permit = _getTokenPermissionsHash(tokenPermissions);
bytes32 tokenPermissionsHash = _getTokenPermissionsHash(
tokenPermissions
);

// Witness
Permit2Proxy.LiFiCall memory lifiCall = LiFiCall(
Expand All @@ -213,7 +224,7 @@ contract Permit2Proxy is WithdrawablePeriphery {
// PermitTransferWithWitness
msgHash = _getPermitWitnessTransferFromHash(
PERMIT2.DOMAIN_SEPARATOR(),
permit,
tokenPermissionsHash,
address(this),
_nonce,
_deadline,
Expand Down
5 changes: 2 additions & 3 deletions src/Periphery/ReceiverAcrossV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
/// @title ReceiverAcrossV3
/// @author LI.FI (https://li.fi)
/// @notice Arbitrary execution contract used for cross-chain swaps and message passing via AcrossV3
/// @custom:version 1.0.1
/// @custom:version 1.0.2
contract ReceiverAcrossV3 is ILiFi, TransferrableOwnership {
using SafeTransferLib for address;

Expand Down Expand Up @@ -105,8 +105,7 @@ contract ReceiverAcrossV3 is ILiFi, TransferrableOwnership {
address payable receiver,
uint256 amount
) private {
assetId.safeApprove(address(executor), 0);
assetId.safeApprove(address(executor), amount);
assetId.safeApproveWithRetry(address(executor), amount);
try
executor.swapAndCompleteBridgeTokens(
_transactionId,
Expand Down
58 changes: 58 additions & 0 deletions test/solidity/Periphery/Permit2Proxy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,64 @@ contract Permit2ProxyTest is TestBase {
assertEq(permit2Proxy.nextNonceAfter(address(this), 1), 2);
}

function test_eip2612_flow_is_resistant_to_frontrun_attack()
public
returns (TestDataEIP2612 memory)
{
// Record initial balance
uint256 initialBalance = ERC20(ADDRESS_USDC).balanceOf(PERMIT2_USER);

vm.startPrank(PERMIT2_USER);

bytes32 domainSeparator = ERC20Permit(ADDRESS_USDC).DOMAIN_SEPARATOR();

TestDataEIP2612
memory testdata = _getTestDataEIP2612SignedByPERMIT2_USER(
ADDRESS_USDC,
domainSeparator,
block.timestamp + 1000
);

vm.stopPrank();

vm.startPrank(address(0xA));
// Attacker calls ERC20.permit directly
ERC20Permit(ADDRESS_USDC).permit(
PERMIT2_USER, //victim address
address(permit2Proxy),
defaultUSDCAmount,
testdata.deadline,
testdata.v,
testdata.r,
testdata.s
);
vm.stopPrank();

vm.startPrank(PERMIT2_USER);

// User's TX should succeed
permit2Proxy.callDiamondWithEIP2612Signature(
ADDRESS_USDC,
defaultUSDCAmount,
testdata.deadline,
testdata.v,
testdata.r,
testdata.s,
testdata.diamondCalldata
);
vm.stopPrank();

// Verify tokens were moved from PERMIT2_USER
uint256 finalBalance = ERC20(ADDRESS_USDC).balanceOf(PERMIT2_USER);
assertEq(
finalBalance,
initialBalance - defaultUSDCAmount,
"User balance should have decreased by defaultUSDCAmount"
);

return testdata;
}

/// Helper Functions ///

function _getPermit2TransferFromParamsSignedByPERMIT2_USER()
Expand Down
Loading