TradeReactor

Description:

ERC20 token contract with Factory capabilities. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "market/TradeReactor.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import {SignatureTransfer} from "./SignatureTransfer.sol";\r
import {ISignatureTransfer} from "./ISignatureTransfer.sol";\r
import {IERC20Permit} from "../ERC20/IERC20Permit.sol";\r
import {IERC20} from "../ERC20/IERC20.sol";\r
import {Intent, IntentHash} from "./IntentHash.sol";\r
import {SafeERC20} from "../utils/SafeERC20.sol";\r
import {IReactor} from "./IReactor.sol";\r
\r
/**\r
 * @title TradeReactor Contract\r
 * @notice This contract handles the signaling and processing of trade intents between buyers and sellers.\r
 * @dev This contract uses the SignatureTransfer contract for secure transfers with signatures.\r
*/\r
contract TradeReactor is SignatureTransfer, IReactor {\r
\r
    using IntentHash for Intent;\r
    using SafeERC20 for IERC20;\r
\r
    error OfferTooLow();\r
    error InvalidFiller();\r
    error TokenMismatch();\r
    error SpreadTooLow(uint256 bid, uint256 ask, uint16 minSpread);\r
\r
    /// @dev Emitted when an intent to trade is signaled.\r
    /// @param owner The address of the intent owner.\r
    /// @param filler The address of the filler, if any, that the intent is specifically directed to.\r
    /// @param tokenOut The address of the token the owner wants to sell or exchange.\r
    /// @param amountOut The amount of the tokenOut the owner wants to sell or exchange.\r
    /// @param tokenIn The address of the token the owner wants to receive in exchange.\r
    /// @param amountIn The amount of the tokenIn the owner wants to receive.\r
    /// @param exp The expiration time of the intent.\r
    /// @param nonce A nonce to ensure the uniqueness of the intent.\r
    /// @param data Additional data that may be used in the trade execution.\r
    /// @param signature The signature of the owner authorizing the intent.\r
    event IntentSignal(address owner, address filler, address tokenOut, uint160 amountOut, address tokenIn, uint160 amountIn, uint48 exp, uint48 nonce, bytes data, bytes signature);\r
\r
    /**\r
     * @notice A function to publicly signal an intent to buy or sell a token so it can be picked up by the filler for processing.\r
     * Alternaticely, the owner can directly communicate with the filler, without recording the intent on chain.\r
     * @param intent The trade intent data structure. \r
     * @param signature The signature of the intent owner.\r
    */\r
    function signalIntent(Intent calldata intent, bytes calldata signature) external {\r
        verify(intent, signature);\r
        emit IntentSignal(intent.owner, intent.filler, intent.tokenOut, intent.amountOut, intent.tokenIn, intent.amountIn, intent.expiration, intent.nonce, intent.data, signature);\r
    }\r
\r
    function verify(Intent calldata intent, bytes calldata sig) public view {\r
        if (block.timestamp > intent.expiration) revert SignatureExpired(intent.expiration);\r
        this.verify(toPermit(intent), intent.owner, intent.hash(), IntentHash.PERMIT2_INTENT_TYPE, sig);\r
    }\r
\r
    function calculateHash(Intent calldata intent) external pure returns (bytes32) {\r
        return intent.hash();\r
    }\r
\r
    /**\r
     * @notice Calculates the asking price for a given amount of tokenOut.\r
     * @dev Ideally called with an intent where tokenIn is a currency with many (e.g. 18) decimals.\r
     * @dev TokenOut can have very few decimals.\r
     * @param intent The trade intent data structure.\r
     * @param amount The amount of tokenOut.\r
     * @return The calculated asking price.\r
     */\r
    function getAsk(Intent calldata intent, uint256 amount) public pure returns (uint256) {\r
        // We should make sure that the rounding is always for the benefit of the intent owner to prevent exploits\r
        // Example: when the seller offers to sell 7 ABC for 10 CHF, the accurate price would be 4.2857....\r
        // The naive approach to calculate the same price using integers would be 3 * 10 / 7 = 3\r
        // But with the given approach, we get 10 - (7 - 3) * 10 / 7 = 5, which is higher than tha accurate price.\r
        return intent.amountIn - intent.amountIn * (intent.amountOut - amount) / intent.amountOut;\r
    }\r
\r
    /**\r
     * @notice Calculates the bidding price for a given amount of tokenIn.\r
     * @dev Ideally called with an intent where tokenOut is a currency with many (e.g. 18) decimals.\r
     * @dev TokenIn can have very few decimals.\r
     * @param intent The trade intent data structure.\r
     * @param amount The amount of tokenIn.\r
     * @return The calculated bidding price.\r
     */\r
    function getBid(Intent calldata intent, uint256 amount) public pure returns (uint256) {\r
        // We should make sure that the rounding is always for the benefit of the intent owner to prevent exploits\r
        // Example: when the buyer offers to buy 7 ABC for 10 CHF, but only 3 can be filled, the accurate price would be 4.2857....\r
        // With this calculation, we get a rounded down bid of 10 * 3 / 7 = 4\r
        return intent.amountOut * amount / intent.amountIn;\r
    }\r
\r
    /**\r
     * @notice Determines the maximum valid amount that can be traded based on seller and buyer intents.\r
     * @param sellerIntent The seller's trade intent.\r
     * @param buyerIntent The buyer's trade intent.\r
     * @return The maximum valid trade amount.\r
     */\r
    function getMaxValidAmount(Intent calldata sellerIntent, Intent calldata buyerIntent, uint16 minSpread) public view returns (uint256) {\r
        uint256 sellerAvailable = getPermittedAmount(sellerIntent.owner, sellerIntent.amountOut, sellerIntent.nonce);\r
        uint256 buyerAvailable = getPermittedAmount(buyerIntent.owner, buyerIntent.amountOut, buyerIntent.nonce);\r
        uint256 biddingFor = buyerIntent.amountIn * buyerAvailable / buyerIntent.amountOut;\r
        uint256 maxAmount = biddingFor > sellerAvailable ? sellerAvailable : biddingFor;\r
        uint256 ask = getAsk(sellerIntent, maxAmount);\r
        uint256 bid = getBid(buyerIntent, maxAmount);\r
        if ((bid < ask) || (bid - ask < (ask * minSpread) / 10000)) revert SpreadTooLow(bid, ask, minSpread);\r
        return maxAmount;\r
    }\r
\r
    /**\r
     * @notice Processes a trade between a seller and a buyer with the maximum valid amount.\r
     * @param sellerIntent The seller's trade intent.\r
     * @param sellerSig The seller's signature.\r
     * @param buyerIntent The buyer's trade intent.\r
     * @param buyerSig The buyer's signature.\r
     */    \r
    function process(Intent calldata sellerIntent, bytes calldata sellerSig, Intent calldata buyerIntent, bytes calldata buyerSig) external {\r
        process(sellerIntent, sellerSig, buyerIntent, buyerSig, getMaxValidAmount(sellerIntent, buyerIntent, 0));\r
    }\r
\r
    /**\r
     * @notice Processes a trade between a seller and a buyer for a specified amount.\r
     * @param sellerIntent The seller's trade intent.\r
     * @param sellerSig The seller's signature.\r
     * @param buyerIntent The buyer's trade intent.\r
     * @param buyerSig The buyer's signature.\r
     * @param amount The amount of the token to trade.\r
     */\r
    function process(Intent calldata sellerIntent, bytes calldata sellerSig, Intent calldata buyerIntent, bytes calldata buyerSig, uint256 amount) public returns (uint256 proceeds, uint256 spread){\r
        {\r
            // signatures will be verified in SignatureTransfer\r
            if (sellerIntent.tokenOut != buyerIntent.tokenIn || sellerIntent.tokenIn != buyerIntent.tokenOut) revert TokenMismatch();\r
            if (sellerIntent.filler != address(0x0) && sellerIntent.filler != msg.sender) revert InvalidFiller();\r
            if (buyerIntent.filler != address(0x0) && buyerIntent.filler != msg.sender) revert InvalidFiller();\r
        }\r
        uint256 ask = getAsk(sellerIntent, amount);\r
        uint256 bid = getBid(buyerIntent, amount);\r
        if (bid < ask) revert SpreadTooLow(bid, ask, 0);\r
        {\r
            // move tokens to reactor in order to implicitly allowlist target address in case reactor is powerlisted\r
            obtainTokens(sellerIntent, sellerSig, amount);\r
            obtainTokens(buyerIntent, buyerSig, bid);\r
        }\r
        {\r
            // move tokens to target addresses\r
            IERC20(sellerIntent.tokenOut).safeTransfer(buyerIntent.owner, amount); // transfer sold tokens to buyer\r
            IERC20(sellerIntent.tokenIn).safeTransfer(sellerIntent.owner, ask); // transfer net proceeds to seller\r
            IERC20(sellerIntent.tokenIn).safeTransfer(msg.sender, bid - ask); // collect spread as fee\r
        }\r
        return (ask, bid - ask);\r
        //leave it to the filler to emit an event with the fees correctly specified\r
        //emit Trade(sellerIntent.owner, buyerIntent.owner, sellerIntent.tokenOut, amount, sellerIntent.tokenIn, ask, bid - ask);\r
    }\r
\r
    function obtainTokens(Intent calldata intent, bytes calldata signature, uint256 amount) private {\r
        this.permitWitnessTransferFrom(toPermit(intent), toDetails(address(this), amount), intent.owner, intent.hash(), IntentHash.PERMIT2_INTENT_TYPE, signature);\r
    }\r
\r
    function toDetails(address recipient, uint256 amount) internal pure returns (ISignatureTransfer.SignatureTransferDetails memory){\r
        return ISignatureTransfer.SignatureTransferDetails({to: recipient, requestedAmount: amount});\r
    }\r
 \r
    function toPermit(Intent memory intent) internal pure returns (ISignatureTransfer.PermitTransferFrom memory) {\r
        return ISignatureTransfer.PermitTransferFrom({\r
            permitted: ISignatureTransfer.TokenPermissions({\r
                token: address(intent.tokenOut),\r
                amount: intent.amountOut\r
            }),\r
            nonce: intent.nonce,\r
            deadline: intent.expiration\r
        });\r
    }\r
\r
}"
    },
    "market/IReactor.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import {Intent} from "./IntentHash.sol";\r
