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_OrderAlreadyCancelled(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_OrderAlreadyCancelled(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
}
}}
Submitted on: 2025-09-19 12:48:02
Comments
Log in to comment.
No comments yet.