Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"market/SecondaryMarketFactory.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import "./SecondaryMarket.sol";\r
\r
contract SecondaryMarketFactory {\r
\r
address public constant REACTOR = address(0x2A48D7aaF58C993943c192b582dbCeFBF674748B); // TODO: set the reactor address here\r
bytes32 public constant SALT = bytes32(uint256(21092025));\r
\r
event SecondaryMarketDeployed(address indexed owner, address market, address router);\r
\r
/**\r
* @notice Deploys a new SecondaryMarket contract using CREATE2\r
* @param owner The owner of the SecondaryMarket\r
* @param router The router address\r
* @return market The address of the deployed SecondaryMarket\r
*/\r
function deploy(address owner, address router, address currency) external returns (address) {\r
SecondaryMarket market = new SecondaryMarket{salt: SALT}(owner, REACTOR, router, currency);\r
emit SecondaryMarketDeployed(owner, address(market), router);\r
return address(market);\r
}\r
\r
/**\r
* @notice Predicts the address where a SecondaryMarket will be deployed\r
* @param owner The owner of the SecondaryMarket\r
* @param router The router address\r
* @return predicted The predicted deployment address\r
*/\r
function predict(address owner, address router, address currency) external view returns (address predicted) {\r
bytes memory initCode = abi.encodePacked(type(SecondaryMarket).creationCode, abi.encode(owner, REACTOR, router, currency));\r
bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), SALT, keccak256(initCode)));\r
return address(uint160(uint256(hash)));\r
}\r
\r
/**\r
* @notice Checks if a SecondaryMarket is already deployed at the predicted address\r
* @param owner The owner of the SecondaryMarket\r
* @param router The router address\r
* @return isDeployed True if a contract exists at the predicted address\r
*/\r
function isDeployed(address owner, address router, address currency) external view returns (bool) {\r
address predictedAddress = this.predict(owner, router, currency);\r
uint32 size;\r
assembly {\r
size := extcodesize(predictedAddress)\r
}\r
return size > 0;\r
}\r
}"
},
"market/SecondaryMarket.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import "./EIP712.sol";\r
import "../ERC20/IERC20.sol";\r
import "../utils/Ownable.sol";\r
import {IReactor} from "./IReactor.sol";\r
import {ISignatureTransfer} from "./ISignatureTransfer.sol";\r
import {Intent} from "./IntentHash.sol";\r
\r
contract SecondaryMarket is Ownable {\r
\r
uint16 public constant ALL = 10000;\r
address public immutable REACTOR;\r
address public immutable CURRENCY;\r
address public constant LICENSE_FEE_RECIPIENT = 0x29Fe8914e76da5cE2d90De98a64d0055f199d06D;\r
\r
event TradingFeeCollected(address currency, uint256 actualFee, address spreadRecipient, uint256 returnedSpread);\r
event TradingFeeWithdrawn(address currency, address target, uint256 amount);\r
event LicenseFeePaid(address currency, address target, uint256 amount);\r
event Trade(address indexed seller, address indexed buyer, address token, uint256 tokenAmount, address currency, uint256 currencyAmount, uint256 fees);\r
\r
error LargerSpreadNeeded(uint256 feesCollected, uint256 requiredMinimum);\r
error WrongFiller();\r
error WrongRouter(address expected, address actual);\r
error InvalidConfiguration();\r
error SignatureExpired(uint256 signatureDeadline);\r
error MarketClosed(uint256 openFrom, uint256 openTo, uint256 nowTime);\r
error NoBalance(address token, address owner);\r
error NoAllowance(address token, address owner, address spender);\r
error NonceUsed(address owner, uint256 nonce);\r
\r
// The following fields should fit into one 32B slot, 20 + 2 + 1 + 1 + 4 + 4 = 32\r
address public router; // null for any, 20B\r
uint16 public tradingFeeBips; // 2B\r
uint16 public routerShare; // Share of the trading fee that goes to the router in bips\r
uint16 public licenseShare; // Share of the trading fee that goes to the router in bips\r
uint24 public openFrom; // Market opening time\r
uint24 public openTo; // Market closing time\r
\r
constructor(address owner, address reactor, address currency, address router_) Ownable(owner) {\r
REACTOR = reactor;\r
CURRENCY = currency;\r
licenseShare = 5000; // default license fee is 50% of fees\r
router = router_;\r
routerShare = 0;\r
openFrom = 0;\r
openTo = type(uint24).max;\r
}\r
\r
//// ADMINISTRATION ////\r
\r
function isOpen() public view returns (bool) {\r
return block.timestamp >= openFrom && block.timestamp < openTo;\r
}\r
\r
/**\r
* Opens the market.\r
*/\r
function open() onlyOwner external {\r
setTradingWindow(0, type(uint24).max);\r
}\r
\r
/**\r
* Closes the market.\r
*/\r
function close() onlyOwner external {\r
setTradingWindow(0, 0);\r
}\r
\r
/**\r
* Opens the market for a limited amount of time.\r
* @param openTime The time in seconds since 1970-01-01 the market opens.\r
* @param window The dureation in seconds for which the market stays open.\r
*/\r
function setTradingWindow(uint24 openTime, uint24 window) onlyOwner public {\r
openFrom = openTime;\r
openTo = openTime + window;\r
}\r
\r
/**\r
* Configures the permissible router or the null address for any.\r
* \r
* Having a trusted router helps with the prevention of front-running attacks as no\r
* one else can front the router with a different matching of the submitted orders.\r
*/\r
function setRouter(address router_, uint16 routerShare_) onlyOwner external {\r
if (uint256(routerShare_) + licenseShare > ALL) revert InvalidConfiguration();\r
router = router_;\r
routerShare = routerShare_;\r
}\r
\r
/**\r
* Configures the software license fee as agreed with the copyright owners.\r
*/\r
function setLicenseFee(uint16 licenseShare_) onlyOwner external {\r
if (uint256(licenseShare_) + routerShare > ALL) revert InvalidConfiguration();\r
licenseShare = licenseShare_;\r
}\r
\r
function setTradingFee(uint16 tradingFeeBips_) onlyOwner external {\r
if (tradingFeeBips_ > 500) revert InvalidConfiguration(); // commit to never set it above 5%\r
tradingFeeBips = tradingFeeBips_;\r
}\r
\r
//// TRADING ////\r
\r
/**\r
* Create an order intent that can be signed by the owner.\r
*/\r
function createBuyOrder(address owner, uint160 amountOut, address tokenIn, uint160 amountIn) public view returns (Intent memory) {\r
return Intent(owner, address(this), CURRENCY, amountOut, tokenIn, amountIn, uint48(block.timestamp + 90 days), ISignatureTransfer(REACTOR).findFreeNonce(owner), new bytes(0));\r
}\r
\r
/**\r
* Create an order intent that can be signed by the owner.\r
* The tokenIn amount is reduced by the trading fee, which is always charged to the seller.\r
*/\r
function createSellOrder(address owner, address tokenOut, uint160 amountOut, uint160 amountIn) public view returns (Intent memory) {\r
return Intent(owner, address(this), tokenOut, amountOut, CURRENCY, amountIn * (10000 - tradingFeeBips) / 10000, uint48(block.timestamp + 90 days), ISignatureTransfer(REACTOR).findFreeNonce(owner), new bytes(0));\r
}\r
\r
/**\r
* Calculate the hash to be signed.\r
*/\r
function calculateHash(Intent calldata intent) public view returns (bytes32) {\r
return IReactor(REACTOR).calculateHash(intent);\r
}\r
\r
/**\r
* Stores an order in the Ethereum blockchain as a publicly readable event, so any allowed router\r
* can pick it up and execute it against another valid order.\r
* \r
* In case the owner configured a specific router to be used, it is usually better to send the\r
* order to the configured router directly through a suitable API. Note that all partially filled\r
* orders and all filled orders are publicly recorded on-chain anyway, so taking the direct\r
* transmission shortcut does not effectively preserve privacy.\r
* \r
* To invalidate an order, the owner must call the invalidateNonce function on the SignatureTransfer\r
* contract found in this.ROUTER().TRANSFER().\r
*/\r
function placeOrder(Intent calldata intent, bytes calldata signature) external {\r
verifySignature(intent, signature);\r
IReactor(REACTOR).signalIntent(intent, signature);\r
}\r
\r
/**\r
* Verify the signature of an order.\r
*/\r
function verifySignature(Intent calldata intent, bytes calldata sig) public view {\r
if (intent.filler != address(this)) revert WrongFiller();\r
IReactor(REACTOR).verify(intent, sig);\r
}\r
\r
/**\r
* Check if an order can be executed and if yes, returns the maximum amount of the tokenOut.\r
*/\r
function validateOrder(Intent calldata intent, bytes calldata sig) external view returns (uint256) {\r
verifySignature(intent, sig);\r
uint256 balance = IERC20(intent.tokenOut).balanceOf(intent.owner);\r
if (balance == 0) revert NoBalance(intent.tokenOut, intent.owner);\r
uint256 allowance = IERC20(intent.tokenOut).allowance(intent.owner, REACTOR);\r
if (allowance == 0) revert NoAllowance(intent.tokenOut, intent.owner, REACTOR);\r
uint256 permitted = ISignatureTransfer(REACTOR).getPermittedAmount(intent.owner, intent.amountOut, intent.nonce);\r
if (permitted == 0) revert NonceUsed(intent.owner, intent.nonce);\r
return permitted;\r
}\r
\r
/**\r
* Validates a match between a seller and a buyer intent and returns the maximum amount of tokens that can be traded.\r
*/\r
function validateMatch(Intent calldata sellerIntent, Intent calldata buyerIntent) external view returns (uint256) {\r
return IReactor(REACTOR).getMaxValidAmount(sellerIntent, buyerIntent, tradingFeeBips);\r
}\r
\r
function process(Intent calldata seller, bytes calldata sellerSig, Intent calldata buyer, bytes calldata buyerSig, uint256 tradedTokens, bool buyerIsTaker) external {\r
if (!isOpen()) revert MarketClosed(openFrom, openTo, block.timestamp);\r
if (router != address(0) && msg.sender != router) revert WrongRouter(msg.sender, router);\r
(uint256 proceeds, uint256 spread) = IReactor(REACTOR).process(seller, sellerSig, buyer, buyerSig, tradedTokens);\r
uint256 price = buyerIsTaker ? proceeds * 10000 / (10000 - tradingFeeBips) : proceeds + spread;\r
splitSpread(buyer.owner, seller, tradedTokens, price, buyerIsTaker ? buyer.owner : seller.owner, spread);\r
}\r
\r
function splitSpread(address buyer, Intent calldata seller, uint256 tradedTokens, uint256 price, address spreadRecipient, uint256 spread) internal {\r
uint256 fees = tradingFeeBips * price / 10000;\r
emit Trade(seller.owner, buyer, seller.tokenOut, tradedTokens, seller.tokenIn, price, fees);\r
if (spread > fees) {\r
IERC20(seller.tokenIn).transfer(spreadRecipient, spread - fees); // return excess spread to the taker who entered the order later\r
}\r
}\r
\r
/**\r
* Withdraw the accumulated fees applying a 50/50 split between the two addresses.\r
* \r
* The assumption is that this can be used to collect accumulated trading fees and to pay license fees\r
* to Aktionariat in the same transaction for convenience.\r
*/\r
function withdrawFees() external {\r
withdrawFees(CURRENCY, IERC20(CURRENCY).balanceOf(address(this)));\r
}\r
\r
function withdrawFees(address currency, uint256 amount) public onlyOwner {\r
uint256 split = amount * licenseShare / 10000;\r
IERC20(currency).transfer(owner, amount - split); // rounded up\r
IERC20(currency).transfer(LICENSE_FEE_RECIPIENT, split); // rounded down\r
emit LicenseFeePaid(currency, LICENSE_FEE_RECIPIENT, split);\r
}\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
"
},
"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/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/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT\r
//\r
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol\r
//\r
// Modifications:\r
// - Replaced Context._msgSender() with msg.sender\r
// - Made leaner\r
// - Extracted interface\r
\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
* @dev Contract module which provides a basic access control mechanism, where\r
* there is an account (an owner) that can be granted exclusive access to\r
* specific functions.\r
*\r
* By default, the owner account will be the one that deploys the contract. This\r
* can later be changed with {transferOwnership}.\r
*\r
* This module is used through inheritance. It will make available the modifier\r
* `onlyOwner`, which can be applied to your functions to restrict their use to\r
* the owner.\r
*/\r
contract Ownable {\r
\r
address public owner;\r
\r
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\r
\r
error Ownable_NotOwner(address sender);\r
\r
/**\r
* @dev Initializes the contract setting the deployer as the initial owner.\r
*/\r
constructor (address initialOwner) {\r
owner = initialOwner;\r
emit OwnershipTransferred(address(0), owner);\r
}\r
\r
modifier onlyOwner() {\r
_checkOwner();\r
_;\r
}\r
\r
/**\r
* @dev Transfers ownership of the contract to a new account (`newOwner`).\r
* Can only be called by the current owner.\r
*/\r
function transferOwnership(address newOwner) external onlyOwner {\r
emit OwnershipTransferred(owner, newOwner);\r
owner = newOwner;\r
}\r
\r
function _checkOwner() internal view {\r
if (msg.sender != owner) {\r
revert Ownable_NotOwner(msg.sender);\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
}"
},
"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
}"
}
},
"settings": {
"optimizer": {
"enabled": false,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": [],
"evmVersion": "prague"
}
}}
Submitted on: 2025-09-24 20:19:20
Comments
Log in to comment.
No comments yet.