\r
interface IReactor {\r
\r
    function calculateHash(Intent calldata intent) external view returns (bytes32);\r
    function verify(Intent calldata intent, bytes calldata sig) external view;\r
    function signalIntent(Intent calldata intent, bytes calldata signature) external;\r
    function getMaxValidAmount(Intent calldata sellerIntent, Intent calldata buyerIntent, uint16 minSpread) external view returns (uint256);\r
    function process(Intent calldata sellerIntent, bytes calldata sellerSig, Intent calldata buyerIntent, bytes calldata buyerSig, uint256 amount) external returns (uint256 proceeds, uint256 spread);\r
\r
}"
    },
    "utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
// coppied and adjusted from OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)\r
\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import {IERC20} from "../ERC20/IERC20.sol";\r
import {IERC20Permit} from "../ERC20/IERC20Permit.sol";\r
import {Address} from "./Address.sol";\r
\r
/**\r
 * @title SafeERC20\r
 * @dev Wrappers around ERC20 operations that throw on failure (when the token\r
 * contract returns false). Tokens that return no value (and instead revert or\r
 * throw on failure) are also supported, non-reverting calls are assumed to be\r
 * successful.\r
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\r
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\r
 */\r
library SafeERC20 {\r
    using Address for address;\r
\r
    /**\r
     * @dev An operation with an ERC20 token failed.\r
     */\r
    error SafeERC20FailedOperation(address token);\r
\r
    /**\r
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\r
     * non-reverting calls are assumed to be successful.\r
     */\r
    function safeTransfer(IERC20 token, address to, uint256 value) internal {\r
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\r
    }\r
\r
    /**\r
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\r
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\r
     */\r
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\r
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\r
    }\r
