PMMProtocol

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/PmmProtocol.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./EIP712.sol";
import "./helpers/AmountCalculator.sol";
import "./interfaces/IPMMSettler.sol";
import "./interfaces/IWETH.sol";
import "./libraries/Errors.sol";
import "./libraries/SafeERC20.sol";
import "./OrderRFQLib.sol";

contract PMMProtocol is EIP712 {
    using SafeERC20 for IERC20;
    using OrderRFQLib for OrderRFQLib.OrderRFQ;

    /**
     * @notice Emitted when RFQ gets filled
     * @param rfqId RFQ order id
     * @param expiry Expiration timestamp of the order
     * @param makerAsset Address of the maker asset
     * @param takerAsset Address of the taker asset
     * @param makerAddress Address of the maker
     * @param expectedMakerAmount Expected amount of maker asset
     * @param expectedTakerAmount Expected amount of taker asset
     * @param filledMakerAmount Actual amount of maker asset that was transferred
     * @param filledTakerAmount Actual amount of taker asset that was transferred
     */
    event OrderFilledRFQ(
        uint256 indexed rfqId,
        uint256 expiry,
        address indexed makerAsset,
        address indexed takerAsset,
        address makerAddress,
        uint256 expectedMakerAmount,
        uint256 expectedTakerAmount,
        uint256 filledMakerAmount,
        uint256 filledTakerAmount,
        bool usePermit2
    );

    /**
     * @notice Emitted when RFQ gets cancelled
     * @param rfqId RFQ order id
     * @param maker Maker address
     */
    event OrderCancelledRFQ(uint256 indexed rfqId, address indexed maker);

    string private constant _NAME = "OKX Lab PMM Protocol";
    string private constant _VERSION = "1.0";

    uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
    uint256 private constant _MAKER_AMOUNT_FLAG = 1 << 255;
    uint256 private constant _SIGNER_SMART_CONTRACT_HINT = 1 << 254;
    uint256 private constant _IS_VALID_SIGNATURE_65_BYTES = 1 << 253;
    uint256 private constant _UNWRAP_WETH_FLAG = 1 << 252;

    uint256 private constant _AMOUNT_MASK =
        0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff; // max uint160

    IWETH private _WETH;
    mapping(address => mapping(uint256 => uint256)) private _invalidator;

    constructor(IWETH weth) EIP712(_NAME, _VERSION) {
        _WETH = weth;
    }

    receive() external payable {
        if (msg.sender != address(_WETH))
            revert Errors.RFQ_EthDepositRejected();
    }

    function DOMAIN_SEPARATOR() external view returns (bytes32) {
        return _domainSeparatorV4();
    }

    function invalidatorForOrderRFQ(
        address maker,
        uint256 slot
    ) external view returns (uint256 /* result */) {
        return _invalidator[maker][slot];
    }

    function isRfqIdUsed(
        address maker,
        uint64 rfqId
    ) public view returns (bool) {
        uint256 invalidatorSlot = uint64(rfqId) >> 8;
        uint256 invalidatorBits = 1 << (uint8(rfqId) & 0xff);
        uint256 bitMap = _invalidator[maker][invalidatorSlot];
        return (bitMap & invalidatorBits) != 0;
    }

    function fillOrderRFQ(
        OrderRFQLib.OrderRFQ memory order,
        bytes calldata signature,
        uint256 flagsAndAmount
    )
        external
        payable
        returns (
            uint256 /* filledMakerAmount */,
            uint256 /* filledTakerAmount */,
            bytes32 /* orderHash */
        )
    {
        return fillOrderRFQTo(order, signature, flagsAndAmount, msg.sender);
    }

    function fillOrderRFQCompact(
        OrderRFQLib.OrderRFQ memory order,
        bytes32 r,
        bytes32 vs,
        uint256 flagsAndAmount
    )
        external
        payable
        returns (
            uint256 filledMakerAmount,
            uint256 filledTakerAmount,
            bytes32 orderHash
        )
    {
        orderHash = order.hash(_domainSeparatorV4());
        if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) {
            if (flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0) {
                if (
                    !ECDSA.isValidSignature65(
                        order.makerAddress,
                        orderHash,
                        r,
                        vs
                    )
                ) revert Errors.RFQ_BadSignature(order.rfqId);
            } else {
                if (
                    !ECDSA.isValidSignature(
                        order.makerAddress,
                        orderHash,
                        r,
                        vs
                    )
                ) revert Errors.RFQ_BadSignature(order.rfqId);
            }
        } else {
            if (
                !ECDSA.recoverOrIsValidSignature(
                    order.makerAddress,
                    orderHash,
                    r,
                    vs
                )
            ) revert Errors.RFQ_BadSignature(order.rfqId);
        }

        (filledMakerAmount, filledTakerAmount) = _fillOrderRFQTo(
            order,
            flagsAndAmount,
            msg.sender
        );
        emit OrderFilledRFQ(
            order.rfqId,
            order.expiry,
            order.makerAsset,
            order.takerAsset,
            order.makerAddress,
            order.makerAmount,
            order.takerAmount,
            filledMakerAmount,
            filledTakerAmount,
            order.usePermit2
        );
    }

    function fillOrderRFQToWithPermit(
        OrderRFQLib.OrderRFQ memory order,
        bytes calldata signature,
        uint256 flagsAndAmount,
        address target,
        bytes calldata permit
    )
        external
        returns (
            uint256 /* filledMakerAmount */,
            uint256 /* filledTakerAmount */,
            bytes32 /* orderHash */
        )
    {
        IERC20(order.takerAsset).safePermit(permit);
        return fillOrderRFQTo(order, signature, flagsAndAmount, target);
    }
    // Anyone can fill the order, including via front-running.
    // This is acceptable by design and does not cause any loss to the maker.

    // This function does not support deflationary or rebasing tokens.
    // The protocol assumes standard token behavior with exact transfer amounts,
    // which is valid for mainstream tokens typically used by market makers.
    function fillOrderRFQTo(
        OrderRFQLib.OrderRFQ memory order,
        bytes calldata signature,
        uint256 flagsAndAmount,
        address target
    )
        public
        payable
        returns (
            uint256 filledMakerAmount,
            uint256 filledTakerAmount,
            bytes32 orderHash
        )
    {
        orderHash = order.hash(_domainSeparatorV4());
        if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) {
            if (
                flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0 &&
                signature.length != 65
            ) revert Errors.RFQ_BadSignature(order.rfqId);
            if (
                !ECDSA.isValidSignature(
                    order.makerAddress,
                    orderHash,
                    signature
                )
            ) revert Errors.RFQ_BadSignature(order.rfqId);
        } else {
            if (
                !ECDSA.recoverOrIsValidSignature(
                    order.makerAddress,
                    orderHash,
                    signature
                )
            ) revert Errors.RFQ_BadSignature(order.rfqId);
        }
        (filledMakerAmount, filledTakerAmount) = _fillOrderRFQTo(
            order,
            flagsAndAmount,
            target
        );
        emit OrderFilledRFQ(
            order.rfqId,
            order.expiry,
            order.makerAsset,
            order.takerAsset,
            order.makerAddress,
            order.makerAmount,
            order.takerAmount,
            filledMakerAmount,
            filledTakerAmount,
            order.usePermit2
        );
    }

    function _fillOrderRFQTo(
        OrderRFQLib.OrderRFQ memory order,
        uint256 flagsAndAmount,
        address target
    ) private returns (uint256 makerAmount, uint256 takerAmount) {
        if (target == address(0))
            revert Errors.RFQ_ZeroTargetIsForbidden(order.rfqId);

        address maker = order.makerAddress;

        {
            // Stack too deep
            // Check time expiration
            uint256 expiration = order.expiry;
            if (expiration != 0 && block.timestamp > expiration)
                revert Errors.RFQ_OrderExpired(order.rfqId); // solhint-disable-line not-rely-on-time
            _invalidateOrder(maker, order.rfqId, 0);
        }
        // user: AMM->PMM
        {
            // Stack too deep
            uint256 orderMakerAmount = order.makerAmount;
            uint256 orderTakerAmount = order.takerAmount;
            uint256 amount = flagsAndAmount & _AMOUNT_MASK;
            // Compute partial fill if needed
            if (amount == 0) {
                // zero amount means whole order
                makerAmount = orderMakerAmount;
                takerAmount = orderTakerAmount;
            } else if (flagsAndAmount & _MAKER_AMOUNT_FLAG != 0) {
                if (amount > orderMakerAmount)
                    revert Errors.RFQ_MakerAmountExceeded(order.rfqId);
                makerAmount = amount;
                takerAmount = AmountCalculator.getTakerAmount(
                    orderMakerAmount,
                    orderTakerAmount,
                    makerAmount
                );
            } else {
                if (amount > orderTakerAmount)
                    revert Errors.RFQ_TakerAmountExceeded(order.rfqId);
                takerAmount = amount;
                makerAmount = AmountCalculator.getMakerAmount(
                    orderMakerAmount,
                    orderTakerAmount,
                    takerAmount
                );
            }
        }

        if (makerAmount == 0 || takerAmount == 0)
            revert Errors.RFQ_SwapWithZeroAmount(order.rfqId);

        bool needUnwrap = order.makerAsset == address(_WETH) &&
            flagsAndAmount & _UNWRAP_WETH_FLAG != 0;

        // Maker => Taker
        address receiver = needUnwrap ? address(this) : target;
        if (order.usePermit2) {
            IERC20(order.makerAsset).safeTransferFromPermit2(
                maker,
                receiver,
                makerAmount
            );
        } else {
            IERC20(order.makerAsset).safeTransferFrom(
                maker,
                receiver,
                makerAmount
            );
        }
        if (needUnwrap) {
            _WETH.withdraw(makerAmount);
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, ) = target.call{
                value: makerAmount,
                gas: _RAW_CALL_GAS_LIMIT
            }("");
            if (!success) revert Errors.RFQ_ETHTransferFailed(order.rfqId);
        }

        // Taker => Maker
        if (order.takerAsset == address(_WETH) && msg.value > 0) {
            if (msg.value != takerAmount)
                revert Errors.RFQ_InvalidMsgValue(order.rfqId);
            _WETH.deposit{value: takerAmount}();
            _WETH.transfer(maker, takerAmount);
        } else {
            if (msg.value != 0) revert Errors.RFQ_InvalidMsgValue(order.rfqId);
            IERC20(order.takerAsset).safeTransferFrom(
                msg.sender,
                maker,
                takerAmount
            );
        }
    }

    /// @dev Prevents replay of RFQ orders by tracking used rfqIds with a bitmask per maker.
    function _invalidateOrder(
        address maker,
        uint256 orderInfo,
        uint256 additionalMask
    ) private {
        uint256 invalidatorSlot = uint64(orderInfo) >> 8;
        uint256 invalidatorBits = (1 << uint8(orderInfo)) | additionalMask;
        mapping(uint256 => uint256) storage invalidatorStorage = _invalidator[
            maker
        ];
        uint256 invalidator = invalidatorStorage[invalidatorSlot];
        if (invalidator & invalidatorBits == invalidatorBits)
            revert Errors.RFQ_InvalidatedOrder(orderInfo);
        invalidatorStorage[invalidatorSlot] = invalidator | invalidatorBits;
    }

    function cancelOrderRFQ(uint64 rfqId) external {
        address maker = msg.sender;
        if (isRfqIdUsed(maker, rfqId))
            revert Errors.RFQ_OrderAlreadyCancelledOrUsed(rfqId);
        _invalidateOrder(maker, rfqId, 0);
        emit OrderCancelledRFQ(rfqId, maker);
    }
}
"
    },
    "src/EIP712.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
 * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
 * they need in their contracts using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * _Available since v3.4._
 */
