From c11c823513ad544f17d0d6306661d7bc6f9f0e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Wed, 22 Jan 2025 07:47:51 +0700 Subject: [PATCH 1/3] update config file --- config/permit2Proxy.json | 151 ++++++++--- package.json | 4 +- src/Periphery/Permit2Proxy.sol | 75 +++++- src/Security/ERC2771ContextCustom.sol | 130 ++++++++++ test/solidity/Periphery/Permit2Proxy.t.sol | 286 ++++++++++++++++++++- 5 files changed, 609 insertions(+), 37 deletions(-) create mode 100644 src/Security/ERC2771ContextCustom.sol diff --git a/config/permit2Proxy.json b/config/permit2Proxy.json index c4ac60ac2..281a007f3 100644 --- a/config/permit2Proxy.json +++ b/config/permit2Proxy.json @@ -1,31 +1,122 @@ { - "mainnet": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "arbitrum": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "aurora": "", - "avalanche": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "base": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "blast": "0x000000000022d473030f116ddee9f6b43ac78ba3", - "boba": "", - "bsc": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "celo": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "fantom": "", - "fraxtal": "", - "fuse": "", - "gnosis": "", - "gravity": "", - "immutablezkevm": "", - "linea": "", - "mantle": "", - "metis": "", - "mode": "", - "moonbeam": "", - "moonriver": "", - "optimism": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "polygon": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "polygonzkevm": "", - "rootstock": "", - "scroll": "", - "sei": "", - "taiko": "", - "zksync": "0x0000000000225e31d15943971f47ad3022f714fa" -} \ No newline at end of file + "mainnet": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "arbitrum": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "aurora": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "avalanche": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "base": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "blast": { + "Permit2": "0x000000000022d473030f116ddee9f6b43ac78ba3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "boba": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "bsc": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "celo": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": [""] + }, + "fantom": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "fraxtal": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "fuse": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "gnosis": { + "Permit2": "", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "gravity": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "immutablezkevm": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "linea": { + "Permit2": "", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "mantle": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "metis": { + "Permit2": "", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "mode": { + "Permit2": "", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "moonbeam": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "moonriver": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "optimism": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "polygon": { + "Permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "polygonzkevm": { + "Permit2": "", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "rootstock": { + "Permit2": "", + "TrustedForwarders": ["0xd8253782c45a12053594b9deB72d8e8aB2Fca54c"] + }, + "scroll": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "sei": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "sonic": { + "Permit2": "", + "TrustedForwarders": ["0x61F2976610970AFeDc1d83229e1E21bdc3D5cbE4"] + }, + "taiko": { + "Permit2": "", + "TrustedForwarders": [""] + }, + "zksync": { + "Permit2": "0x0000000000225e31d15943971f47ad3022f714fa", + "TrustedForwarders": ["0x97015cD4C3d456997DD1C40e2a18c79108FCc412"] + } +} diff --git a/package.json b/package.json index 51cc55fde..1ad566738 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "test": "forge test --evm-version 'shanghai'", "test:fix": "npm run lint:fix; npm run format:fix; npm run test", "gas": "forge snapshot --match-path 'test/solidity/Gas/**/*' -vvv", - "coverage": "rm -rf coverage && rm -f lcov-filtered.info && rm -f lcov.info && forge coverage --report lcov --evm-version 'shanghai' --ir-minimum && ts-node script/utils/filter_lcov.ts lcov.info lcov-filtered.info 'test/' 'script/' && genhtml lcov-filtered.info --branch-coverage --output-dir coverage && open coverage/index.html", + "coverage": "rm -rf coverage && rm -f lcov-filtered.info && rm -f lcov.info && forge coverage --report lcov --ir-minimum && ts-node script/utils/filter_lcov.ts lcov.info lcov-filtered.info 'test/' 'script/' && genhtml lcov-filtered.info --branch-coverage --output-dir coverage && open coverage/index.html", "execute": "node ./_scripts.js run", "abi:generate": "forge clean && rm -fr typechain/* && forge build --skip script --skip test --skip Base --skip Test && hardhat diamondABI", "typechain": "forge clean && rm -rf typechain/* && forge build src && typechain --target ethers-v5 'out/*.sol/*.json' --out-dir typechain", @@ -123,4 +123,4 @@ "solhint --fix" ] } -} \ No newline at end of file +} diff --git a/src/Periphery/Permit2Proxy.sol b/src/Periphery/Permit2Proxy.sol index ebb29eb0d..1f643a1d3 100644 --- a/src/Periphery/Permit2Proxy.sol +++ b/src/Periphery/Permit2Proxy.sol @@ -6,14 +6,17 @@ import { LibAsset, IERC20 } from "lifi/Libraries/LibAsset.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"; +import { ERC2771ContextCustom } from "lifi/Security/ERC2771ContextCustom.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { UnAuthorized } from "../Errors/GenericErrors.sol"; +import { console2 } from "forge-std/console2.sol"; /// @title Permit2Proxy /// @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 -contract Permit2Proxy is WithdrawablePeriphery { +/// @custom:version 1.1.0 +contract Permit2Proxy is WithdrawablePeriphery, ERC2771ContextCustom { /// Storage /// address public immutable LIFI_DIAMOND; @@ -45,8 +48,12 @@ contract Permit2Proxy is WithdrawablePeriphery { constructor( address _lifiDiamond, ISignatureTransfer _permit2, - address _owner - ) WithdrawablePeriphery(_owner) { + address _owner, + address[] memory _trustedForwarderAddresses + ) + WithdrawablePeriphery(_owner) + ERC2771ContextCustom(_trustedForwarderAddresses) + { LIFI_DIAMOND = _lifiDiamond; PERMIT2 = _permit2; @@ -60,6 +67,66 @@ contract Permit2Proxy is WithdrawablePeriphery { /// External Functions /// + /// @notice Same functionality as callDiamondWithEIP2612Signature(...) but allows the transaction to be executed by a (whitelisted) + /// trusted forwarder that implements ERC2771 such as Gelato + /// see https://docs.gelato.network/web3-services/relay/erc-2771-recommended + /// @param tokenAddress Address of the token to be bridged + /// @param amount Amount of tokens to be bridged + /// @param deadline Transaction must be completed before this timestamp + /// @param v User signature (recovery ID) + /// @param r User signature (ECDSA output) + /// @param s User signature (ECDSA output) + /// @param diamondCalldata calldata to execute + function callDiamondWithEIP2612SignatureViaTrustedForwarder( + address tokenAddress, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s, + bytes calldata diamondCalldata + ) public payable returns (bytes memory) { + // only allow this function to be called by pre-whitelisted trusted forwarders + if (!isTrustedForwarder(msg.sender)) revert UnAuthorized(); + + // // Get the original msg.sender from ERC2771Context (i.e. the user address) + address msgSender = _msgSender(); + + // call permit on token contract to register approval using signature + try + ERC20Permit(tokenAddress).permit( + msgSender, + address(this), + amount, + deadline, + v, + r, + s + ) + {} catch Error(string memory reason) { + if ( + IERC20(tokenAddress).allowance(msgSender, address(this)) < + amount + ) { + revert(reason); + } + } + + // deposit assets + LibAsset.transferFromERC20( + tokenAddress, + msgSender, + address(this), + amount + ); + + // maxApprove token to diamond if current allowance is insufficient + LibAsset.maxApproveERC20(IERC20(tokenAddress), LIFI_DIAMOND, amount); + + // call our diamond to execute calldata + return _executeCalldata(diamondCalldata); + } + /// @notice Allows to bridge tokens through a LI.FI diamond contract using /// an EIP2612 gasless permit (only works with tokenAddresses that /// implement EIP2612) diff --git a/src/Security/ERC2771ContextCustom.sol b/src/Security/ERC2771ContextCustom.sol new file mode 100644 index 000000000..f1988845f --- /dev/null +++ b/src/Security/ERC2771ContextCustom.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (metatx/ERC2771Context.sol) + +pragma solidity ^0.8.17; + +import { Context } from "@openzeppelin/contracts/utils/Context.sol"; +import { InvalidCallData } from "../Errors/GenericErrors.sol"; +import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; + +/** + * @dev Context variant with ERC-2771 support. + * + * WARNING: Avoid using this pattern in contracts that rely in a specific calldata length as they'll + * be affected by any forwarder whose `msg.data` is suffixed with the `from` address according to the ERC-2771 + * specification adding the address size in bytes (20) to the calldata size. An example of an unexpected + * behavior could be an unintended fallback (or another function) invocation while trying to invoke the `receive` + * function only accessible if `msg.data.length == 0`. + * + * WARNING: The usage of `delegatecall` in this contract is dangerous and may result in context corruption. + * Any forwarded request to this contract triggering a `delegatecall` to itself will result in an invalid {_msgSender} + * recovery. + */ +abstract contract ERC2771ContextCustom is Context, TransferrableOwnership { + event TrustedForwardersUpdated( + address[] forwarderAddresses, + bool[] isTrusted + ); + mapping(address => bool) private _trustedForwarders; + + /// @notice Constructor + /// We need to have a constructor to silence the compiler + /// However, we do not call the constructor of TransferrableOwnership here since the + /// contract is already initialized by WithdrawablePeriphery.sol and we cannot initialize twice + constructor(address[] memory _trustedForwarderAddresses) { + // update forwarder addresses + for (uint i; i < _trustedForwarderAddresses.length; ) { + _trustedForwarders[_trustedForwarderAddresses[i]] = true; + + unchecked { + ++i; + } + } + } + + /// @notice Updates the list of trusted forwarder addresses + /// A trusted forwarder is a relayer that must implement ERC2771 (= appends original msg.sender to calldata) + /// @param _forwarderAddresses A list of addresses that should be updated + /// @param _isTrusted The bool values that should be assigned for each address update + function setTrustedForwarders( + address[] memory _forwarderAddresses, + bool[] memory _isTrusted + ) public onlyOwner { + // make sure parameters have same length to prevent unexpected behaviour + if (_forwarderAddresses.length != _isTrusted.length) + revert InvalidCallData(); + + emit TrustedForwardersUpdated(_forwarderAddresses, _isTrusted); + + // update forwarder addresses + for (uint i; i < _forwarderAddresses.length; ) { + _trustedForwarders[_forwarderAddresses[i]] = _isTrusted[i]; + + unchecked { + ++i; + } + } + } + + /// @notice Checks if an address is a trusted forwarder address + /// @param _forwarder The address that should be checked if it's a trusted forwarder + function isTrustedForwarder( + address _forwarder + ) public view virtual returns (bool) { + return _trustedForwarders[_forwarder]; + } + + // ###### UNCHANGED CODE FROM OPENZEPPELIN CONTRACT ####### + + /** + * @dev Override for `msg.sender`. Defaults to the original `msg.sender` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ + function _msgSender() internal view virtual override returns (address) { + uint256 calldataLength = msg.data.length; + uint256 contextSuffixLength = _contextSuffixLength(); + if ( + isTrustedForwarder(msg.sender) && + calldataLength >= contextSuffixLength + ) { + return + address( + bytes20(msg.data[calldataLength - contextSuffixLength:]) + ); + } else { + return super._msgSender(); + } + } + + /** + * @dev Override for `msg.data`. Defaults to the original `msg.data` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ + function _msgData() + internal + view + virtual + override + returns (bytes calldata) + { + uint256 calldataLength = msg.data.length; + uint256 contextSuffixLength = _contextSuffixLength(); + if ( + isTrustedForwarder(msg.sender) && + calldataLength >= contextSuffixLength + ) { + return msg.data[:calldataLength - contextSuffixLength]; + } else { + return super._msgData(); + } + } + + /** + * @dev ERC-2771 specifies the context as being a single address (20 bytes). + */ + function _contextSuffixLength() internal view virtual returns (uint256) { + return 20; + } +} diff --git a/test/solidity/Periphery/Permit2Proxy.t.sol b/test/solidity/Periphery/Permit2Proxy.t.sol index 6f7fdb9ed..21868e861 100644 --- a/test/solidity/Periphery/Permit2Proxy.t.sol +++ b/test/solidity/Periphery/Permit2Proxy.t.sol @@ -7,12 +7,16 @@ 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 { UnAuthorized } from "lifi/Errors/GenericErrors.sol"; +import { ERC2771ContextCustom } from "lifi/Security/ERC2771ContextCustom.sol"; contract Permit2ProxyTest is TestBase { using PermitHash for ISignatureTransfer.PermitTransferFrom; /// Constants /// + address internal constant GELATO_1BALANCEERC2771 = + 0xd8253782c45a12053594b9deB72d8e8aB2Fca54c; address internal constant PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3; uint256 internal PRIVATE_KEY = 0x1234567890; @@ -50,11 +54,16 @@ contract Permit2ProxyTest is TestBase { customBlockNumberForForking = 20261175; initTestBase(); + // add Gelato relayer address as trusted forwarder + address[] memory trustedForwarders = new address[](1); + trustedForwarders[0] = GELATO_1BALANCEERC2771; + uniPermit2 = ISignatureTransfer(PERMIT2_ADDRESS); permit2Proxy = new Permit2Proxy( DIAMOND_ADDRESS, uniPermit2, - USER_DIAMOND_OWNER + USER_DIAMOND_OWNER, + trustedForwarders ); PERMIT_WITH_WITNESS_TYPEHASH = keccak256( abi.encodePacked( @@ -76,6 +85,243 @@ contract Permit2ProxyTest is TestBase { /// EIP2612 (native permit) related test cases /// + /// Trusted Forwarder Tests /// + + function testRevert_RevertsIfForwarderIsNotTrusted() + public + assertBalanceChange(ADDRESS_USDC, PERMIT2_USER, 0) + { + vm.startPrank(PERMIT2_USER); + + // get token-specific domainSeparator + bytes32 domainSeparator = ERC20Permit(ADDRESS_USDC).DOMAIN_SEPARATOR(); + + // // using USDC on ETH for testing (implements EIP2612) + TestDataEIP2612 + memory testdata = _getTestDataEIP2612SignedByPERMIT2_USER( + ADDRESS_USDC, + domainSeparator, + block.timestamp + 1000 + ); + + vm.stopPrank(); + + // send transaction from a non-trusted address + vm.startPrank(USER_SENDER); + + // call Permit2Proxy with signature + bytes memory callData = abi.encodeWithSelector( + permit2Proxy + .callDiamondWithEIP2612SignatureViaTrustedForwarder + .selector, + ADDRESS_USDC, + defaultUSDCAmount, + testdata.deadline, + testdata.v, + testdata.r, + testdata.s, + testdata.diamondCalldata + ); + + // append msg.sender to calldata as specified in ERC2771 + bytes memory callDataWithMsgSenderAppended = abi.encodePacked( + callData, + PERMIT2_USER + ); + + // expect tx to revert as we call fom untrusted address + vm.expectRevert(UnAuthorized.selector); + + // // call Permit2Proxy from Gelato contract with msgSender appended to calldata + (bool success, ) = address(permit2Proxy).call( + callDataWithMsgSenderAppended + ); + + if (!success) revert(); + + vm.stopPrank(); + } + + function test_can_execute_calldata_using_eip2612_signature_usdc_via_trustedForwarder() + public + assertBalanceChange( + ADDRESS_USDC, + PERMIT2_USER, + -int256(defaultUSDCAmount) + ) + { + vm.startPrank(PERMIT2_USER); + + // get token-specific domainSeparator + bytes32 domainSeparator = ERC20Permit(ADDRESS_USDC).DOMAIN_SEPARATOR(); + + // // using USDC on ETH for testing (implements EIP2612) + TestDataEIP2612 + memory testdata = _getTestDataEIP2612SignedByPERMIT2_USER( + ADDRESS_USDC, + domainSeparator, + block.timestamp + 1000 + ); + + vm.stopPrank(); + + // send transaction from Gelato contract to mock relayed tx behavior + vm.startPrank(GELATO_1BALANCEERC2771); + + // expect LifiTransferStarted event to be emitted by our diamond contract + vm.expectEmit(true, true, true, true, DIAMOND_ADDRESS); + emit LiFiTransferStarted(bridgeData); + + // call Permit2Proxy with signature + bytes memory callData = abi.encodeWithSelector( + permit2Proxy + .callDiamondWithEIP2612SignatureViaTrustedForwarder + .selector, + ADDRESS_USDC, + defaultUSDCAmount, + testdata.deadline, + testdata.v, + testdata.r, + testdata.s, + testdata.diamondCalldata + ); + + bytes memory callDataWithMsgSenderAppended = abi.encodePacked( + callData, + PERMIT2_USER + ); + + // // call Permit2Proxy from Gelato contract with msgSender appended to calldata + (bool success, ) = address(permit2Proxy).call( + callDataWithMsgSenderAppended + ); + + if (!success) revert(); + + vm.stopPrank(); + } + + function testRevert_RevertsIfPermitApprovalFails() public { + vm.startPrank(PERMIT2_USER); + + // get token-specific domainSeparator + bytes32 domainSeparator = ERC20Permit(ADDRESS_USDC).DOMAIN_SEPARATOR(); + + // // using USDC on ETH for testing (implements EIP2612) + TestDataEIP2612 + memory testdata = _getTestDataEIP2612SignedByPERMIT2_USER( + ADDRESS_USDC, + domainSeparator, + block.timestamp + 1000 + ); + + vm.stopPrank(); + + // send transaction from Gelato contract to mock relayed tx behavior + vm.startPrank(GELATO_1BALANCEERC2771); + + // make all permit calls to USDC revert + vm.mockCallRevert( + ADDRESS_USDC, + abi.encodeWithSelector(ERC20Permit.permit.selector), + abi.encodeWithSignature("Error(string)", "Mocked Call Revert") + ); + + // call Permit2Proxy with signature + bytes memory callData = abi.encodeWithSelector( + permit2Proxy + .callDiamondWithEIP2612SignatureViaTrustedForwarder + .selector, + ADDRESS_USDC, + defaultUSDCAmount, + testdata.deadline, + testdata.v, + testdata.r, + testdata.s, + testdata.diamondCalldata + ); + + bytes memory callDataWithMsgSenderAppended = abi.encodePacked( + callData, + PERMIT2_USER + ); + + // expect the call to revert for the mocked call revert reason as this is bubbled up by the contract + vm.expectRevert("Mocked Call Revert"); + + // // call Permit2Proxy from Gelato contract with msgSender appended to calldata + (bool success, ) = address(permit2Proxy).call( + callDataWithMsgSenderAppended + ); + + if (!success) revert(); + + vm.stopPrank(); + } + + function test_allowsOwnerToUpdateTrustedForwarderAddresses() public { + // make sure address is not trusted yet + assertEq( + ERC2771ContextCustom(address(permit2Proxy)).isTrustedForwarder( + USER_REFUND + ), + false + ); + // prepare arguments for trusted forwarder update + address[] memory trustedForwarders = new address[](1); + trustedForwarders[0] = USER_REFUND; + + bool[] memory isTrusted = new bool[](1); + isTrusted[0] = true; + + // update trusted forwarder addresses + permit2Proxy.setTrustedForwarders(trustedForwarders, isTrusted); + + // make sure update was successful + assertEq( + ERC2771ContextCustom(address(permit2Proxy)).isTrustedForwarder( + USER_REFUND + ), + true + ); + } + + function testRevert_RevertsIfNonOwnerTriesToUpdateTrustedForwarder() + public + { + // make sure address is not trusted yet + assertEq( + ERC2771ContextCustom(address(permit2Proxy)).isTrustedForwarder( + GELATO_1BALANCEERC2771 + ), + true + ); + // prepare arguments for trusted forwarder update + address[] memory trustedForwarders = new address[](1); + trustedForwarders[0] = GELATO_1BALANCEERC2771; + + bool[] memory isTrusted = new bool[](1); + isTrusted[0] = false; + + vm.startPrank(USER_SENDER); + + // expect tx to revert as we call fom untrusted address + vm.expectRevert(UnAuthorized.selector); + + // update trusted forwarder addresses + permit2Proxy.setTrustedForwarders(trustedForwarders, isTrusted); + + // make sure update was successful + assertEq( + ERC2771ContextCustom(address(permit2Proxy)).isTrustedForwarder( + GELATO_1BALANCEERC2771 + ), + true + ); + } + + /// Tx sent by user Tests /// + function test_can_execute_calldata_using_eip2612_signature_usdc() public assertBalanceChange( @@ -261,6 +507,44 @@ contract Permit2ProxyTest is TestBase { vm.stopPrank(); } + function testRevert_RevertsIfPermitCallToTokenFails() public { + vm.startPrank(USER_SENDER); + + // get token-specific domainSeparator + bytes32 domainSeparator = ERC20Permit(ADDRESS_USDC).DOMAIN_SEPARATOR(); + + // // using USDC on ETH for testing (implements EIP2612) + TestDataEIP2612 + memory testdata = _getTestDataEIP2612SignedByPERMIT2_USER( + ADDRESS_USDC, + domainSeparator, + block.timestamp + ); + + // make all permit calls to USDC revert + vm.mockCallRevert( + ADDRESS_USDC, + abi.encodeWithSelector(ERC20Permit.permit.selector), + abi.encodeWithSignature("Error(string)", "Mocked Call Revert") + ); + + // expect call to revert since signature was created by a different address + vm.expectRevert("Mocked Call Revert"); + + // call Permit2Proxy with signature + permit2Proxy.callDiamondWithEIP2612Signature( + ADDRESS_USDC, + defaultUSDCAmount, + testdata.deadline, + testdata.v, + testdata.r, + testdata.s, + testdata.diamondCalldata + ); + + vm.stopPrank(); + } + /// Permit2 specific tests /// function test_user_can_call_diamond_with_own_permit2_signature() public { From f0e81116b4d89cd19df6474914f80b556f05f941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Wed, 22 Jan 2025 08:06:41 +0700 Subject: [PATCH 2/3] deploy to ARB, OPT staging --- deployments/_deployments_log_file.json | 22 +++++ deployments/arbitrum.diamond.staging.json | 2 +- deployments/arbitrum.staging.json | 3 +- deployments/optimism.diamond.staging.json | 2 +- deployments/optimism.staging.json | 3 +- script/deploy/_targetState.json | 98 +++++++++++++++++++++++ 6 files changed, 125 insertions(+), 5 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 821d6bc34..21b3a1061 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -25843,6 +25843,16 @@ "SALT": "", "VERIFIED": "true" } + ], + "1.1.0": [ + { + "ADDRESS": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-01-22 07:56:36", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000d3b2b0ac0afdd0d166a495f5e9fca4ecc715a782000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba30000000000000000000000009e606d0d2bba344b911e2f4eab95d9235a83fe1500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d8253782c45a12053594b9deb72d8e8ab2fca54c", + "SALT": "", + "VERIFIED": "false" + } ] }, "production": { @@ -25896,6 +25906,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.1.0": [ + { + "ADDRESS": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-01-22 07:57:22", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000d3b2b0ac0afdd0d166a495f5e9fca4ecc715a782000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3000000000000000000000000a8892ea3fddef2aa8afb1e3643a3284f978a511400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d8253782c45a12053594b9deb72d8e8ab2fca54c", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "mainnet": { diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index ee64127be..e66be66ae 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -153,7 +153,7 @@ "GasZipPeriphery": "", "LiFiDEXAggregator": "", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", - "Permit2Proxy": "0x6FC01BC9Ff6Cdab694Ec8Ca41B21a2F04C8c37E5", + "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", "ReceiverAcrossV3": "0xe4F3DEF14D61e47c696374453CD64d438FD277F8", "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverStargateV2": "", diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 96b7f9fe6..d8038a66d 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -35,7 +35,6 @@ "DeBridgeDlnFacet": "0xE15C7585636e62b88bA47A40621287086E0c2E33", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "StandardizedCallFacet": "0xA7ffe57ee70Ac4998e9E9fC6f17341173E081A8f", - "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "GenericSwapFacetV3": "0xFf6Fa203573Baaaa4AE375EB7ac2819d539e16FF", "CalldataVerificationFacet": "0x90B5b319cA20D9E466cB5b843952363C34d1b54E", "AcrossFacetPacked": "0x7A3770a9504924d99D38BBba4F0116B756393Eb3", @@ -47,7 +46,7 @@ "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70", "EmergencyPauseFacet": "0x17Bb203F42d8e404ac7E8dB6ff972B7E8473850b", - "Permit2Proxy": "0x6FC01BC9Ff6Cdab694Ec8Ca41B21a2F04C8c37E5", + "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", "AcrossFacetV3": "0x08BfAc22A3B41637edB8A7920754fDb30B18f740", "ReceiverAcrossV3": "0xe4F3DEF14D61e47c696374453CD64d438FD277F8", "AcrossFacetPackedV3": "0x21767081Ff52CE5563A29f27149D01C7127775A2", diff --git a/deployments/optimism.diamond.staging.json b/deployments/optimism.diamond.staging.json index 992511bc2..06447f980 100644 --- a/deployments/optimism.diamond.staging.json +++ b/deployments/optimism.diamond.staging.json @@ -145,7 +145,7 @@ "GasZipPeriphery": "", "LiFiDEXAggregator": "", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", - "Permit2Proxy": "", + "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverStargateV2": "", diff --git a/deployments/optimism.staging.json b/deployments/optimism.staging.json index fd2733047..46b10c321 100644 --- a/deployments/optimism.staging.json +++ b/deployments/optimism.staging.json @@ -40,5 +40,6 @@ "AcrossFacetV3": "0x7A7dA456a99B5C8fef3D3E3c032a9F13949AdF07", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", - "RelayFacet": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5" + "RelayFacet": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5", + "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999" } \ No newline at end of file diff --git a/script/deploy/_targetState.json b/script/deploy/_targetState.json index 248526c3e..6d5f21bdb 100644 --- a/script/deploy/_targetState.json +++ b/script/deploy/_targetState.json @@ -394,6 +394,56 @@ "ReceiverStargateV2": "1.0.0", "SymbiosisFacet": "1.0.0" } + }, + "staging": { + "LiFiDiamond": { + "DiamondCutFacet": "1.0.0", + "DiamondLoupeFacet": "1.0.0", + "OwnershipFacet": "1.0.0", + "DexManagerFacet": "1.0.1", + "AccessManagerFacet": "1.0.0", + "WithdrawFacet": "1.0.0", + "PeripheryRegistryFacet": "1.0.0", + "GenericSwapFacet": "1.0.0", + "GenericSwapFacetV3": "1.0.1", + "LIFuelFacet": "1.0.1", + "CalldataVerificationFacet": "1.1.2", + "StandardizedCallFacet": "1.1.0", + "EmergencyPauseFacet": "1.0.1", + "LiFiDiamond": "1.0.0", + "ERC20Proxy": "1.0.0", + "Executor": "2.0.0", + "FeeCollector": "1.0.0", + "Receiver": "2.0.2", + "LiFuelFeeCollector": "1.0.1", + "TokenWrapper": "1.0.0", + "LiFiDEXAggregator": "1.0.0", + "Permit2Proxy": "1.0.0", + "GasZipPeriphery": "1.0.0", + "AcrossFacet": "2.0.0", + "AcrossFacetPacked": "1.0.0", + "AcrossFacetV3": "1.0.0", + "AcrossFacetPackedV3": "1.0.0", + "ReceiverAcrossV3": "1.0.0", + "AllBridgeFacet": "2.0.0", + "AmarokFacet": "3.0.0", + "AmarokFacetPacked": "1.0.0", + "CBridgeFacet": "1.0.0", + "CBridgeFacetPacked": "1.0.3", + "CelerCircleBridgeFacet": "1.0.1", + "RelayerCelerIM": "2.0.0", + "CelerIMFacetMutable": "2.0.0", + "GasZipFacet": "2.0.0", + "HopFacet": "2.0.0", + "HopFacetPacked": "1.0.6", + "HopFacetOptimized": "2.0.0", + "MayanFacet": "1.0.0", + "SquidFacet": "1.0.0", + "StargateFacet": "2.2.0", + "StargateFacetV2": "1.0.1", + "ReceiverStargateV2": "1.0.0", + "SymbiosisFacet": "1.0.0" + } } }, "optimism": { @@ -444,6 +494,54 @@ "ReceiverStargateV2": "1.0.0", "SymbiosisFacet": "1.0.0" } + }, + "staging": { + "LiFiDiamond": { + "DiamondCutFacet": "1.0.0", + "DiamondLoupeFacet": "1.0.0", + "OwnershipFacet": "1.0.0", + "DexManagerFacet": "1.0.1", + "AccessManagerFacet": "1.0.0", + "WithdrawFacet": "1.0.0", + "PeripheryRegistryFacet": "1.0.0", + "GenericSwapFacet": "1.0.0", + "GenericSwapFacetV3": "1.0.1", + "LIFuelFacet": "1.0.1", + "CalldataVerificationFacet": "1.1.2", + "StandardizedCallFacet": "1.1.0", + "EmergencyPauseFacet": "1.0.1", + "LiFiDiamond": "1.0.0", + "ERC20Proxy": "1.0.0", + "Executor": "2.0.0", + "FeeCollector": "1.0.0", + "Receiver": "2.0.2", + "LiFuelFeeCollector": "1.0.1", + "TokenWrapper": "1.0.0", + "LiFiDEXAggregator": "1.0.0", + "Permit2Proxy": "1.0.0", + "GasZipPeriphery": "1.0.0", + "AcrossFacet": "2.0.0", + "AcrossFacetPacked": "1.0.0", + "AcrossFacetV3": "1.0.0", + "AcrossFacetPackedV3": "1.0.0", + "ReceiverAcrossV3": "1.0.0", + "AllBridgeFacet": "2.0.0", + "AmarokFacet": "3.0.0", + "AmarokFacetPacked": "1.0.0", + "CBridgeFacet": "1.0.0", + "CBridgeFacetPacked": "1.0.3", + "CelerCircleBridgeFacet": "1.0.1", + "RelayerCelerIM": "2.0.0", + "CelerIMFacetMutable": "2.0.0", + "GasZipFacet": "2.0.0", + "HopFacet": "2.0.0", + "HopFacetPacked": "1.0.6", + "HopFacetOptimized": "2.0.0", + "StargateFacet": "2.2.0", + "StargateFacetV2": "1.0.1", + "ReceiverStargateV2": "1.0.0", + "SymbiosisFacet": "1.0.0" + } } }, "moonriver": { From 56115a871c47eada104cf2d022f028d6863b560b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Wed, 22 Jan 2025 08:06:52 +0700 Subject: [PATCH 3/3] update deploy script --- script/deploy/facets/DeployPermit2Proxy.s.sol | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/script/deploy/facets/DeployPermit2Proxy.s.sol b/script/deploy/facets/DeployPermit2Proxy.s.sol index d1553cd3e..86172fca3 100644 --- a/script/deploy/facets/DeployPermit2Proxy.s.sol +++ b/script/deploy/facets/DeployPermit2Proxy.s.sol @@ -42,7 +42,15 @@ contract DeployScript is DeployScriptBase { string memory permit2ProxyConfigJSON = vm.readFile(permit2ProxyConfig); address permit2Address = permit2ProxyConfigJSON.readAddress( - string.concat(".", network) + string.concat(".", network, ".Permit2") + ); + + bytes memory rawForwarders = permit2ProxyConfigJSON.parseRaw( + string.concat(".", network, ".TrustedForwarders") + ); + address[] memory trustedForwarders = abi.decode( + rawForwarders, + (address[]) ); // get the multisig SAFE address for the given network @@ -57,6 +65,12 @@ contract DeployScript is DeployScriptBase { string.concat(".", network, ".safeAddress") ); - return abi.encode(diamond, permit2Address, safeAddress); + return + abi.encode( + diamond, + permit2Address, + safeAddress, + trustedForwarders + ); } }