\r
    /**\r
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.\r
     * Revert on invalid signature.\r
     */\r
    function safePermit(\r
        IERC20Permit token,\r
        address owner,\r
        address spender,\r
        uint256 value,\r
        uint256 deadline,\r
        uint8 v,\r
        bytes32 r,\r
        bytes32 s\r
    ) internal {\r
        uint256 nonceBefore = token.nonces(owner);\r
        token.permit(owner, spender, value, deadline, v, r, s);\r
        uint256 nonceAfter = token.nonces(owner);\r
        if (nonceAfter != nonceBefore + 1) {\r
            revert SafeERC20FailedOperation(address(token));\r
        }\r
    }\r
\r
    /**\r
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\r
     * on the return value: the return value is optional (but if data is returned, it must not be false).\r
     * @param token The token targeted by the call.\r
     * @param data The call data (encoded using abi.encode or one of its variants).\r
     */\r
    function _callOptionalReturn(IERC20 token, bytes memory data) private {\r
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\r
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\r
        // the target address contains contract code and also asserts for success in the low-level call.\r
\r
        bytes memory returndata = address(token).functionCall(data);\r
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {\r
            revert SafeERC20FailedOperation(address(token));\r
        }\r
    }\r
\r
    /**\r
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\r
     * on the return value: the return value is optional (but if data is returned, it must not be false).\r
     * @param token The token targeted by the call.\r
     * @param data The call data (encoded using abi.encode or one of its variants).\r
     *\r
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.\r
     */\r
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\r
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\r
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false\r
        // and not revert is the subcall reverts.\r
\r
        (bool success, bytes memory returndata) = address(token).call(data);\r
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;\r
    }\r
}"
    },
    "market/IntentHash.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import {IERC20} from "../ERC20/IERC20.sol";\r