import "./libraries/ECDSA.sol";
abstract contract EIP712 {
    /* solhint-disable var-name-mixedcase */
    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
    uint256 private immutable _CACHED_CHAIN_ID;
    address private immutable _CACHED_THIS;

    bytes32 private immutable _HASHED_NAME;
    bytes32 private immutable _HASHED_VERSION;
    bytes32 private immutable _TYPE_HASH;

    /* solhint-enable var-name-mixedcase */

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        bytes32 hashedName = keccak256(bytes(name));
        bytes32 hashedVersion = keccak256(bytes(version));
        bytes32 typeHash = keccak256(
            "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
        );
        _HASHED_NAME = hashedName;
        _HASHED_VERSION = hashedVersion;
        _CACHED_CHAIN_ID = block.chainid;
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(
            typeHash,
            hashedName,
            hashedVersion
        );
        _CACHED_THIS = address(this);
        _TYPE_HASH = typeHash;
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (
            address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID
        ) {
            return _CACHED_DOMAIN_SEPARATOR;
        } else {
            return
                _buildDomainSeparator(
                    _TYPE_HASH,
                    _HASHED_NAME,
                    _HASHED_VERSION
                );
        }
    }

    function _buildDomainSeparator(
        bytes32 typeHash,
        bytes32 nameHash,
        bytes32 versionHash
    ) private view returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    typeHash,
                    nameHash,
                    versionHash,
                    block.chainid,
                    address(this)
                )
            );
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(
        bytes32 structHash
    ) internal view virtual returns (bytes32) {
        return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
    }
}
"
    },
    "src/helpers/AmountCalculator.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/// @title A helper contract for calculations related to order amounts
