Permit2Proxy

0x8eabb4e117fb70b346592e013855f6d825f50af1

Verification
Verified
v0.8.29+commit.ab55807c
Type
Contract
8,037 bytes
ABI entries
32
10 read · 7 write
License
none

Contract information

Address
0x8eabb4e117fb70b346592e013855f6d825f50af1
Chain
Robinhood Chain (4663)
Compiler
v0.8.29+commit.ab55807c
Optimization
Enabled
Creation tx
0x955d0df402…ecedb1af61

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (10)

LIFI_DIAMOND()address
PERMIT2()address
PERMIT_WITH_WITNESS_TYPEHASH()bytes32
WITNESS_TYPEHASH()bytes32
WITNESS_TYPE_STRING()string
getPermit2MsgHash(bytes, address, uint256, uint256, uint256)bytes32
nextNonce(address)uint256
nextNonceAfter(address, uint256)uint256
owner()address
pendingOwner()address

Events (3)

OwnershipTransferRequestedOwnershipTransferredTokensWithdrawn

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_lifiDiamond",
        "type": "address"
      },
      {
        "internalType": "contract ISignatureTransfer",
        "name": "_permit2",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_owner",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "CallToDiamondFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExternalCallFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidReceiver",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NewOwnerMustNotBeSelf",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoNullOwner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoPendingOwnershipTransfer",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotPendingOwner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NullAddrIsNotAValidSpender",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NullAddrIsNotAnERC20Token",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnAuthorized",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "_from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "_to",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferRequested",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "assetId",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address payable",
        "name": "receiver",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "TokensWithdrawn",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "LIFI_DIAMOND",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PERMIT2",
    "outputs": [
      {
        "internalType": "contract ISignatureTransfer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PERMIT_WITH_WITNESS_TYPEHASH",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WITNESS_TYPEHASH",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WITNESS_TYPE_STRING",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tokenAddress",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      },
      {
        "internalType": "uint8",
        "name": "v",
        "type": "uint8"
      },
      {
        "internalType": "bytes32",
        "name": "r",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "s",
        "type": "bytes32"
      },
      {
        "internalType": "bytes",
        "name": "diamondCalldata",
        "type": "bytes"
      }
    ],
    "name": "callDiamondWithEIP2612Signature",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "_diamondCalldata",
        "type": "bytes"
      },
      {
        "components": [
          {
            "components": [
              {
                "internalType": "address",
                "name": "token",
                "type": "address"
              },
              {
                "internalType": "uint256",
                "name": "amount",
                "type": "uint256"
              }
            ],
            "internalType": "struct ISignatureTransfer.TokenPermissions",
            "name": "permitted",
            "type": "tuple"
          },
          {
            "internalType": "uint256",
            "name": "nonce",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          }
        ],
        "internalType": "struct ISignatureTransfer.PermitTransferFrom",
        "name": "_permit",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "_signature",
        "type": "bytes"
      }
    ],
    "name": "callDiamondWithPermit2",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "_diamondCalldata",
        "type": "bytes"
      },
      {
        "internalType": "address",
        "name": "_signer",
        "type": "address"
      },
      {
        "components": [
          {
            "components": [
              {
                "internalType": "address",
                "name": "token",
                "type": "address"
              },
              {
                "internalType": "uint256",
                "name": "amount",
                "type": "uint256"
              }
            ],
            "internalType": "struct ISignatureTransfer.TokenPermissions",
            "name": "permitted",
            "type": "tuple"
          },
          {
            "internalType": "uint256",
            "name": "nonce",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          }
        ],
        "internalType": "struct ISignatureTransfer.PermitTransferFrom",
        "name": "_permit",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "_signature",
        "type": "bytes"
      }
    ],
    "name": "callDiamondWithPermit2Witness",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelOwnershipTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "confirmOwnershipTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "_diamondCalldata",
        "type": "bytes"
      },
      {
        "internalType": "address",
        "name": "_assetId",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "_amount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_nonce",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_deadline",
        "type": "uint256"
      }
    ],
    "name": "getPermit2MsgHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "msgHash",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "nextNonce",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "start",
        "type": "uint256"
      }
    ],
    "name": "nextNonceAfter",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "assetId",
        "type": "address"
      },
      {
        "internalType": "address payable",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "withdrawToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: LGPL-3.0-only
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";

/// @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.4
contract Permit2Proxy is WithdrawablePeriphery {
    /// Storage ///

    address public immutable LIFI_DIAMOND;
    ISignatureTransfer public immutable PERMIT2;

    string public constant WITNESS_TYPE_STRING =
        // solhint-disable-next-line max-line-length
        "LiFiCall witness)LiFiCall(address diamondAddress,bytes32 diamondCalldataHash)TokenPermissions(address token,uint256 amount)";
    bytes32 public constant WITNESS_TYPEHASH =
        keccak256(
            "LiFiCall(address diamondAddress,bytes32 diamondCalldataHash)"
        );
    bytes32 public immutable PERMIT_WITH_WITNESS_TYPEHASH;

    /// Types ///

    // @dev LIFI Specific Witness which verifies the correct calldata and
    //      diamond address
    struct LiFiCall {
        address diamondAddress;
        bytes32 diamondCalldataHash;
    }

    /// Errors ///

    error CallToDiamondFailed(bytes);

    /// Constructor ///

    constructor(
        address _lifiDiamond,
        ISignatureTransfer _permit2,
        address _owner
    ) WithdrawablePeriphery(_owner) {
        LIFI_DIAMOND = _lifiDiamond;
        PERMIT2 = _permit2;

        PERMIT_WITH_WITNESS_TYPEHASH = keccak256(
            abi.encodePacked(
                PermitHash._PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB,
                WITNESS_TYPE_STRING
            )
        );
    }

    /// External Functions ///

    /// @notice Allows to bridge tokens through a LI.FI diamond contract using
    /// an EIP2612 gasless permit (only works with tokenAddresses that
    /// implement EIP2612)
    /// The permit signer must be the caller to prevent front-running and ensure
    /// the calldata cannot be replaced by others.
    /// Can only be called by the permit signer to prevent front-running.
    /// @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 callDiamondWithEIP2612Signature(
        address tokenAddress,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s,
        bytes calldata diamondCalldata
    ) public payable returns (bytes memory) {
        // call permit on token contract to register approval using signature
        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);
            }
        } catch (bytes memory reason) {
            if (
                IERC20(tokenAddress).allowance(msg.sender, address(this)) <
                amount
            ) {
                LibUtil.revertWith(reason);
            }
        }

        // deposit assets
        LibAsset.transferFromERC20(
            tokenAddress,
            msg.sender,
            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 of one type through a LI.FI diamond
    ///         contract using Uniswap's Permit2 contract and a user signature
    ///         that verifies allowance. The permit signer must be the caller to prevent front-running and
    ///         ensure the calldata cannot be replaced by others. Can only be called by the permit signer to prevent
    ///         front-running.
    /// @param _diamondCalldata the calldata to execute
    /// @param _permit the Uniswap Permit2 parameters
    /// @param _signature the signature giving approval to transfer tokens
    function callDiamondWithPermit2(
        bytes calldata _diamondCalldata,
        ISignatureTransfer.PermitTransferFrom calldata _permit,
        bytes calldata _signature
    ) external payable returns (bytes memory) {
        PERMIT2.permitTransferFrom(
            _permit,
            ISignatureTransfer.SignatureTransferDetails({
                to: address(this),
                requestedAmount: _permit.permitted.amount
            }),
            msg.sender, // Ensure msg.sender is same wallet that signed permit
            _signature
        );

        // maxApprove token to diamond if current allowance is insufficient
        LibAsset.maxApproveERC20(
            IERC20(_permit.permitted.token),
            LIFI_DIAMOND,
            _permit.permitted.amount
        );

        return _executeCalldata(_diamondCalldata);
    }

    /// @notice Allows to bridge tokens of one type through a LI.FI diamond
    ///         contract using Uniswap's Permit2 contract and a user signature
    ///         that verifies allowance, diamondAddress and diamondCalldata
    /// @param _diamondCalldata the calldata to execute
    /// @param _signer the signer giving permission to transfer tokens
    /// @param _permit the Uniswap Permit2 parameters
    /// @param _signature the signature giving approval to transfer tokens
    function callDiamondWithPermit2Witness(
        bytes calldata _diamondCalldata,
        address _signer,
        ISignatureTransfer.PermitTransferFrom calldata _permit,
        bytes calldata _signature
    ) external payable returns (bytes memory) {
        LiFiCall memory lifiCall = LiFiCall(
            LIFI_DIAMOND,
            keccak256(_diamondCalldata)
        );

        bytes32 witness = keccak256(abi.encode(WITNESS_TYPEHASH, lifiCall));

        PERMIT2.permitWitnessTransferFrom(
            _permit,
            ISignatureTransfer.SignatureTransferDetails({
                to: address(this),
                requestedAmount: _permit.permitted.amount
            }),
            _signer,
            witness,
            WITNESS_TYPE_STRING,
            _signature
        );

        // maxApprove token to diamond if current allowance is insufficient
        LibAsset.maxApproveERC20(
            IERC20(_permit.permitted.token),
            LIFI_DIAMOND,
            _permit.permitted.amount
        );

        return _executeCalldata(_diamondCalldata);
    }

    /// @notice utitlity method for constructing a valid Permit2 message hash
    /// @param _diamondCalldata the calldata to execute
    /// @param _assetId the address of the token to approve
    /// @param _amount amount of tokens to approve
    /// @param _nonce the nonce to use
    /// @param _deadline the expiration deadline
    function getPermit2MsgHash(
        bytes calldata _diamondCalldata,
        address _assetId,
        uint256 _amount,
        uint256 _nonce,
        uint256 _deadline
    ) external view returns (bytes32 msgHash) {
        // Token Permissions
        ISignatureTransfer.TokenPermissions
            memory tokenPermissions = ISignatureTransfer.TokenPermissions(
                _assetId,
                _amount
            );
        bytes32 tokenPermissionsHash = _getTokenPermissionsHash(
            tokenPermissions
        );

        // Witness
        Permit2Proxy.LiFiCall memory lifiCall = LiFiCall(
            LIFI_DIAMOND,
            keccak256(_diamondCalldata)
        );
        bytes32 witness = _getWitnessHash(lifiCall);

        // PermitTransferWithWitness
        msgHash = _getPermitWitnessTransferFromHash(
            PERMIT2.DOMAIN_SEPARATOR(),
            tokenPermissionsHash,
            address(this),
            _nonce,
            _deadline,
            witness
        );
    }

    /// Internal Functions ///

    function _getTokenPermissionsHash(
        ISignatureTransfer.TokenPermissions memory tokenPermissions
    ) internal pure returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    PermitHash._TOKEN_PERMISSIONS_TYPEHASH,
                    tokenPermissions.token,
                    tokenPermissions.amount
                )
            );
    }

    function _getWitnessHash(
        Permit2Proxy.LiFiCall memory lifiCall
    ) internal pure returns (bytes32) {
        return keccak256(abi.encode(WITNESS_TYPEHASH, lifiCall));
    }

    function _getPermitWitnessTransferFromHash(
        bytes32 domainSeparator,
        bytes32 permit,
        address spender,
        uint256 nonce,
        uint256 deadline,
        bytes32 witness
    ) internal view returns (bytes32) {
        bytes32 dataHash = keccak256(
            abi.encode(
                PERMIT_WITH_WITNESS_TYPEHASH,
                permit,
                spender,
                nonce,
                deadline,
                witness
            )
        );

        return
            keccak256(abi.encodePacked("\x19\x01", domainSeparator, dataHash));
    }

    function _executeCalldata(
        bytes memory diamondCalldata
    ) internal returns (bytes memory) {
        // call diamond with provided calldata
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory data) = LIFI_DIAMOND.call{
            value: msg.value
        }(diamondCalldata);

        if (!success) {
            revert CallToDiamondFailed(data);
        }
        return data;
    }

    /// The following code was adapted from
    // solhint-disable-next-line max-line-length
    /// https://github.com/flood-protocol/permit2-nonce-finder/blob/7a4ac8a58d0b499308000b75ddb2384834f31fac/src/Permit2NonceFinder.sol
    /// Provides utility functions for determining the next valid Permit2 nonce

    /// @dev This can be helpful if you're signing multiple nonces in a row and need the next nonce to sign
    ///      but the start one is still valid.
    /// @notice Finds the next valid nonce for a user, starting from 0.
    /// @param owner The owner of the nonces
    /// @return nonce The first valid nonce starting from 0
    function nextNonce(address owner) external view returns (uint256 nonce) {
        nonce = _nextNonce(owner, 0, 0);
    }

    /// @notice Finds the next valid nonce for a user, after from a given nonce.
    /// @dev This can be helpful if you're signing multiple nonces in a row and need the next nonce to sign
    ///      but the start one is still valid.
    /// @param owner The owner of the nonces
    /// @param start The nonce to start from
    /// @return nonce The first valid nonce after the given nonce
    function nextNonceAfter(
        address owner,
        uint256 start
    ) external view returns (uint256 nonce) {
        uint248 word = uint248(start >> 8);
        uint8 pos = uint8(start);
        if (pos == type(uint8).max) {
            // If the position is 255, we need to move to the next word
            word++;
            pos = 0;
        } else {
            // Otherwise, we just move to the next position
            pos++;
        }
        nonce = _nextNonce(owner, word, pos);
    }

    /// @notice Finds the next valid nonce for a user, starting from a given word and position.
    /// @param owner The owner of the nonces
    /// @param word Word to start looking from
    /// @param pos Position inside the word to start looking from
    function _nextNonce(
        address owner,
        uint248 word,
        uint8 pos
    ) internal view returns (uint256 nonce) {
        while (true) {
            uint256 bitmap = PERMIT2.nonceBitmap(owner, word);

            // Check if the bitmap is completely full
            if (bitmap == type(uint256).max) {
                // If so, move to the next word
                ++word;
                pos = 0;
                continue;
            }
            if (pos != 0) {
                // If the position is not 0, we need to shift the bitmap to ignore the bits before position
                bitmap = bitmap >> pos;
            }
            // Find the first zero bit in the bitmap
            while (bitmap & 1 == 1) {
                bitmap = bitmap >> 1;
                ++pos;
            }

            return _nonceFromWordAndPos(word, pos);
        }
    }

    /// @notice Constructs a nonce from a word and a position inside the word
    /// @param word The word containing the nonce
    /// @param pos The position of the nonce inside the word
    /// @return nonce The nonce constructed from the word and position
    function _nonceFromWordAndPos(
        uint248 word,
        uint8 pos
    ) internal pure returns (uint256 nonce) {
        // The last 248 bits of the word are the nonce bits
        nonce = uint256(word) << 8;
        // The first 8 bits of the word are the position inside the word
        nonce |= pos;
    }

    // required to be able to receive native token refunds from the diamond
    receive() external payable {}
}
Chain explorer4676msChain node86ms