import {PermitHash} from "./PermitHash.sol";\r
\r
struct Intent {\r
	address owner;\r
	address filler;\r
	address tokenOut; // The ERC20 token sent out\r
	uint160 amountOut; // The maximum amount\r
	address tokenIn; // The ERC20 token received\r
	uint160 amountIn; // The amount received in exchange for the maximum of the sent token\r
	uint48 expiration; // timestamp at which the intent expires\r
	uint48 nonce; // a unique value indexed per owner,token,and spender for each signature\r
	bytes data;\r
}\r
\r
/// @notice helpers for handling dutch order objects\r
library IntentHash {\r
\r
	bytes internal constant INTENT_TYPE =\r
		abi.encodePacked(\r
			"Intent(",\r
			"address owner,",\r
			"address filler,",\r
			"address tokenOut,",\r
			"uint160 amountOut,",\r
			"address tokenIn,",\r
			"uint160 amountIn,",\r
			"uint48 expiration,",\r
			"uint48 nonce,",\r
			"bytes data)"\r
		);\r
\r
	bytes32 internal constant INTENT_TYPE_HASH = keccak256(INTENT_TYPE);\r
\r
	string internal constant PERMIT2_INTENT_TYPE =\r
        string(abi.encodePacked("Intent witness)", INTENT_TYPE, PermitHash._TOKEN_PERMISSIONS_TYPESTRING));\r
\r
	/// @notice hash the given intent\r
	/// @param intent the intent to hash\r
	/// @return the eip-712 intent hash\r
	function hash(Intent calldata intent) internal pure returns (bytes32) {\r
		return\r
			keccak256(\r
				abi.encode(\r
					INTENT_TYPE_HASH,\r
					intent.owner,\r
					intent.filler,\r
					intent.tokenOut,\r
					intent.amountOut,\r
					intent.tokenIn,\r
					intent.amountIn,\r
					intent.expiration,\r
					intent.nonce,\r
					keccak256(intent.data)\r
				)\r
			);\r
	}\r
}\r
"
    },
    "ERC20/IERC20.sol": {
      "content": "/**\r
* SPDX-License-Identifier: MIT\r
*\r
* Copyright (c) 2016-2019 zOS Global Limited\r
*\r
*/\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include\r
 * the optional functions; to access them see `ERC20Detailed`.\r
 */\r
\r
interface IERC20 {\r
\r
    // Optional functions\r
    function name() external view returns (string memory);\r
\r
    function symbol() external view returns (string memory);\r
\r
    function decimals() external view returns (uint8);\r
\r
    /**\r
     * @dev Returns the amount of tokens in existence.\r
     */\r
    function totalSupply() external view returns (uint256);\r
\r
    /**\r
     * @dev Returns the amount of tokens owned by `account`.\r
     */\r
    function balanceOf(address account) external view returns (uint256);\r
\r
    /**\r
     * @dev Moves `amount` tokens from the caller's account to `recipient`.\r
     *\r
     * Returns a boolean value indicating whether the operation succeeded.\r
     *\r
     * Emits a `Transfer` event.\r
     */\r
    function transfer(address recipient, uint256 amount) external returns (bool);\r
\r
    /**\r
     * @dev Returns the remaining number of tokens that `spender` will be\r
     * allowed to spend on behalf of `owner` through `transferFrom`. This is\r
     * zero by default.\r
     *\r
     * This value changes when `approve` or `transferFrom` are called.\r
     */\r
    function allowance(address owner, address spender) external view returns (uint256);\r
\r
    /**\r
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\r
     *\r
     * Returns a boolean value indicating whether the operation succeeded.\r
     *\r
     * > Beware that changing an allowance with this method brings the risk\r
     * that someone may use both the old and the new allowance by unfortunate\r
     * transaction ordering. One possible solution to mitigate this race\r
     * condition is to first reduce the spender's allowance to 0 and set the\r
     * desired value afterwards:\r
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\r
     *\r
     * Emits an `Approval` event.\r
     */\r
    function approve(address spender, uint256 amount) external returns (bool);\r
\r
    /**\r
     * @dev Moves `amount` tokens from `sender` to `recipient` using the\r
     * allowance mechanism. `amount` is then deducted from the caller's\r
     * allowance.\r
     *\r
     * Returns a boolean value indicating whether the operation succeeded.\r
     *\r
     * Emits a `Transfer` event.\r
     */\r
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\r
\r
    /**\r
     * @dev Emitted when `value` tokens are moved from one account (`from`) to\r
     * another (`to`).\r
     *\r
     * Note that `value` may be zero.\r
     */\r
    event Transfer(address indexed from, address indexed to, uint256 value);\r
\r
    /**\r
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by\r
     * a call to `approve`. `value` is the new allowance.\r
     */\r
    event Approval(address indexed owner, address indexed spender, uint256 value);\r
\r
}"
    },
    "ERC20/IERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\r
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/draft-IERC20Permit.sol\r
\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import "./IERC20.sol";\r
\r
/**\r
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\r
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\r
 *\r
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\r
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\r
 * need to send a transaction, and thus is not required to hold Ether at all.\r
 */\r