library AmountCalculator {
    /// @notice Calculates maker amount
    /// @return Result Floored maker amount
    function getMakerAmount(
        uint256 orderMakerAmount,
        uint256 orderTakerAmount,
        uint256 swapTakerAmount
    ) internal pure returns (uint256) {
        return (swapTakerAmount * orderMakerAmount) / orderTakerAmount;
    }

    /// @notice Calculates taker amount
    /// @return Result Ceiled taker amount

    function getTakerAmount(
        uint256 orderMakerAmount,
        uint256 orderTakerAmount,
        uint256 swapMakerAmount
    ) internal pure returns (uint256) {
        return
            (swapMakerAmount * orderTakerAmount + orderMakerAmount - 1) /
            orderMakerAmount;
    }
}
"
    },
    "src/interfaces/IPMMSettler.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

interface IPMMSettler {
    /**
     * @notice Interface for interactor which acts for `maker -> taker` transfers.
     * @param taker Taker address
     * @param token Settle token address
     * @param amount Settle token amount
     * @param isUnwrap Whether unwrap WETH
     */
    function settleToTaker(
        address taker,
        address token,
        uint256 amount,
        bool isUnwrap
    ) external;

    /**
     * @notice Returns the settlement treasury address.
     */
    function getTreasury() external view returns (address);
}
"
    },
    "src/interfaces/IWETH.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWETH is IERC20 {
    function deposit() external payable;

    function withdraw(uint256 amount) external;
}
"
    },
    "src/libraries/Errors.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