interface IERC20Permit is IERC20 {\r
\r
    /*//////////////////////////////////////////////////////////////\r
                            Custom errors\r
	//////////////////////////////////////////////////////////////*/\r
    /// Block timestamp must to be before deadline.\r
    /// @param deadline The deadline of the permit.\r
    /// @param blockTimestamp The timestamp of the execution block.\r
    error Permit_DeadlineExpired(uint256 deadline, uint256 blockTimestamp);\r
    /// Recovered address must be owner and not zero address.\r
    /// @param signerAddress The recovered signer address.\r
    error Permit_InvalidSigner(address signerAddress);\r
\r
    /**\r
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\r
     * given ``owner``'s signed approval.\r
     *\r
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction\r
     * ordering also apply here.\r
     *\r
     * Emits an {Approval} event.\r
     *\r
     * Requirements:\r
     *\r
     * - `spender` cannot be the zero address.\r
     * - `deadline` must be a timestamp in the future.\r
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\r
     * over the EIP712-formatted function arguments.\r
     * - the signature must use ``owner``'s current nonce (see {nonces}).\r
     *\r
     * For more information on the signature format, see the\r
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\r
     * section].\r
     */\r
    function permit(\r
        address owner,\r
        address spender,\r
        uint256 value,\r
        uint256 deadline,\r
        uint8 v,\r
        bytes32 r,\r
        bytes32 s\r
    ) external;\r
\r
    /**\r
     * @dev Returns the current nonce for `owner`. This value must be\r
     * included whenever a signature is generated for {permit}.\r
     *\r
     * Every successful call to {permit} increases ``owner``'s nonce by one. This\r
     * prevents a signature from being used multiple times.\r
     */\r
    function nonces(address owner) external view returns (uint256);\r
\r
    /**\r
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\r
     */\r
    // solhint-disable-next-line func-name-mixedcase\r
    function DOMAIN_SEPARATOR() external view returns (bytes32);\r
}"
    },
    "market/ISignatureTransfer.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/// @title SignatureTransfer\r
/// @notice Handles ERC20 token transfers through signature based actions\r
/// @dev Requires user's token approval on the Permit2 contract\r
interface ISignatureTransfer {\r
    /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount\r
    /// @param maxAmount The maximum amount a spender can request to transfer\r
    error InvalidAmount(uint256 maxAmount);\r
\r
    /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred\r
    /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred\r
    error LengthMismatch();\r
\r
    /// @notice Emits an event when the owner successfully invalidates an unordered nonce.\r
    event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);\r
\r
    /// @notice The token and amount details for a transfer signed in the permit transfer signature\r
    struct TokenPermissions {\r
        // ERC20 token address\r
        address token;\r
        // the maximum amount that can be spent\r
        uint256 amount;\r
    }\r
\r
    /// @notice The signed permit message for a single token transfer\r
    struct PermitTransferFrom {\r
        TokenPermissions permitted;\r
        // a unique value for every token owner's signature to prevent signature replays\r
        uint256 nonce;\r
        // deadline on the permit signature\r
        uint256 deadline;\r
    }\r
\r
    /// @notice Specifies the recipient address and amount for batched transfers.\r
    /// @dev Recipients and amounts correspond to the index of the signed token permissions array.\r
    /// @dev Reverts if the requested amount is greater than the permitted signed amount.\r
    struct SignatureTransferDetails {\r
        // recipient address\r
        address to;\r
        // spender requested amount\r
        uint256 requestedAmount;\r
    }\r
\r
    /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection\r
    /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order\r
    /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce\r
    /// @dev It returns a uint256 bitmap\r
    /// @dev The index, or wordPosition is capped at type(uint248).max\r
    function nonceBitmap(address, uint256) external view returns (uint256);\r
\r
    /// @notice Transfers a token using a signed permit message\r
    /// @dev Reverts if the requested amount is greater than the permitted signed amount\r
    /// @param permit The permit data signed over by the owner\r
    /// @param owner The owner of the tokens to transfer\r
    /// @param transferDetails The spender's requested transfer details for the permitted token\r
    /// @param signature The signature to verify\r
    function permitTransferFrom(\r
        PermitTransferFrom memory permit,\r
        SignatureTransferDetails calldata transferDetails,\r
        address owner,\r
        bytes calldata signature\r
    ) external;\r
\r
    /// @notice Transfers a token using a signed permit message\r
    /// @notice Includes extra data provided by the caller to verify signature over\r
    /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition\r
    /// @dev Reverts if the requested amount is greater than the permitted signed amount\r
    /// @param permit The permit data signed over by the owner\r
    /// @param owner The owner of the tokens to transfer\r
    /// @param transferDetails The spender's requested transfer details for the permitted token\r
    /// @param witness Extra data to include when checking the user signature\r
    /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash\r
    /// @param signature The signature to verify\r
    function permitWitnessTransferFrom(\r
        PermitTransferFrom memory permit,\r
        SignatureTransferDetails calldata transferDetails,\r
        address owner,\r
        bytes32 witness,\r
        string calldata witnessTypeString,\r
        bytes calldata signature\r
    ) external;\r
\r
\r
    /// @notice Invalidates the bits specified in mask for the bitmap at the word position\r
    /// @dev The wordPos is maxed at type(uint248).max\r
    /// @param wordPos A number to index the nonceBitmap at\r
    /// @param mask A bitmap masked against msg.sender's current bitmap at the word position\r
    function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;\r
\r
    function findFreeNonce(address owner) external view returns (uint48);\r
\r
    function getPermittedAmount(address owner, uint256 permitMax, uint48 nonce) external view returns (uint256);\r
}"
    },
    "market/SignatureTransfer.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * Copied from github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol and modified.\r
 */\r
import {ISignatureTransfer} from "./ISignatureTransfer.sol";\r
import {IERC20} from "../ERC20/IERC20.sol";\r
import {SignatureVerification} from "./SignatureVerification.sol";\r
import {PermitHash} from "./PermitHash.sol";\r
import {EIP712} from "./EIP712.sol";\r
\r
contract SignatureTransfer is ISignatureTransfer, EIP712 {\r
\r
    using SignatureVerification for bytes;\r
    using PermitHash for PermitTransferFrom;\r
\r
    /// @inheritdoc ISignatureTransfer\r
    mapping(address => mapping(uint256 => uint256)) public nonceBitmap;\r
    mapping(address => mapping(uint256 => uint256)) public partialFills;\r
\r
    /// @notice Thrown when validating an inputted signature that is stale\r
    /// @param signatureDeadline The timestamp at which a signature is no longer valid\r
    error SignatureExpired(uint256 signatureDeadline);\r
\r
    /// @notice Thrown when validating that the inputted nonce has not been used\r
    error InvalidNonce();\r
\r
    error OverFilled();\r
\r
    function permitTransferFrom(PermitTransferFrom calldata permit, SignatureTransferDetails calldata transferDetails, address owner, bytes calldata signature) external {\r
        _permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature);\r
    }\r
\r
    /// @inheritdoc ISignatureTransfer\r
    function permitWitnessTransferFrom(\r
        PermitTransferFrom calldata permit,\r
        SignatureTransferDetails calldata transferDetails,\r
        address owner,\r
        bytes32 witness,\r
        string calldata witnessTypeString,\r
        bytes calldata signature\r
    ) public {\r
        _permitTransferFrom(permit, transferDetails, owner, permit.hashWithWitness(witness, witnessTypeString), signature);\r
    }\r
\r
    function findFreeNonce(address owner) external view returns (uint48){\r
        return findFreeNonce(owner, 0);\r
    }\r
\r
    /**\r
     * Find a nonce that looks free given the data on the blockchain.\r
     * Of course, this method cannot take into account nonces of valid but unused permits.\r
     */\r
    function findFreeNonce(address owner, uint48 start) public view returns (uint48){\r
        while (!isFreeNonce(owner, start)){\r
            start++;\r
        }\r
        return start;\r
    }\r
\r
    function isFreeNonce(address owner, uint256 nonce) public view returns (bool){\r
        return !isUsedNonce(owner, nonce) && partialFills[owner][nonce] == 0;\r
    }\r
\r
    function isUsedNonce(address owner, uint256 nonce) public view returns (bool) {\r
        (uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce);\r
        uint256 bit = 1 << bitPos;\r
        return nonceBitmap[owner][wordPos] & bit != 0;\r
    }\r
\r
    function getPermittedAmount(address owner, uint256 permitMax, uint48 nonce) public view returns (uint256) {\r
        if (isUsedNonce(owner, nonce)) {\r
            return 0;\r
        } else {\r
            return permitMax - partialFills[owner][nonce];\r
        }\r
    }\r
\r
    function verify(PermitTransferFrom calldata permit, address owner, bytes32 witness, string memory witnessTypeString, bytes calldata signature) public view {\r
        if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline);\r
        signature.verify(_hashTypedData(permit.hashWithWitness(witness, witnessTypeString)), owner);\r
    }\r
\r
    /// @notice Transfers a token using a signed permit message.\r
    /// @param permit The permit data signed over by the owner\r
    /// @param dataHash The EIP-712 hash of permit data to include when checking signature\r
    /// @param owner The owner of the tokens to transfer\r
    /// @param transferDetails The spender's requested transfer details for the permitted token\r
    /// @param signature The signature to verify\r
    function _permitTransferFrom(PermitTransferFrom calldata permit, SignatureTransferDetails calldata transferDetails, address owner, bytes32 dataHash, bytes calldata signature) private {\r
        uint256 requestedAmount = transferDetails.requestedAmount;\r
\r
        if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline);\r
\r
        _useUnorderedNonce(owner, permit.nonce, requestedAmount, permit.permitted.amount);\r
\r
        signature.verify(_hashTypedData(dataHash), owner);\r
\r
        IERC20(permit.permitted.token).transferFrom(owner, transferDetails.to, requestedAmount);\r
    }\r
\r
    /**\r
     * Invalidate a nonce.\r
     */\r
    function invalidateNonce(uint256 nonce) external {\r
        (uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce);\r
        invalidateUnorderedNonces(wordPos, 1 << bitPos);\r
    }\r