library Errors {
    error RFQ_InvalidMsgValue(uint256 rfqId);
    error RFQ_ETHTransferFailed(uint256 rfqId);
    error RFQ_EthDepositRejected();
    error RFQ_ZeroTargetIsForbidden(uint256 rfqId);
    error RFQ_BadSignature(uint256 rfqId);
    error RFQ_OrderExpired(uint256 rfqId);
    error RFQ_MakerAmountExceeded(uint256 rfqId);
    error RFQ_TakerAmountExceeded(uint256 rfqId);
    error RFQ_SwapWithZeroAmount(uint256 rfqId);
    error RFQ_InvalidatedOrder(uint256 rfqId);
    error RFQ_OrderAlreadyCancelledOrUsed(uint256 rfqId);
    error RFQ_NotOrderMaker(uint256 rfqId);
}
"
    },
    "src/libraries/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
import "../interfaces/IDaiLikePermit.sol";
import "../interfaces/IPermit2.sol";
import "../libraries/RevertReasonForwarder.sol";

/// @title Implements efficient safe methods for ERC20 interface.
library SafeERC20 {
    error SafeTransferFailed();
    error SafeTransferFromFailed();
    error ForceApproveFailed();
    error SafeIncreaseAllowanceFailed();
    error SafeDecreaseAllowanceFailed();
    error SafePermitBadLength();
    error Permit2TransferAmountTooHigh();

    address public constant _PERMIT2 =
        0x000000000022D473030F116dDEE9F6B43aC78BA3;
    /**
     * @notice Attempts to safely transfer tokens from one address to another using the Permit2 standard.
     * @dev Either requires `true` in return data, or requires target to be smart-contract and empty return data.
     * Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
     * the caller to make sure that the higher 96 bits of the `from` and `to` parameters are clean.
     * @param token The IERC20 token contract from which the tokens will be transferred.
     * @param from The address from which the tokens will be transferred.
     * @param to The address to which the tokens will be transferred.
     * @param amount The amount of tokens to transfer.
     */
    function safeTransferFromPermit2(
        IERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        if (amount > type(uint160).max) revert Permit2TransferAmountTooHigh();
        bytes4 selector = IPermit2.transferFrom.selector;
        bool success;
        assembly ("memory-safe") {
            // solhint-disable-line no-inline-assembly
            let data := mload(0x40)

            mstore(data, selector)
            mstore(add(data, 0x04), from)
            mstore(add(data, 0x24), to)
            mstore(add(data, 0x44), amount)
            mstore(add(data, 0x64), token)
            success := call(gas(), _PERMIT2, 0, data, 0x84, 0x0, 0x0)
            if success {
                success := gt(extcodesize(_PERMIT2), 0)
            }
        }
        if (!success) revert SafeTransferFromFailed();
    }

    // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract
    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bytes4 selector = token.transferFrom.selector;
        bool success;
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let data := mload(0x40)

            mstore(data, selector)
            mstore(add(data, 0x04), from)
            mstore(add(data, 0x24), to)
            mstore(add(data, 0x44), amount)
            success := call(gas(), token, 0, data, 100, 0x0, 0x20)
            if success {
                switch returndatasize()
                case 0 {
                    success := gt(extcodesize(token), 0)
                }
                default {
                    success := and(gt(returndatasize(), 31), eq(mload(0), 1))
                }
            }
        }
        if (!success) revert SafeTransferFromFailed();
    }

    // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        if (!_makeCall(token, token.transfer.selector, to, value)) {
            revert SafeTransferFailed();
        }
    }

    // If `approve(from, to, amount)` fails, try to `approve(from, to, 0)` before retry
    function forceApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        if (!_makeCall(token, token.approve.selector, spender, value)) {
            if (
                !_makeCall(token, token.approve.selector, spender, 0) ||
                !_makeCall(token, token.approve.selector, spender, value)
            ) {
                revert ForceApproveFailed();
            }
        }
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 allowance = token.allowance(address(this), spender);
        if (value > type(uint256).max - allowance)
            revert SafeIncreaseAllowanceFailed();
        forceApprove(token, spender, allowance + value);
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 allowance = token.allowance(address(this), spender);
        if (value > allowance) revert SafeDecreaseAllowanceFailed();
        forceApprove(token, spender, allowance - value);
    }

    function safePermit(IERC20 token, bytes calldata permit) internal {
        bool success;
        if (permit.length == 32 * 7) {
            success = _makeCalldataCall(
                token,
                IERC20Permit.permit.selector,
                permit
            );
        } else if (permit.length == 32 * 8) {
            success = _makeCalldataCall(
                token,
                IDaiLikePermit.permit.selector,
                permit
            );
        } else {
            revert SafePermitBadLength();
        }
        if (!success) RevertReasonForwarder.reRevert();
    }

    function _makeCall(
        IERC20 token,
        bytes4 selector,
        address to,
        uint256 amount
    ) private returns (bool success) {
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let data := mload(0x40)

            mstore(data, selector)
            mstore(add(data, 0x04), to)
            mstore(add(data, 0x24), amount)
            success := call(gas(), token, 0, data, 0x44, 0x0, 0x20)
            if success {
                switch returndatasize()
                case 0 {
                    success := gt(extcodesize(token), 0)
                }
                default {
                    success := and(gt(returndatasize(), 31), eq(mload(0), 1))
                }
            }
        }
    }

    function _makeCalldataCall(
        IERC20 token,
        bytes4 selector,
        bytes calldata args
    ) private returns (bool success) {
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let len := add(4, args.length)
            let data := mload(0x40)

            mstore(data, selector)
            calldatacopy(add(data, 0x04), args.offset, args.length)
            success := call(gas(), token, 0, data, len, 0x0, 0x20)
            if success {
                switch returndatasize()
                case 0 {
                    success := gt(extcodesize(token), 0)
                }
                default {
                    success := and(gt(returndatasize(), 31), eq(mload(0), 1))
                }
            }
        }
    }
}
"
    },
    "src/OrderRFQLib.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./libraries/ECDSA.sol";