\r
    /// @inheritdoc ISignatureTransfer\r
    function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) public {\r
        nonceBitmap[msg.sender][wordPos] |= mask;\r
        emit UnorderedNonceInvalidation(msg.sender, wordPos, mask);\r
    }\r
\r
    /// @notice Returns the index of the bitmap and the bit position within the bitmap. Used for unordered nonces\r
    /// @param nonce The nonce to get the associated word and bit positions\r
    /// @return wordPos The word position or index into the nonceBitmap\r
    /// @return bitPos The bit position\r
    /// @dev The first 248 bits of the nonce value is the index of the desired bitmap\r
    /// @dev The last 8 bits of the nonce value is the position of the bit in the bitmap\r
    function bitmapPositions(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) {\r
        wordPos = uint248(nonce >> 8);\r
        bitPos = uint8(nonce);\r
    }\r
\r
    /// @notice Checks whether a nonce is taken and sets the bit at the bit position in the bitmap at the word position\r
    /// @dev This function is used to manage nonces for transactions, ensuring that each nonce is used only once and tracking partial fills of orders.\r
    /// @param from The address to use the nonce at\r
    /// @param nonce The nonce to spend\r
    /// @param amount The amount being filled in the current transaction.\r
    /// @param max The maximum allowable fill amount for the nonce.\r
    function _useUnorderedNonce(address from, uint256 nonce, uint256 amount, uint256 max) internal {\r
        (uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce);\r
        uint256 bit = 1 << bitPos;\r
        uint256 state = nonceBitmap[from][wordPos];\r
        if (state & bit != 0) revert InvalidNonce();\r
\r
        uint256 alreadyFilled = partialFills[from][nonce];\r
        if (alreadyFilled + amount > max) revert OverFilled();\r
        if (alreadyFilled + amount < max){\r
            partialFills[from][nonce] = alreadyFilled + amount;\r
        } else {\r
            if (alreadyFilled > 0) delete partialFills[from][nonce]; // get some gas back \r
            nonceBitmap[from][wordPos] |= bit; // flag done\r
        }\r
    }\r
}"
    },
    "utils/Address.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol\r
// and modified it.\r
\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
library Address {\r
\r
    /// @param target Target address to call the function on.\r
    error Address_NotTransferNorContract(address target);\r
\r
    /**\r
     * @dev Returns true if `account` is a contract.\r
     *\r
     * [IMPORTANT]\r
     * ====\r
     * It is unsafe to assume that an address for which this function returns\r
     * false is an externally-owned account (EOA) and not a contract.\r
     *\r
     * Among others, `isContract` will return false for the following\r
     * types of addresses:\r
     *\r
     *  - an externally-owned account\r
     *  - a contract in construction\r
     *  - an address where a contract will be created\r
     *  - an address where a contract lived, but was destroyed\r
     * ====\r
     */\r
    function isContract(address account) internal view returns (bool) {\r
        // This method relies on extcodesize/address.code.length, which returns 0\r
        // for contracts in construction, since the code is only stored at the end\r
        // of the constructor execution.\r
        return account.code.length > 0;\r
    }\r
    \r
    /**\r
     * @dev Performs a Solidity function call using a low level `call`. A\r
     * plain `call` is an unsafe replacement for a function call: use this\r
     * function instead.\r
     *\r
     * If `target` reverts with a revert reason or custom error, it is bubbled\r
     * up by this function (like regular Solidity function calls). However, if\r
     * the call reverted with no returned reason, this function reverts with a\r
     * {FailedInnerCall} error.\r
     *\r
     * Returns the raw returned data. To convert to the expected return value,\r
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\r
     *\r
     * Requirements:\r
     *\r
     * - `target` must be a contract.\r
     * - calling `target` with `data` must not revert.\r
     */\r
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {\r
        return functionCallWithValue(target, data, 0);\r
    }\r
\r
    function functionCallWithValue(address target, bytes memory data, uint256 weiValue) internal returns (bytes memory) {\r
        if (data.length != 0 && !isContract(target)) {\r
            revert Address_NotTransferNorContract(target);\r
        }\r
        // solhint-disable-next-line avoid-low-level-calls\r
        (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);\r
        if (success) {\r
            return returndata;\r
        } else if (returndata.length > 0) {\r
            assembly{\r
                revert (add (returndata, 0x20), mload (returndata))\r
            }\r
        } else {\r
           revert("failed");\r
        }\r
    }\r
}"
    },
    "market/EIP712.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * Copied from github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol and modified.\r
 */\r
\r
/// @notice EIP712 helpers for permit2\r
/// @dev Maintains cross-chain replay protection in the event of a fork\r
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol\r
contract EIP712 {\r
    // Cache the domain separator as an immutable value, but also store the chain id that it\r
    // corresponds to, in order to invalidate the cached domain separator if the chain id changes.\r
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;\r
    uint256 private immutable _CACHED_CHAIN_ID;\r
\r
    bytes32 private constant _HASHED_NAME = keccak256("Permit2"); // TODO: what name should we use here?\r
    bytes32 private constant _TYPE_HASH =\r
        keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");\r
\r
    constructor() {\r
        _CACHED_CHAIN_ID = block.chainid;\r
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);\r
    }\r