library OrderRFQLib {
    struct OrderRFQ {
        uint256 rfqId; // 0x00
        uint256 expiry; // 0x20
        address makerAsset; // 0x40
        address takerAsset; // 0x60
        address makerAddress; // 0x80
        uint256 makerAmount; // 0xa0
        uint256 takerAmount; // 0xc0
        bool usePermit2; // 0xe0;
    }

    bytes32 internal constant _LIMIT_ORDER_RFQ_TYPEHASH =
        keccak256(
            "OrderRFQ("
            "uint256 rfqId,"
            "uint256 expiry,"
            "address makerAsset,"
            "address takerAsset,"
            "address makerAddress,"
            "uint256 makerAmount,"
            "uint256 takerAmount,"
            "bool usePermit2"
            ")"
        );
        
    function hash(
        OrderRFQ memory order,
        bytes32 domainSeparator
    ) internal pure returns (bytes32 result) {
        // Manually encoding each field instead of abi.encode(..., order)
        // to avoid Yul "stack too deep" errors caused by expanding memory structs.
        bytes32 structHash = keccak256(
            abi.encode(
                _LIMIT_ORDER_RFQ_TYPEHASH,
                order.rfqId,
                order.expiry,
                order.makerAsset,
                order.takerAsset,
                order.makerAddress,
                order.makerAmount,
                order.takerAmount,
                order.usePermit2
            )
        );
        return ECDSA.toTypedDataHash(domainSeparator, structHash);
    }
}
"
    },
    "src/libraries/ECDSA.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/interfaces/IERC1271.sol";

library ECDSA {
    // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
    // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
    // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
    // signatures from current libraries generate a unique signature with an s-value in the lower half order.
    //
    // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
    // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
    // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
    // these malleable signatures as well.
    uint256 private constant _S_BOUNDARY =
        0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 + 1;
    uint256 private constant _COMPACT_S_MASK =
        0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
    uint256 private constant _COMPACT_V_SHIFT = 255;

    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal view returns (address signer) {
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            if lt(s, _S_BOUNDARY) {
                let ptr := mload(0x40)

                mstore(ptr, hash)
                mstore(add(ptr, 0x20), v)
                mstore(add(ptr, 0x40), r)
                mstore(add(ptr, 0x60), s)
                mstore(0, 0)
                pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20))
                signer := mload(0)
            }
        }
    }

    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal view returns (address signer) {
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let s := and(vs, _COMPACT_S_MASK)
            if lt(s, _S_BOUNDARY) {
                let ptr := mload(0x40)

                mstore(ptr, hash)
                mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs)))
                mstore(add(ptr, 0x40), r)
                mstore(add(ptr, 0x60), s)
                mstore(0, 0)
                pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20))
                signer := mload(0)
            }
        }
    }

    /// @dev WARNING!!!
    /// There is a known signature malleability issue with two representations of signatures!
    /// Even though this function is able to verify both standard 65-byte and compact 64-byte EIP-2098 signatures
    /// one should never use raw signatures for any kind of invalidation logic in their code.
    /// As the standard and compact representations are interchangeable any invalidation logic that relies on
    /// signature uniqueness will get rekt.
    /// More info: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-4h98-2769-gh6h
    function recover(
        bytes32 hash,
        bytes calldata signature
    ) internal view returns (address signer) {
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let ptr := mload(0x40)

            // memory[ptr:ptr+0x80] = (hash, v, r, s)
            switch signature.length
            case 65 {
                // memory[ptr+0x20:ptr+0x80] = (v, r, s)
                mstore(
                    add(ptr, 0x20),
                    byte(0, calldataload(add(signature.offset, 0x40)))
                )
                calldatacopy(add(ptr, 0x40), signature.offset, 0x40)
            }
            case 64 {
                // memory[ptr+0x20:ptr+0x80] = (v, r, s)
                let vs := calldataload(add(signature.offset, 0x20))
                mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs)))
                calldatacopy(add(ptr, 0x40), signature.offset, 0x20)
                mstore(add(ptr, 0x60), and(vs, _COMPACT_S_MASK))
            }
            default {
                ptr := 0
            }

            if ptr {
                if lt(mload(add(ptr, 0x60)), _S_BOUNDARY) {
                    // memory[ptr:ptr+0x20] = (hash)
                    mstore(ptr, hash)

                    mstore(0, 0)
                    pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20))
                    signer := mload(0)
                }
            }
        }
    }

    function recoverOrIsValidSignature(
        address signer,
        bytes32 hash,
        bytes calldata signature
    ) internal view returns (bool success) {
        if (signer == address(0)) return false;
        if (
            (signature.length == 64 || signature.length == 65) &&
            recover(hash, signature) == signer
        ) {
            return true;
        }
        return isValidSignature(signer, hash, signature);
    }

    function recoverOrIsValidSignature(
        address signer,
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal view returns (bool success) {
        if (signer == address(0)) return false;
        if (recover(hash, v, r, s) == signer) {
            return true;
        }
        return isValidSignature(signer, hash, v, r, s);
    }

    function recoverOrIsValidSignature(
        address signer,
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal view returns (bool success) {
        if (signer == address(0)) return false;
        if (recover(hash, r, vs) == signer) {
            return true;
        }
        return isValidSignature(signer, hash, r, vs);
    }

    function recoverOrIsValidSignature65(
        address signer,
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal view returns (bool success) {
        if (signer == address(0)) return false;
        if (recover(hash, r, vs) == signer) {
            return true;
        }
        return isValidSignature65(signer, hash, r, vs);
    }

    function isValidSignature(
        address signer,
        bytes32 hash,
        bytes calldata signature
    ) internal view returns (bool success) {
        // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature));
        // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector;
        bytes4 selector = IERC1271.isValidSignature.selector;
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let ptr := mload(0x40)

            mstore(ptr, selector)
            mstore(add(ptr, 0x04), hash)
            mstore(add(ptr, 0x24), 0x40)
            mstore(add(ptr, 0x44), signature.length)
            calldatacopy(add(ptr, 0x64), signature.offset, signature.length)
            if staticcall(
                gas(),
                signer,
                ptr,
                add(0x64, signature.length),
                0,
                0x20
            ) {
                success := and(
                    eq(selector, mload(0)),
                    eq(returndatasize(), 0x20)
                )
            }
        }
    }

    function isValidSignature(
        address signer,
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal view returns (bool success) {
        bytes4 selector = IERC1271.isValidSignature.selector;
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let ptr := mload(0x40)

            mstore(ptr, selector)
            mstore(add(ptr, 0x04), hash)
            mstore(add(ptr, 0x24), 0x40)
            mstore(add(ptr, 0x44), 65)
            mstore(add(ptr, 0x64), r)
            mstore(add(ptr, 0x84), s)
            mstore8(add(ptr, 0xa4), v)
            if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) {
                success := and(
                    eq(selector, mload(0)),
                    eq(returndatasize(), 0x20)
                )
            }
        }
    }

    function isValidSignature(
        address signer,
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal view returns (bool success) {
        // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs)));
        // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector;
        bytes4 selector = IERC1271.isValidSignature.selector;
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let ptr := mload(0x40)

            mstore(ptr, selector)
            mstore(add(ptr, 0x04), hash)
            mstore(add(ptr, 0x24), 0x40)
            mstore(add(ptr, 0x44), 64)
            mstore(add(ptr, 0x64), r)
            mstore(add(ptr, 0x84), vs)
            if staticcall(gas(), signer, ptr, 0xa4, 0, 0x20) {
                success := and(
                    eq(selector, mload(0)),
                    eq(returndatasize(), 0x20)
                )
            }
        }
    }

    function isValidSignature65(
        address signer,
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal view returns (bool success) {
        // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs & ~uint256(1 << 255), uint8(vs >> 255))));
        // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector;
        bytes4 selector = IERC1271.isValidSignature.selector;
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let ptr := mload(0x40)

            mstore(ptr, selector)
            mstore(add(ptr, 0x04), hash)
            mstore(add(ptr, 0x24), 0x40)
            mstore(add(ptr, 0x44), 65)
            mstore(add(ptr, 0x64), r)
            mstore(add(ptr, 0x84), and(vs, _COMPACT_S_MASK))
            mstore8(add(ptr, 0xa4), add(27, shr(_COMPACT_V_SHIFT, vs)))
            if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) {
                success := and(
                    eq(selector, mload(0)),
                    eq(returndatasize(), 0x20)
                )
            }
        }
    }

    function toEthSignedMessageHash(
        bytes32 hash
    ) internal pure returns (bytes32 res) {
        // 32 is the length in bytes of hash, enforced by the type signature above
        // return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\
32", hash));
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            mstore(
                0,
                0x19457468657265756d205369676e6564204d6573736167653a0a333200000000
            ) // "\x19Ethereum Signed Message:\
32"
            mstore(28, hash)
            res := keccak256(0, 60)
        }
    }

    function toTypedDataHash(
        bytes32 domainSeparator,
        bytes32 structHash
    ) internal pure returns (bytes32 res) {
        // return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let ptr := mload(0x40)
            mstore(
                ptr,
                0x1901000000000000000000000000000000000000000000000000000000000000
            ) // "\x19\x01"
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            res := keccak256(ptr, 66)
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/draft-IERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
"
    },
    "src/interfaces/IDaiLikePermit.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IDaiLikePermit {
    function permit(
        address holder,
        address spender,
        uint256 nonce,
        uint256 expiry,
        bool allowed,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
}
"
    },
    "src/interfaces/IPermit2.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;