\r
    /// @notice Returns the domain separator for the current chain.\r
    /// @dev Uses cached version if chainid and address are unchanged from construction.\r
    function DOMAIN_SEPARATOR() public view returns (bytes32) {\r
        return block.chainid == _CACHED_CHAIN_ID\r
            ? _CACHED_DOMAIN_SEPARATOR\r
            : _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);\r
    }\r
\r
    /// @notice Builds a domain separator using the current chainId and contract address.\r
    function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash) private view returns (bytes32) {\r
        return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this)));\r
    }\r
\r
    /// @notice Creates an EIP-712 typed data hash\r
    function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) {\r
        return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash));\r
    }\r
}"
    },
    "market/PermitHash.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * Copied from github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol and modified.\r
 */\r
import {ISignatureTransfer} from "./ISignatureTransfer.sol";\r
\r
library PermitHash {\r
    bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)");\r
\r
    bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = keccak256(\r
        "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)"\r
    );\r
\r
    string public constant _TOKEN_PERMISSIONS_TYPESTRING = "TokenPermissions(address token,uint256 amount)";\r
\r
    string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB =\r
        "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,";\r
\r
    function hash(ISignatureTransfer.PermitTransferFrom memory permit) internal view returns (bytes32) {\r
        bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted);\r
        return keccak256(\r
            abi.encode(_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline)\r
        );\r
    }\r
\r
    function hashWithWitness(\r
        ISignatureTransfer.PermitTransferFrom memory permit,\r
        bytes32 witness,\r
        string memory witnessTypeString\r
    ) internal view returns (bytes32) {\r
        bytes32 typeHash = keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString));\r
\r
        bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted);\r
        return keccak256(abi.encode(typeHash, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline, witness));\r
    }\r
\r
    function _hashTokenPermissions(ISignatureTransfer.TokenPermissions memory permitted)\r
        private\r
        pure\r
        returns (bytes32)\r
    {\r
        return keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permitted));\r
    }\r
}"
    },
    "market/SignatureVerification.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * Copied from github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol and modified.\r
 */\r
library SignatureVerification {\r
    /// @notice Thrown when the passed in signature is not a valid length\r
    error InvalidSignatureLength();\r
\r
    /// @notice Thrown when the recovered signer is equal to the zero address\r
    error InvalidSignature();\r
\r
    /// @notice Thrown when the recovered signer does not equal the claimedSigner\r
    error InvalidSigner();\r
\r
    /// @notice Thrown when the recovered contract signature is incorrect\r
    error InvalidContractSignature();\r
\r
    bytes32 constant UPPER_BIT_MASK = (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\r
\r
    function verify(bytes calldata signature, bytes32 hash, address claimedSigner) internal view {\r
        bytes32 r;\r
        bytes32 s;\r
        uint8 v;\r
\r
        if (claimedSigner.code.length == 0) {\r
            if (signature.length == 65) {\r
                (r, s) = abi.decode(signature, (bytes32, bytes32));\r
                v = uint8(signature[64]);\r
            } else if (signature.length == 64) {\r
                // EIP-2098\r
                bytes32 vs;\r
                (r, vs) = abi.decode(signature, (bytes32, bytes32));\r
                s = vs & UPPER_BIT_MASK;\r
                v = uint8(uint256(vs >> 255)) + 27;\r
            } else {\r
                revert InvalidSignatureLength();\r
            }\r
            address signer = ecrecover(hash, v, r, s);\r
            if (signer == address(0)) revert InvalidSignature();\r
            if (signer != claimedSigner) revert InvalidSigner();\r
        } else {\r
            bytes4 magicValue = IERC1271(claimedSigner).isValidSignature(hash, signature);\r
            if (magicValue != IERC1271.isValidSignature.selector) revert InvalidContractSignature();\r
        }\r
    }\r
}\r
\r
interface IERC1271 {\r
    /// @dev Should return whether the signature provided is valid for the provided data\r
    /// @param hash      Hash of the data to be signed\r
    /// @param signature Signature byte array associated with _data\r
    /// @return magicValue The bytes4 magic value 0x1626ba7e\r
    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);\r
}"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": [],
    "evmVersion": "prague"
  }
}}

Tags:
ERC20, Token, Factory|addr:0x2a48d7aaf58c993943c192b582dbcefbf674748b|verified:true|block:23433726|tx:0x2ac36e70ecc4da99577acedc6b745311ffc42ef30607f91b3beb578c41450d48|first_check:1758737949

Submitted on: 2025-09-24 20:19:09

Comments

Log in to comment.

No comments yet.