interface IPermit2 {
    struct PermitDetails {
        // ERC20 token address
        address token;
        // the maximum amount allowed to spend
        uint160 amount;
        // timestamp at which a spender's token allowances become invalid
        uint48 expiration;
        // an incrementing value indexed per owner,token,and spender for each signature
        uint48 nonce;
    }
    /// @notice The permit message signed for a single token allownce
    struct PermitSingle {
        // the permit data for a single token alownce
        PermitDetails details;
        // address permissioned on the allowed tokens
        address spender;
        // deadline on the permit signature
        uint256 sigDeadline;
    }
    /// @notice Packed allowance
    struct PackedAllowance {
        // amount allowed
        uint160 amount;
        // permission expiry
        uint48 expiration;
        // an incrementing value indexed per owner,token,and spender for each signature
        uint48 nonce;
    }

    function transferFrom(
        address user,
        address spender,
        uint160 amount,
        address token
    ) external;

    function permit(
        address owner,
        PermitSingle memory permitSingle,
        bytes calldata signature
    ) external;

    function allowance(
        address user,
        address token,
        address spender
    ) external view returns (PackedAllowance memory);

    /// @notice Approves the spender to use up to amount of the specified token up until the expiration
    /// @param token The token to approve
    /// @param spender The spender address to approve
    /// @param amount The approved amount of the token
    /// @param expiration The timestamp at which the approval is no longer valid
    /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
    /// @dev Setting amount to type(uint160).max sets an unlimited approval
    function approve(address token, address spender, uint160 amount, uint48 expiration) external;
}
"
    },
    "src/libraries/RevertReasonForwarder.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Revert reason forwarder.
library RevertReasonForwarder {
    function reRevert() internal pure {
        // bubble up revert reason from latest external call
        /// @solidity memory-safe-assembly
        assembly {
            // solhint-disable-line no-inline-assembly
            let ptr := mload(0x40)
            returndatacopy(ptr, 0, returndatasize())
            revert(ptr, returndatasize())
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC1271 standard signature validation method for
 * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
 *
 * _Available since v4.1._
 */
interface IERC1271 {
    /**
     * @dev Should return whether the signature provided is valid for the provided data
     * @param hash      Hash of the data to be signed
     * @param signature Signature byte array associated with _data
     */
    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}
"
    }
  },
  "settings": {
    "remappings": [
      "forge-std/=lib/forge-std/src/",
      "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
      "ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
      "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
      "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
      "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
      "openzeppelin-contracts/=lib/openzeppelin-contracts/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs"
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "london",
    "viaIR": true
  }
}}

Tags:
ERC20, Proxy, Swap, Upgradeable, Factory|addr:0x2bd541ab3b704f7d4c9dff79efadeaa85ec034f1|verified:true|block:23395278|tx:0xacda054964ef9ea825c54badd4e2790260ebab4d9d9c01abab3e95889d65025f|first_check:1758278883

Submitted on: 2025-09-19 12:48:04

Comments

Log in to comment.

No comments yet.