Contract Name:
TokenMasterRouter
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @dev Storage data struct for stored approvals and order approvals
struct PackedApproval {
// Only used for partial fill position 1155 transfers
uint8 state;
// Amount allowed
uint200 amount;
// Permission expiry
uint48 expiration;
}
/// @dev Calldata data struct for order fill amounts
struct OrderFillAmounts {
uint256 orderStartAmount;
uint256 requestedFillAmount;
uint256 minimumFillAmount;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {OrderFillAmounts} from "../DataTypes.sol";
interface IPermitC {
/**
* =================================================
* ==================== Events =====================
* =================================================
*/
/// @dev Emitted when an approval is stored
event Approval(
address indexed owner,
address indexed token,
address indexed operator,
uint256 id,
uint200 amount,
uint48 expiration
);
/// @dev Emitted when a user increases their master nonce
event Lockdown(address indexed owner);
/// @dev Emitted when an order is opened
event OrderOpened(
bytes32 indexed orderId,
address indexed owner,
address indexed operator,
uint256 fillableQuantity
);
/// @dev Emitted when an order has a fill
event OrderFilled(
bytes32 indexed orderId,
address indexed owner,
address indexed operator,
uint256 amount
);
/// @dev Emitted when an order has been fully filled or cancelled
event OrderClosed(
bytes32 indexed orderId,
address indexed owner,
address indexed operator,
bool wasCancellation);
/// @dev Emitted when an order has an amount restored due to a failed transfer
event OrderRestored(
bytes32 indexed orderId,
address indexed owner,
uint256 amountRestoredToOrder
);
/**
* =================================================
* ============== Approval Transfers ===============
* =================================================
*/
function approve(uint256 tokenType, address token, uint256 id, address operator, uint200 amount, uint48 expiration) external;
function updateApprovalBySignature(
uint256 tokenType,
address token,
uint256 id,
uint256 nonce,
uint200 amount,
address operator,
uint48 approvalExpiration,
uint48 sigDeadline,
address owner,
bytes calldata signedPermit
) external;
function allowance(
address owner,
address operator,
uint256 tokenType,
address token,
uint256 id
) external view returns (uint256 amount, uint256 expiration);
/**
* =================================================
* ================ Signed Transfers ===============
* =================================================
*/
function registerAdditionalDataHash(string memory additionalDataTypeString) external;
function permitTransferFromERC721(
address token,
uint256 id,
uint256 nonce,
uint256 expiration,
address owner,
address to,
bytes calldata signedPermit
) external returns (bool isError);
function permitTransferFromWithAdditionalDataERC721(
address token,
uint256 id,
uint256 nonce,
uint256 expiration,
address owner,
address to,
bytes32 additionalData,
bytes32 advancedPermitHash,
bytes calldata signedPermit
) external returns (bool isError);
function permitTransferFromERC1155(
address token,
uint256 id,
uint256 nonce,
uint256 permitAmount,
uint256 expiration,
address owner,
address to,
uint256 transferAmount,
bytes calldata signedPermit
) external returns (bool isError);
function permitTransferFromWithAdditionalDataERC1155(
address token,
uint256 id,
uint256 nonce,
uint256 permitAmount,
uint256 expiration,
address owner,
address to,
uint256 transferAmount,
bytes32 additionalData,
bytes32 advancedPermitHash,
bytes calldata signedPermit
) external returns (bool isError);
function permitTransferFromERC20(
address token,
uint256 nonce,
uint256 permitAmount,
uint256 expiration,
address owner,
address to,
uint256 transferAmount,
bytes calldata signedPermit
) external returns (bool isError);
function permitTransferFromWithAdditionalDataERC20(
address token,
uint256 nonce,
uint256 permitAmount,
uint256 expiration,
address owner,
address to,
uint256 transferAmount,
bytes32 additionalData,
bytes32 advancedPermitHash,
bytes calldata signedPermit
) external returns (bool isError);
function isRegisteredTransferAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered);
function isRegisteredOrderAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered);
/**
* =================================================
* =============== Order Transfers =================
* =================================================
*/
function fillPermittedOrderERC1155(
bytes calldata signedPermit,
OrderFillAmounts calldata orderFillAmounts,
address token,
uint256 id,
address owner,
address to,
uint256 nonce,
uint48 expiration,
bytes32 orderId,
bytes32 advancedPermitHash
) external returns (uint256 quantityFilled, bool isError);
function fillPermittedOrderERC20(
bytes calldata signedPermit,
OrderFillAmounts calldata orderFillAmounts,
address token,
address owner,
address to,
uint256 nonce,
uint48 expiration,
bytes32 orderId,
bytes32 advancedPermitHash
) external returns (uint256 quantityFilled, bool isError);
function closePermittedOrder(
address owner,
address operator,
uint256 tokenType,
address token,
uint256 id,
bytes32 orderId
) external;
function allowance(
address owner,
address operator,
uint256 tokenType,
address token,
uint256 id,
bytes32 orderId
) external view returns (uint256 amount, uint256 expiration);
/**
* =================================================
* ================ Nonce Management ===============
* =================================================
*/
function invalidateUnorderedNonce(uint256 nonce) external;
function isValidUnorderedNonce(address owner, uint256 nonce) external view returns (bool isValid);
function lockdown() external;
function masterNonce(address owner) external view returns (uint256);
/**
* =================================================
* ============== Transfer Functions ===============
* =================================================
*/
function transferFromERC721(
address from,
address to,
address token,
uint256 id
) external returns (bool isError);
function transferFromERC1155(
address from,
address to,
address token,
uint256 id,
uint256 amount
) external returns (bool isError);
function transferFromERC20(
address from,
address to,
address token,
uint256 amount
) external returns (bool isError);
/**
* =================================================
* ============ Signature Verification =============
* =================================================
*/
function domainSeparatorV4() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.8;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @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].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*
* _Available since v3.4._
*
* @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
*/
abstract contract EIP712 {
bytes32 private constant _TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
// 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 _cachedDomainSeparator;
uint256 private immutable _cachedChainId;
bytes32 private immutable _hashedName;
bytes32 private immutable _hashedVersion;
/**
* @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) {
_hashedName = keccak256(bytes(name));
_hashedVersion = keccak256(bytes(version));
_cachedChainId = block.chainid;
_cachedDomainSeparator = _buildDomainSeparator();
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (block.chainid == _cachedChainId) {
return _cachedDomainSeparator;
} else {
return _buildDomainSeparator();
}
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, 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);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
// 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.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32")
mstore(0x1c, hash)
message := keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x00", validator, data));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (metatx/ERC2771Context.sol)
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/Context.sol";
import "./interfaces/ITrustedForwarderFactory.sol";
/**
* @title TrustedForwarderERC2771Context
* @author Limit Break, Inc.
* @notice Context variant that utilizes the TrustedForwarderFactory contract to determine if the sender is a trusted forwarder.
*/
abstract contract TrustedForwarderERC2771Context is Context {
ITrustedForwarderFactory private immutable _factory;
constructor(address factory) {
_factory = ITrustedForwarderFactory(factory);
}
/**
* @notice Returns true if the sender is a trusted forwarder, false otherwise.
*
* @dev This function is required by ERC2771Context.
*
* @param forwarder The address to check.
* @return True if the provided address is a trusted forwarder, false otherwise.
*/
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
return _factory.isTrustedForwarder(forwarder);
}
function _msgSender() internal view virtual override returns (address sender) {
if (_factory.isTrustedForwarder(msg.sender)) {
if (msg.data.length >= 20) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
/// @solidity memory-safe-assembly
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return super._msgSender();
}
} else {
return super._msgSender();
}
}
function _msgData() internal view virtual override returns (bytes calldata data) {
if (_factory.isTrustedForwarder(msg.sender)) {
assembly {
let len := calldatasize()
// Create a slice that defaults to the entire calldata
data.offset := 0
data.length := len
// If the calldata is > 20 bytes, it contains the sender address at the end
// and needs to be truncated
if gt(len, 0x14) {
data.length := sub(len, 0x14)
}
}
} else {
return super._msgData();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface ITrustedForwarderFactory {
error TrustedForwarderFactory__TrustedForwarderInitFailed(address admin, address appSigner);
event TrustedForwarderCreated(address indexed trustedForwarder);
function cloneTrustedForwarder(address admin, address appSigner, bytes32 salt)
external
returns (address trustedForwarder);
function forwarders(address) external view returns (bool);
function isTrustedForwarder(address sender) external view returns (bool);
function trustedForwarderImplementation() external view returns (address);
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity ^0.8.0;
/*
# PolyForm Strict License 1.0.0
<https://polyformproject.org/licenses/strict/1.0.0>
## Acceptance
In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.
## Copyright License
The licensor grants you a copyright license for the software
to do everything you might do with the software that would
otherwise infringe the licensor's copyright in it for any
permitted purpose, other than distributing the software or
making changes or new works based on the software.
## Patent License
The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.
## Noncommercial Purposes
Any noncommercial purpose is a permitted purpose.
## Personal Uses
Personal use for research, experiment, and testing for
the benefit of public knowledge, personal study, private
entertainment, hobby projects, amateur pursuits, or religious
observance, without any anticipated commercial application,
is use for a permitted purpose.
## Noncommercial Organizations
Use by any charitable organization, educational institution,
public research organization, public safety or health
organization, environmental protection organization,
or government institution is use for a permitted purpose
regardless of the source of funding or obligations resulting
from the funding.
## Fair Use
You may have "fair use" rights for the software under the
law. These terms do not limit them.
## No Other Rights
These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else. These terms do not imply
any other licenses.
## Patent Defense
If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.
## Violations
The first time you are notified in writing that you have
violated any of these terms, or done anything with the software
not covered by your licenses, your licenses can nonetheless
continue if you come into full compliance with these terms,
and take practical steps to correct past violations, within
32 days of receiving notice. Otherwise, all your licenses
end immediately.
## No Liability
***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.
**You** refers to the individual or entity agreeing to these
terms.
**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization. **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the
software under these terms.
**Use** means anything you do with the software requiring one
of your licenses.
*/
pragma solidity ^0.8.4;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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);
/**
* @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);
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
library SafeERC20 {
/**
* @dev A gas efficient, and fallback-safe method to transfer ERC20 tokens owned by the contract.
*
* @param tokenAddress The address of the token to transfer.
* @param to The address to transfer tokens to.
* @param amount The amount of tokens to transfer.
*
* @return isError True if there was an error transferring, false if the call was successful.
*/
function safeTransfer(
address tokenAddress,
address to,
uint256 amount
) internal returns(bool isError) {
assembly {
function _callTransfer(_tokenAddress, _to, _amount) -> _isError {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x60))
mstore(ptr, 0xa9059cbb)
mstore(add(0x20, ptr), _to)
mstore(add(0x40, ptr), _amount)
if call(gas(), _tokenAddress, 0, add(ptr, 0x1C), 0x44, 0x00, 0x00) {
if lt(returndatasize(), 0x20) {
_isError := iszero(extcodesize(_tokenAddress))
leave
}
returndatacopy(0x00, 0x00, 0x20)
_isError := iszero(mload(0x00))
leave
}
_isError := true
}
isError := _callTransfer(tokenAddress, to, amount)
}
}
/**
* @dev A gas efficient, and fallback-safe method to transfer ERC20 tokens owned by another address.
*
* @param tokenAddress The address of the token to transfer.
* @param from The address to transfer tokens from.
* @param to The address to transfer tokens to.
* @param amount The amount of tokens to transfer.
*
* @return isError True if there was an error transferring, false if the call was successful.
*/
function safeTransferFrom(
address tokenAddress,
address from,
address to,
uint256 amount
) internal returns(bool isError) {
assembly {
function _callTransferFrom(_tokenAddress, _from, _to, _amount) -> _isError {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x80))
mstore(ptr, 0x23b872dd)
mstore(add(0x20, ptr), _from)
mstore(add(0x40, ptr), _to)
mstore(add(0x60, ptr), _amount)
if call(gas(), _tokenAddress, 0, add(ptr, 0x1C), 0x64, 0x00, 0x00) {
if lt(returndatasize(), 0x20) {
_isError := iszero(extcodesize(_tokenAddress))
leave
}
returndatacopy(0x00, 0x00, 0x20)
_isError := iszero(mload(0x00))
leave
}
_isError := true
}
isError := _callTransferFrom(tokenAddress, from, to, amount)
}
}
/**
* @dev A gas efficient, and fallback-safe method to set approval on ERC20 tokens.
*
* @param tokenAddress The address of the token to transfer.
* @param spender The address to allow to spend tokens.
* @param allowance The amount of tokens to allow `spender` to transfer.
*
* @return isError True if there was an error setting allowance, false if the call was successful.
*/
function safeApprove(
address tokenAddress,
address spender,
uint256 allowance
) internal returns(bool isError) {
assembly {
function _callApprove(_tokenAddress, _spender, _allowance) -> _isError {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x60))
mstore(ptr, 0x095ea7b3)
mstore(add(0x20, ptr), _spender)
mstore(add(0x40, ptr), _allowance)
if call(gas(), _tokenAddress, 0, add(ptr, 0x1C), 0x44, 0x00, 0x00) {
if lt(returndatasize(), 0x20) {
_isError := iszero(extcodesize(_tokenAddress))
leave
}
returndatacopy(0x00, 0x00, 0x20)
_isError := iszero(mload(0x00))
leave
}
_isError := true
}
isError := _callApprove(tokenAddress, spender, allowance)
}
}
/**
* @dev A gas efficient, and fallback-safe method to set approval on ERC20 tokens.
* @dev If the initial approve fails, it will retry setting the allowance to zero and then
* @dev to the new allowance.
*
* @param tokenAddress The address of the token to transfer.
* @param spender The address to allow to spend tokens.
* @param allowance The amount of tokens to allow `spender` to transfer.
*
* @return isError True if there was an error setting allowance, false if the call was successful.
*/
function safeApproveWithRetryAfterZero(
address tokenAddress,
address spender,
uint256 allowance
) internal returns(bool isError) {
assembly {
function _callApprove(_ptr, _tokenAddress, _spender, _allowance) -> _isError {
mstore(add(0x40, _ptr), _allowance)
if call(gas(), _tokenAddress, 0, add(_ptr, 0x1C), 0x44, 0x00, 0x00) {
if lt(returndatasize(), 0x20) {
_isError := iszero(extcodesize(_tokenAddress))
leave
}
returndatacopy(0x00, 0x00, 0x20)
_isError := iszero(mload(0x00))
leave
}
_isError := true
}
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x60))
mstore(ptr, 0x095ea7b3)
mstore(add(0x20, ptr), spender)
isError := _callApprove(ptr, tokenAddress, spender, allowance)
if isError {
pop(_callApprove(ptr, tokenAddress, spender, 0x00))
isError := _callApprove(ptr, tokenAddress, spender, allowance)
}
}
}
}
pragma solidity ^0.8.4;
// General Purpose Custom Errors
error Error__BadConstructorArgument();
// Authorization Errors
error RoleClient__Unauthorized();
// Extensible Custom Errors
error Extensible__ConflictingFunctionSelectorAlreadyInstalled();
error Extensible__ExtensionAlreadyInstalled();
error Extensible__ExtensionNotInstalled();
error Extensible__InvalidExtension();
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/**
* @title EfficientHash
*
* @author Limit Break
*
* @notice Performs keccak256 hashing of value type parameters more efficiently than
* @notice high-level Solidity by utilizing scratch space for one or two values and
* @notice efficient utilization of memory for parameter counts greater than two.
*
* @notice Gas savings for EfficientHash compared to keccak256(abi.encode(...)):
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 1 / 67 / 67
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 5 / 66 / 66
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 10 / 58 / 58
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 15 / 1549 / 565
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 20 / 3379 / 1027
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 25 / 5807 / 1650
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 50 / 23691 / 10107
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 75 / 69164 / 41620
* @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 99 / 172694 / 126646
*
* @dev Notes:
* @dev - `efficientHash` is overloaded for parameter counts between one and eight.
* @dev - Parameter counts between nine and sixteen require two functions to avoid
* @dev stack too deep errors. Each parameter count has a dedicated set of functions
* @dev (`efficientHashNineStep1`/`efficientHashNineStep2` ... `efficientHashSixteenStep1`/`efficientHashSixteenStep2`)
* @dev that must both be called to get the hash.
* @dev `Step1` functions take eight parameters and return a memory pointer that is passed to `Step2`
* @dev `Step2` functions take the remaining parameters and return the hash of the values
* @dev Example:
* @dev bytes32 hash = EfficientHash.efficientHashElevenStep2(
* @dev EfficientHash.efficientHashElevenStep1(
* @dev value1,
* @dev value2,
* @dev value3,
* @dev value4,
* @dev value5,
* @dev value6,
* @dev value7,
* @dev value8
* @dev ),
* @dev value9,
* @dev value10,
* @dev value11,
* @dev );
* @dev - Parameter counts greater than sixteen must use the `Extension` functions.
* @dev Extension starts with `efficientHashExtensionStart` which takes the number
* @dev of parameters and the first eight parameters as an input and returns a
* @dev memory pointer that is passed to the `Continue` and `End` functions.
* @dev While the number of parameters remaining is greater than eight, call the
* @dev `efficientHashExtensionContinue` function with the pointer value and
* @dev the next eight values.
* @dev When the number of parameters remaining is less than or equal to eight
* @dev call the `efficientHashExtensionEnd` function with the pointer value
* @dev and remaining values.
* @dev Example:
* @dev bytes32 hash = EfficientHash.efficientHashExtensionEnd(
* @dev EfficientHash.efficientHashExtensionContinue(
* @dev EfficientHash.efficientHashExtensionStart(
* @dev 23,
* @dev value1,
* @dev value2,
* @dev value3,
* @dev value4,
* @dev value5,
* @dev value6,
* @dev value7,
* @dev value8
* @dev ),
* @dev value9,
* @dev value10,
* @dev value11,
* @dev value12,
* @dev value13,
* @dev value14,
* @dev value15,
* @dev value16
* @dev ),
* @dev value17,
* @dev value18,
* @dev value19,
* @dev value20,
* @dev value21,
* @dev value22,
* @dev value23
* @dev );
*/
library EfficientHash {
/**
* @notice Hashes one value type.
*
* @param value The value to be hashed.
*
* @return hash The hash of the value.
*/
function efficientHash(bytes32 value) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(0x00, value)
hash := keccak256(0x00, 0x20)
}
}
/**
* @notice Hashes two value types.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHash(bytes32 value1, bytes32 value2) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(0x00, value1)
mstore(0x20, value2)
hash := keccak256(0x00, 0x40)
}
}
/**
* @notice Hashes three value types.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHash(bytes32 value1, bytes32 value2, bytes32 value3) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x60))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
hash := keccak256(ptr, 0x60)
}
}
/**
* @notice Hashes four value types.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHash(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x80))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
hash := keccak256(ptr, 0x80)
}
}
/**
* @notice Hashes five value types.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHash(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0xA0))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
hash := keccak256(ptr, 0xA0)
}
}
/**
* @notice Hashes six value types.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHash(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0xC0))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
hash := keccak256(ptr, 0xC0)
}
}
/**
* @notice Hashes seven value types.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHash(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0xE0))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
hash := keccak256(ptr, 0xE0)
}
}
/**
* @notice Hashes eight value types.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHash(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x100))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
hash := keccak256(ptr, 0x100)
}
}
/**
* @notice Step one of hashing nine values. Must be followed by `efficientHashNineStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashNineStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x120))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing nine values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashNineStep2(
uint256 ptr,
bytes32 value9
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
hash := keccak256(ptr, 0x120)
}
}
/**
* @notice Step one of hashing ten values. Must be followed by `efficientHashTenStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashTenStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x140))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing ten values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
* @param value10 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashTenStep2(
uint256 ptr,
bytes32 value9, bytes32 value10
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
mstore(add(ptr, 0x120), value10)
hash := keccak256(ptr, 0x140)
}
}
/**
* @notice Step one of hashing eleven values. Must be followed by `efficientHashElevenStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashElevenStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x160))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing eleven values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
* @param value10 Value to be hashed.
* @param value11 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashElevenStep2(
uint256 ptr,
bytes32 value9, bytes32 value10, bytes32 value11
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
mstore(add(ptr, 0x120), value10)
mstore(add(ptr, 0x140), value11)
hash := keccak256(ptr, 0x160)
}
}
/**
* @notice Step one of hashing twelve values. Must be followed by `efficientHashTwelveStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashTwelveStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x180))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing twelve values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
* @param value10 Value to be hashed.
* @param value11 Value to be hashed.
* @param value12 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashTwelveStep2(
uint256 ptr,
bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
mstore(add(ptr, 0x120), value10)
mstore(add(ptr, 0x140), value11)
mstore(add(ptr, 0x160), value12)
hash := keccak256(ptr, 0x180)
}
}
/**
* @notice Step one of hashing thirteen values. Must be followed by `efficientHashThirteenStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashThirteenStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x1A0))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing thirteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
* @param value10 Value to be hashed.
* @param value11 Value to be hashed.
* @param value12 Value to be hashed.
* @param value13 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashThirteenStep2(
uint256 ptr,
bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
bytes32 value13
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
mstore(add(ptr, 0x120), value10)
mstore(add(ptr, 0x140), value11)
mstore(add(ptr, 0x160), value12)
mstore(add(ptr, 0x180), value13)
hash := keccak256(ptr, 0x1A0)
}
}
/**
* @notice Step one of hashing fourteen values. Must be followed by `efficientHashFourteenStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashFourteenStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x1C0))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing fourteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
* @param value10 Value to be hashed.
* @param value11 Value to be hashed.
* @param value12 Value to be hashed.
* @param value13 Value to be hashed.
* @param value14 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashFourteenStep2(
uint256 ptr,
bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
bytes32 value13, bytes32 value14
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
mstore(add(ptr, 0x120), value10)
mstore(add(ptr, 0x140), value11)
mstore(add(ptr, 0x160), value12)
mstore(add(ptr, 0x180), value13)
mstore(add(ptr, 0x1A0), value14)
hash := keccak256(ptr, 0x1C0)
}
}
/**
* @notice Step one of hashing fifteen values. Must be followed by `efficientHashFifteenStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashFifteenStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x1E0))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing fifteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
* @param value10 Value to be hashed.
* @param value11 Value to be hashed.
* @param value12 Value to be hashed.
* @param value13 Value to be hashed.
* @param value14 Value to be hashed.
* @param value15 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashFifteenStep2(
uint256 ptr,
bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
bytes32 value13, bytes32 value14, bytes32 value15
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
mstore(add(ptr, 0x120), value10)
mstore(add(ptr, 0x140), value11)
mstore(add(ptr, 0x160), value12)
mstore(add(ptr, 0x180), value13)
mstore(add(ptr, 0x1A0), value14)
mstore(add(ptr, 0x1C0), value15)
hash := keccak256(ptr, 0x1E0)
}
}
/**
* @notice Step one of hashing sixteen values. Must be followed by `efficientHashSixteenStep2` to hash the values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashSixteenStep1(
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(ptr, 0x200))
mstore(ptr, value1)
mstore(add(ptr, 0x20), value2)
mstore(add(ptr, 0x40), value3)
mstore(add(ptr, 0x60), value4)
mstore(add(ptr, 0x80), value5)
mstore(add(ptr, 0xA0), value6)
mstore(add(ptr, 0xC0), value7)
mstore(add(ptr, 0xE0), value8)
}
}
/**
* @notice Step two of hashing sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value9 Value to be hashed.
* @param value10 Value to be hashed.
* @param value11 Value to be hashed.
* @param value12 Value to be hashed.
* @param value13 Value to be hashed.
* @param value14 Value to be hashed.
* @param value15 Value to be hashed.
* @param value16 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashSixteenStep2(
uint256 ptr,
bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
bytes32 value13, bytes32 value14, bytes32 value15, bytes32 value16
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
mstore(add(ptr, 0x100), value9)
mstore(add(ptr, 0x120), value10)
mstore(add(ptr, 0x140), value11)
mstore(add(ptr, 0x160), value12)
mstore(add(ptr, 0x180), value13)
mstore(add(ptr, 0x1A0), value14)
mstore(add(ptr, 0x1C0), value15)
mstore(add(ptr, 0x1E0), value16)
hash := keccak256(ptr, 0x200)
}
}
/**
* @notice Step one of hashing more than sixteen values.
* @notice Must be followed by at least one call to
* @notice `efficientHashExtensionContinue` and completed with
* @notice a call to `efficientHashExtensionEnd` with the remaining
* @notice values.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptr The memory pointer location for the values to hash.
*/
function efficientHashExtensionStart(
uint256 numberOfValues,
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
mstore(0x40, add(add(ptr, 0x20), mul(numberOfValues, 0x20)))
mstore(ptr, 0x100)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
mstore(add(ptr, 0x80), value4)
mstore(add(ptr, 0xA0), value5)
mstore(add(ptr, 0xC0), value6)
mstore(add(ptr, 0xE0), value7)
mstore(add(ptr, 0x100), value8)
}
}
/**
* @notice Second step of hashing more than sixteen values.
* @notice Adds another eight values to the values to be hashed.
* @notice May be called as many times as necessary until the values
* @notice remaining to be added to the hash is less than or equal to
* @notice eight.
*
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return ptrReturn The memory pointer location for the values to hash.
*/
function efficientHashExtensionContinue(
uint256 ptr,
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(uint256 ptrReturn) {
assembly ("memory-safe") {
ptrReturn := ptr
let length := mload(ptrReturn)
mstore(ptrReturn, add(length, 0x100))
ptr := add(ptrReturn, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
mstore(add(ptr, 0x80), value4)
mstore(add(ptr, 0xA0), value5)
mstore(add(ptr, 0xC0), value6)
mstore(add(ptr, 0xE0), value7)
mstore(add(ptr, 0x100), value8)
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
hash := keccak256(add(ptrStart, 0x20), add(length, 0x20))
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1, bytes32 value2
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
hash := keccak256(add(ptrStart, 0x20), add(length, 0x40))
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1, bytes32 value2, bytes32 value3
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
hash := keccak256(add(ptrStart, 0x20), add(length, 0x60))
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
mstore(add(ptr, 0x80), value4)
hash := keccak256(add(ptrStart, 0x20), add(length, 0x80))
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
mstore(add(ptr, 0x80), value4)
mstore(add(ptr, 0xA0), value5)
hash := keccak256(add(ptrStart, 0x20), add(length, 0xA0))
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
mstore(add(ptr, 0x80), value4)
mstore(add(ptr, 0xA0), value5)
mstore(add(ptr, 0xC0), value6)
hash := keccak256(add(ptrStart, 0x20), add(length, 0xC0))
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
mstore(add(ptr, 0x80), value4)
mstore(add(ptr, 0xA0), value5)
mstore(add(ptr, 0xC0), value6)
mstore(add(ptr, 0xE0), value7)
hash := keccak256(add(ptrStart, 0x20), add(length, 0xE0))
}
}
/**
* @notice Final step of hashing more than sixteen values.
*
* @param ptr The memory pointer location for the values to hash.
* @param value1 Value to be hashed.
* @param value2 Value to be hashed.
* @param value3 Value to be hashed.
* @param value4 Value to be hashed.
* @param value5 Value to be hashed.
* @param value6 Value to be hashed.
* @param value7 Value to be hashed.
* @param value8 Value to be hashed.
*
* @return hash The hash of the values.
*/
function efficientHashExtensionEnd(
uint256 ptr,
bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
) internal pure returns(bytes32 hash) {
assembly ("memory-safe") {
let ptrStart := ptr
let length := mload(ptrStart)
ptr := add(ptrStart, length)
mstore(add(ptr, 0x20), value1)
mstore(add(ptr, 0x40), value2)
mstore(add(ptr, 0x60), value3)
mstore(add(ptr, 0x80), value4)
mstore(add(ptr, 0xA0), value5)
mstore(add(ptr, 0xC0), value6)
mstore(add(ptr, 0xE0), value7)
mstore(add(ptr, 0x100), value8)
hash := keccak256(add(ptrStart, 0x20), add(length, 0x100))
}
}
}
pragma solidity ^0.8.24;
library StorageTstorish {
// keccak256(abi.encode(uint256(keccak256("storage.Tstorish")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant DATA_STORAGE_SLOT =
0xdacd49f6a6c42b45a5c3d195b83b324104542d9147bb8064a39c6a8d23ba9b00;
struct Data {
// Indicates if TSTORE support has been activated during or post-deployment.
bool tstoreSupport;
}
function data() internal pure returns (Data storage ptr) {
bytes32 slot = DATA_STORAGE_SLOT;
assembly {
ptr.slot := slot
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./StorageTstorish.sol";
/**
* @title Tstorish
* @notice Based on https://github.com/ProjectOpenSea/tstorish/commit/a81ed74453ed7b9fe7e96a9906bc4def19b73e33
*/
abstract contract Tstorish {
/*
* ------------------------------------------------------------------------+
* Opcode | Mnemonic | Stack | Memory |
* ------------------------------------------------------------------------|
* 60 0x02 | PUSH1 0x02 | 0x02 | |
* 60 0x1e | PUSH1 0x1e | 0x1e 0x02 | |
* 61 0x3d5c | PUSH2 0x3d5c | 0x3d5c 0x1e 0x02 | |
* 3d | RETURNDATASIZE | 0 0x3d5c 0x1e 0x02 | |
* |
* :: store deployed bytecode in memory: (3d) RETURNDATASIZE (5c) TLOAD :: |
* 52 | MSTORE | 0x1e 0x02 | [0..0x20): 0x3d5c |
* f3 | RETURN | | [0..0x20): 0x3d5c |
* ------------------------------------------------------------------------+
*/
uint256 constant _TLOAD_TEST_PAYLOAD = 0x6002_601e_613d5c_3d_52_f3;
uint256 constant _TLOAD_TEST_PAYLOAD_LENGTH = 0x0a;
uint256 constant _TLOAD_TEST_PAYLOAD_OFFSET = 0x16;
// Declare an immutable variable to store the tstore test contract address.
address private immutable _tloadTestContract;
// Declare an immutable variable to store the initial TSTORE support status.
bool internal immutable _tstoreInitialSupport;
// Declare an immutable function type variable for the _setTstorish function
// based on chain support for tstore at time of deployment.
function(uint256,uint256) internal immutable _setTstorish;
// Declare an immutable function type variable for the _getTstorish function
// based on chain support for tstore at time of deployment.
function(uint256) view returns (uint256) internal immutable _getTstorish;
// Declare an immutable function type variable for the _clearTstorish function
// based on chain support for tstore at time of deployment.
function(uint256) internal immutable _clearTstorish;
// Declare a few custom revert error types.
error TStoreAlreadyActivated();
error TStoreNotSupported();
error TloadTestContractDeploymentFailed();
error OnlyDirectCalls();
/**
* @dev Determine TSTORE availability during deployment. This involves
* attempting to deploy a contract that utilizes TLOAD as part of the
* contract construction bytecode, and configuring initial support for
* using TSTORE in place of SSTORE based on the result.
*/
constructor() {
// Deploy the contract testing TLOAD support and store the address.
address tloadTestContract = _prepareTloadTest();
// Ensure the deployment was successful.
if (tloadTestContract == address(0)) {
revert TloadTestContractDeploymentFailed();
}
// Determine if TSTORE is supported.
_tstoreInitialSupport = StorageTstorish.data().tstoreSupport = _testTload(tloadTestContract);
if (_tstoreInitialSupport) {
// If TSTORE is supported, set functions to their versions that use
// tstore/tload directly without support checks.
_setTstorish = _setTstore;
_getTstorish = _getTstore;
_clearTstorish = _clearTstore;
} else {
// If TSTORE is not supported, set functions to their versions that
// fallback to sstore/sload until tstoreSupport is true.
_setTstorish = _setTstorishWithSstoreFallback;
_getTstorish = _getTstorishWithSloadFallback;
_clearTstorish = _clearTstorishWithSstoreFallback;
}
// Set the address of the deployed TLOAD test contract as an immutable.
_tloadTestContract = tloadTestContract;
}
/**
* @dev Called internally when tstore is activated by an external call to
* `__activateTstore`. Developers must override this function and handle
* relevant transfers of data from regular storage to transient storage *OR*
* revert the transaction if it is in a state that should not support the activation
* of tstore.
*/
function _onTstoreSupportActivated() internal virtual;
/**
* @dev External function to activate TSTORE usage. Does not need to be
* called if TSTORE is supported from deployment, and only needs to be
* called once. Reverts if TSTORE has already been activated or if the
* opcode is not available. Note that this must be called directly from
* an externally-owned account to avoid potential reentrancy issues.
*/
function __activateTstore() external {
// Determine if TSTORE can potentially be activated.
if (_tstoreInitialSupport || StorageTstorish.data().tstoreSupport) {
revert TStoreAlreadyActivated();
}
// Determine if TSTORE can be activated and revert if not.
if (!_testTload(_tloadTestContract)) {
revert TStoreNotSupported();
}
// Mark TSTORE as activated.
StorageTstorish.data().tstoreSupport = true;
_onTstoreSupportActivated();
}
/**
* @dev Private function to set a TSTORISH value. Assigned to _setTstorish
* internal function variable at construction if chain has tstore support.
*
* @param storageSlot The slot to write the TSTORISH value to.
* @param value The value to write to the given storage slot.
*/
function _setTstore(uint256 storageSlot, uint256 value) internal {
assembly {
tstore(storageSlot, value)
}
}
/**
* @dev Private function to set a TSTORISH value with sstore fallback.
* Assigned to _setTstorish internal function variable at construction
* if chain does not have tstore support.
*
* @param storageSlot The slot to write the TSTORISH value to.
* @param value The value to write to the given storage slot.
*/
function _setTstorishWithSstoreFallback(uint256 storageSlot, uint256 value) internal {
if (StorageTstorish.data().tstoreSupport) {
assembly {
tstore(storageSlot, value)
}
} else {
assembly {
sstore(storageSlot, value)
}
}
}
/**
* @dev Private function to read a TSTORISH value. Assigned to _getTstorish
* internal function variable at construction if chain has tstore support.
*
* @param storageSlot The slot to read the TSTORISH value from.
*
* @return value The TSTORISH value at the given storage slot.
*/
function _getTstore(
uint256 storageSlot
) internal view returns (uint256 value) {
assembly {
value := tload(storageSlot)
}
}
/**
* @dev Private function to read a TSTORISH value with sload fallback.
* Assigned to _getTstorish internal function variable at construction
* if chain does not have tstore support.
*
* @param storageSlot The slot to read the TSTORISH value from.
*
* @return value The TSTORISH value at the given storage slot.
*/
function _getTstorishWithSloadFallback(
uint256 storageSlot
) internal view returns (uint256 value) {
if (StorageTstorish.data().tstoreSupport) {
assembly {
value := tload(storageSlot)
}
} else {
assembly {
value := sload(storageSlot)
}
}
}
/**
* @dev Private function to clear a TSTORISH value. Assigned to _clearTstorish internal
* function variable at construction if chain has tstore support.
*
* @param storageSlot The slot to clear the TSTORISH value for.
*/
function _clearTstore(uint256 storageSlot) internal {
assembly {
tstore(storageSlot, 0)
}
}
/**
* @dev Private function to clear a TSTORISH value with sstore fallback.
* Assigned to _clearTstorish internal function variable at construction
* if chain does not have tstore support.
*
* @param storageSlot The slot to clear the TSTORISH value for.
*/
function _clearTstorishWithSstoreFallback(uint256 storageSlot) internal {
if (StorageTstorish.data().tstoreSupport) {
assembly {
tstore(storageSlot, 0)
}
} else {
assembly {
sstore(storageSlot, 0)
}
}
}
/**
* @dev Private function to copy a value from storage to transient storage at the same slot.
* Useful when tstore is activated on a chain that didn't initially support it.
*/
function _copyFromStorageToTransient(uint256 storageSlot) internal {
if (StorageTstorish.data().tstoreSupport) {
assembly {
tstore(storageSlot, sload(storageSlot))
}
} else {
revert TStoreNotSupported();
}
}
/**
* @dev Private function to deploy a test contract that utilizes TLOAD as
* part of its fallback logic.
*/
function _prepareTloadTest() private returns (address contractAddress) {
// Utilize assembly to deploy a contract testing TLOAD support.
assembly {
// Write the contract deployment code payload to scratch space.
mstore(0, _TLOAD_TEST_PAYLOAD)
// Deploy the contract.
contractAddress := create(
0,
_TLOAD_TEST_PAYLOAD_OFFSET,
_TLOAD_TEST_PAYLOAD_LENGTH
)
}
}
/**
* @dev Private view function to determine if TSTORE/TLOAD are supported by
* the current EVM implementation by attempting to call the test
* contract, which utilizes TLOAD as part of its fallback logic.
*/
function _testTload(
address tloadTestContract
) private view returns (bool ok) {
// Call the test contract, which will perform a TLOAD test. If the call
// does not revert, then TLOAD/TSTORE is supported. Do not forward all
// available gas, as all forwarded gas will be consumed on revert.
(ok, ) = tloadTestContract.staticcall{ gas: gasleft() / 10 }("");
}
}
pragma solidity ^0.8.4;
interface IRoleClient {
function onRoleHolderChanged(bytes32 /*role*/, address /*roleHolder*/) external;
}
pragma solidity ^0.8.4;
interface IRoleServer {
function getRoleHolder(bytes32 /*role*/) external view returns (address);
function setRoleHolder(bytes32 /*role*/, address /*authority*/, address[] calldata /*clients*/) external;
}
pragma solidity ^0.8.4;
import "./IRoleClient.sol";
import "./IRoleServer.sol";
import "../Errors.sol";
abstract contract RoleClientBase is IRoleClient {
struct RoleRecord {
address roleHolder;
uint64 expiration;
uint32 ttl;
}
IRoleServer private immutable _roleServer;
struct RoleClientStorage {
mapping (bytes32 role => RoleRecord record) roleRecords;
}
bytes32 private constant ROLE_CLIENT_STORAGE_SLOT = keccak256("storage.RoleClientBase");
function roleClientStorage() internal pure returns (RoleClientStorage storage ptr) {
bytes32 slot = ROLE_CLIENT_STORAGE_SLOT;
assembly {
ptr.slot := slot
}
}
constructor(address roleServer) {
if (roleServer == address(0)) {
revert Error__BadConstructorArgument();
}
_roleServer = IRoleServer(roleServer);
}
modifier callerHasRole(bytes32 role) {
_requireCallerHasRole(role);
_;
}
function onRoleHolderChanged(bytes32 role, address roleHolder) external {
if (msg.sender != address(_roleServer)) {
revert RoleClient__Unauthorized();
}
unchecked {
RoleRecord storage record = roleClientStorage().roleRecords[role];
record.roleHolder = roleHolder;
record.expiration = uint64(block.timestamp + record.ttl);
}
}
function _getRoleHolder(bytes32 role) internal returns (address roleHolder) {
RoleRecord storage record = roleClientStorage().roleRecords[role];
roleHolder = record.roleHolder;
unchecked {
if (record.expiration < block.timestamp) {
roleHolder = _roleServer.getRoleHolder(role);
record.roleHolder = roleHolder;
record.expiration = uint64(block.timestamp + record.ttl);
}
}
}
function _getRoleHolderView(bytes32 role) internal view returns (address roleHolder) {
RoleRecord storage record = roleClientStorage().roleRecords[role];
roleHolder = record.roleHolder;
unchecked {
if (record.expiration < block.timestamp) {
roleHolder = _roleServer.getRoleHolder(role);
}
}
}
function _requireCallerHasRole(bytes32 role) internal {
if (msg.sender != _getRoleHolder(role)) {
revert RoleClient__Unauthorized();
}
}
function _setupRole(bytes32 role, uint32 ttl) internal {
unchecked {
RoleRecord storage record = roleClientStorage().roleRecords[role];
record.roleHolder = _roleServer.getRoleHolder(role);
record.ttl = ttl;
record.expiration = uint64(block.timestamp) + ttl;
}
}
}
pragma solidity ^0.8.4;
import "./RoleClientBase.sol";
abstract contract RoleSetClient is RoleClientBase {
constructor(address roleServer, bytes32 roleSet) RoleClientBase(roleServer) {
_setupRoles(roleSet);
}
function _hashRoleSetRole(
bytes32 roleSet,
bytes32 baseRole
) internal pure returns (bytes32 role) {
role = keccak256(abi.encode(roleSet, baseRole));
}
function _setupRoles(bytes32 roleSet) internal virtual;
}
pragma solidity ^0.8.24;
import "../misc/Tstorish.sol";
/**
* @dev Variant of {ReentrancyGuard} that uses transient storage.
*
* NOTE: This variant only works on networks where EIP-1153 is available.
*/
abstract contract TstorishReentrancyGuard is Tstorish {
// keccak256(abi.encode(uint256(keccak256("storage.TstorishReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
uint256 private constant REENTRANCY_GUARD_STORAGE =
0xeff9701f8ef712cda0f707f0a4f48720f142bf7e1bce9d4747c32b4eeb890500;
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() Tstorish() {
if (!_tstoreInitialSupport) {
_setTstorish(REENTRANCY_GUARD_STORAGE, NOT_ENTERED);
}
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_getTstorish(REENTRANCY_GUARD_STORAGE) == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_setTstorish(REENTRANCY_GUARD_STORAGE, ENTERED);
}
function _nonReentrantAfter() private {
_setTstorish(REENTRANCY_GUARD_STORAGE, NOT_ENTERED);
}
function _onTstoreSupportActivated() internal virtual override {
_copyFromStorageToTransient(REENTRANCY_GUARD_STORAGE);
}
}
pragma solidity ^0.8.4;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
/// @dev Constant value for BPS where 1 BPS is 0.01%
uint16 constant BPS = 100_00;
/// @dev Constant value for zero.
uint256 constant ZERO = 0;
/// @dev Constant value for one.
uint256 constant ONE = 1;
// Target supply baseline is a uint48 multiplied by 10 ^ scale factor
// Limiting scale factor to 62 prevents an overflow of expected supply
// since type(uint48).max * 10**62 equals 2.81e76.
// To overflow with target supply growth rate the current timestamp would need to exceed
// the baseline timestamp by 3.5e40 years.
uint16 constant MAX_BASELINE_SCALE_FACTOR = 62;
/// @dev Pausable flag to pause buys in a TokenMaster token.
uint256 constant PAUSE_FLAG_BUYS = 1 << 0;
/// @dev Pausable flag to pause sells in a TokenMaster token.
uint256 constant PAUSE_FLAG_SELLS = 1 << 1;
/// @dev Pausable flag to pause spends in a TokenMaster token.
uint256 constant PAUSE_FLAG_SPENDS = 1 << 2;
/// @dev EIP-712 typehash for deployments that require signature validation.
bytes32 constant DEPLOYMENT_TYPEHASH = keccak256("DeploymentParameters(address tokenFactory,bytes32 tokenSalt,address tokenAddress,bool blockTransactionsFromUntrustedChannels,bool restrictPairingToLists)");
/// @dev EIP-712 typehash for advanced buy orders.
bytes32 constant BUY_TYPEHASH = keccak256("BuyTokenMasterToken(bytes32 creatorBuyIdentifier,address tokenMasterToken,address tokenMasterOracle,address baseToken,uint256 baseValue,uint256 maxPerWallet,uint256 maxTotal,uint256 expiration,address hook,address cosigner)");
/// @dev EIP-712 typehash for advanced sell orders.
bytes32 constant SELL_TYPEHASH = keccak256("SellTokenMasterToken(bytes32 creatorSellIdentifier,address tokenMasterToken,address tokenMasterOracle,address baseToken,uint256 baseValue,uint256 maxPerWallet,uint256 maxTotal,uint256 expiration,address hook,address cosigner)");
/// @dev EIP-712 typehash for spend orders.
bytes32 constant SPEND_TYPEHASH = keccak256("SpendTokenMasterToken(bytes32 creatorSpendIdentifier,address tokenMasterToken,address tokenMasterOracle,address baseToken,uint256 baseValue,uint256 maxPerWallet,uint256 maxTotal,uint256 expiration,address hook,address cosigner)");
/// @dev EIP-712 tyephash for cosignatures.
bytes32 constant COSIGNATURE_TYPEHASH = keccak256("Cosignature(uint8 v,bytes32 r,bytes32 s,uint256 expiration,address executor)");
/// @dev EIP-712 typehash for advanced PermitC transfers.
bytes32 constant PERMITTED_TRANSFER_ADDITIONAL_DATA_BUY_TYPEHASH = keccak256("PermitTransferFromWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,uint256 masterNonce,AdvancedBuyOrder advancedBuyOrder)AdvancedBuyOrder(address tokenMasterToken,uint256 tokensToBuy,uint256 pairedValueIn,bytes32 creatorBuyIdentifier,address hook,uint8 buyOrderSignatureV,bytes32 buyOrderSignatureR,bytes32 buyOrderSignatureS)");
/// @dev EIP-712 typehash for the advanced data struct in PermitC advanced transfers.
bytes32 constant PERMITTED_TRANSFER_BUY_TYPEHASH = keccak256("AdvancedBuyOrder(address tokenMasterToken,uint256 tokensToBuy,uint256 pairedValueIn,bytes32 creatorBuyIdentifier,address hook,uint8 buyOrderSignatureV,bytes32 buyOrderSignatureR,bytes32 buyOrderSignatureS)");
/// @dev Role constant for roles in a token to grant an address the ability to manage orders.
bytes32 constant ORDER_MANAGER_ROLE = bytes32(bytes4(keccak256("ORDER_MANAGER")));
/// @dev Base amount of calldata expected for a buy order when not being called by a trusted forwarder.
// | 4 | 96 | = 100 bytes
// | selector | buyOrder |
uint256 constant BASE_MSG_LENGTH_BUY_ORDER = 100;
/// @dev Base amount of calldata expected for an advanced buy order when not being called by a trusted forwarder.
// | 4 | 96 | 32 | 32 | 640 | 192 | = 996 bytes
// | selector | buyOrder | signedOrder Offset | permitTransfer Offset | signedOrder | permitTransfer |
uint256 constant BASE_MSG_LENGTH_BUY_ORDER_ADVANCED = 996;
/// @dev Base amount of calldata expected for a sell order when not being called by a trusted forwarder.
// | 4 | 96 | = 100 bytes
// | selector | sellOrder |
uint256 constant BASE_MSG_LENGTH_SELL_ORDER = 100;
/// @dev Base amount of calldata expected for an advanced sell order when not being called by a trusted forwarder.
// | 4 | 96 | 32 | 640 | = 772 bytes
// | selector | sellOrder | signedOrder Offset | signedOrder |
uint256 constant BASE_MSG_LENGTH_SELL_ORDER_ADVANCED = 772;
/// @dev Base amount of calldata expected for a spend order when not being called by a trusted forwarder.
// | 4 | 96 | 32 | 640 | = 772 bytes
// | selector | spendOrder | signedOrder Offset | signedOrder |
uint256 constant BASE_MSG_LENGTH_SPEND_ORDER = 772;
/// @dev Base amount of calldata expected for a token deployment when not being called by a trusted forwarder.
// | 4 | 32 | 96 | 672 | = 996 bytes
// | selector | deploymentParmeters Offset | signature | deploymentParameters |
uint256 constant BASE_MSG_LENGTH_DEPLOY_TOKEN = 804;
/// @dev Token setting flag to indicate a token was deployed by TokenMaster.
uint8 constant FLAG_DEPLOYED_BY_TOKENMASTER = 1 << 0;
/// @dev Token setting flag to block transactions from untrusted channels.
uint8 constant FLAG_BLOCK_TRANSACTIONS_FROM_UNTRUSTED_CHANNELS = 1 << 1;
/// @dev Token setting flag to restrict pairing of the token to only allowed addresses.
uint8 constant FLAG_RESTRICT_PAIRING_TO_LISTS = 1 << 2;
/// @dev Base role constant for the TokenMaster Admin in the Role Server.
bytes32 constant TOKENMASTER_ADMIN_BASE_ROLE = keccak256("TOKENMASTER_ADMIN_ROLE");
/// @dev Base role constant for the TokenMaster Deployment Signer in the Role Server.
bytes32 constant TOKENMASTER_SIGNER_BASE_ROLE = keccak256("TOKENMASTER_SIGNER_ROLE");
/// @dev Base role constant for the TokenMaster Fee Receiver in the Role Server.
bytes32 constant TOKENMASTER_FEE_RECEIVER_BASE_ROLE = keccak256("TOKENMASTER_FEE_RECEIVER");
/// @dev Base role constant for the TokenMaster Fee Collector in the Role Server.
bytes32 constant TOKENMASTER_FEE_COLLECTOR_BASE_ROLE = keccak256("TOKENMASTER_FEE_COLLECTOR");
/// @dev Transaction type value passed to a TokenMasterOracle when the transaction being executed is a buy.
uint256 constant ORACLE_BUY_TRANSACTION_TYPE = 0;
/// @dev Transaction type value passed to a TokenMasterOracle when the transaction being executed is a sell.
uint256 constant ORACLE_SELL_TRANSACTION_TYPE = 1;
/// @dev Transaction type value passed to a TokenMasterOracle when the transaction being executed is a spend.
uint256 constant ORACLE_SPEND_TRANSACTION_TYPE = 2;
/// @dev Constant value for the maximum address value for masking in factories.
address constant ADDRESS_MASK = address(type(uint160).max);
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
import "@limitbreak/tm-core-lib/src/utils/structs/EnumerableSet.sol";
/**
* @dev This struct defines storage for token settings.
*
* @dev **flags**: Bit packed flags for token settings defined in constants.
* @dev **spacer**: Unused
* @dev **partnerFeeRecipient**: Address of the partner fee recipient for a token.
* @dev **proposedPartnerFeeRecipient**: Address proposed by the current partner fee recipient to be the new fee recipient.
* @dev **orderSigners**: Enumerable list of addresses that are allowed order signers.
* @dev **trustedChannels**: Enumerable list of channels that are allowed for token transactions.
* @dev **allowedPairToDeployers**: Enumberable list of deployers that are allowed to deploy tokens paired with the token.
* @dev **allowedPairToTokens**: Enumberable list of tokens that are allowed to deploy as paired with the token.
*/
struct TokenSettings {
uint8 flags;
uint248 spacer;
address partnerFeeRecipient;
address proposedPartnerFeeRecipient;
EnumerableSet.AddressSet orderSigners;
EnumerableSet.AddressSet trustedChannels;
EnumerableSet.AddressSet allowedPairToDeployers;
EnumerableSet.AddressSet allowedPairToTokens;
}
/**
* @dev This struct defines parameters used by the TokenMasterRouter and factories for deploying tokens.
*
* @dev **tokenFactory**: The token factory to use to deploy a specific pool type.
* @dev **tokenSalt**: The salt value to use when deploying the token to control the deterministic address.
* @dev **tokenAddress**: The deterministic address of the token that will be deployed.
* @dev **blockTransactionsFromUntrustedChannels**: Initial setting for blocking transactions from untrusted channels.
* @dev **restrictPairingToLists**: Initial setting for restricting pairing of the new token with other tokens.
* @dev **poolParams**: The parameters that will be sent during token contract construction.
* @dev **maxInfrastructureFeeBPS**: The maximum infrastructure fee that is allowed without reverting the deployment.
*/
struct DeploymentParameters {
address tokenFactory;
bytes32 tokenSalt;
address tokenAddress;
bool blockTransactionsFromUntrustedChannels;
bool restrictPairingToLists;
PoolDeploymentParameters poolParams;
uint16 maxInfrastructureFeeBPS;
}
/**
* @dev This struct defines parameters that are sent by token factories to create a token contract.
*
* @dev **name**: The name of the token.
* @dev **symbol**: The symbol of the token.
* @dev **decimals**: The number of decimals of the token.
* @dev **initialOwner**: Address to set as the initial owner of the token.
* @dev **pairedToken**: Address of the token to pair with the new token, for native token use `address(0)`.
* @dev **initialPairedTokenToDeposit**: Amount of paired token to deposit to the new token pool.
* @dev **encodedInitializationArgs**: Bytes array of ABI encoded initialization arguments to allow new pool types
* @dev with different types of constructor arguments that are decoded during deployment.
* @dev **defaultTransferValidator**: Address of the initial transfer validator for a token.
* @dev **useRouterForPairedTransfers**: If true, the pool will default to allowing the router to transfer paired tokens
* @dev during operations that require the paired token to transfer from the pool. This is useful when pairing with
* @dev ERC20C tokens that utilize the default operator whitelist which includes the TokenMasterRouter but does not
* @dev include individual token pools.
* @dev **partnerFeeRecipient**: The address that will receive partner fee shares.
* @dev **partnerFeeBPS**: The fee rate in BPS for partner fees.
*/
struct PoolDeploymentParameters {
string name;
string symbol;
uint8 tokenDecimals;
address initialOwner;
address pairedToken;
uint256 initialPairedTokenToDeposit;
bytes encodedInitializationArgs;
address defaultTransferValidator;
bool useRouterForPairedTransfers;
address partnerFeeRecipient;
uint256 partnerFeeBPS;
}
/**
* @dev This struct defines storage for tracking advanced orders.
*
* @dev **orderDisabled**: True if the order has been disabled by the creator.
* @dev **orderTotal**: The total amount executed by all users on the order.
* @dev **orderTotalPerWallet**: The total amount per wallet executed on the order.
*/
struct OrderTracking {
bool orderDisabled;
uint256 orderTotal;
mapping (address => uint256) orderTotalPerWallet;
}
/**
* @dev This struct defines buy order base parameters.
*
* @dev **tokenMasterToken**: The address of the TokenMaster token to buy.
* @dev **tokensToBuy**: The amount of tokens to buy.
* @dev **pairedValueIn**: The amount of paired tokens to transfer in to the token contract for the purchase.
*/
struct BuyOrder {
address tokenMasterToken;
uint256 tokensToBuy;
uint256 pairedValueIn;
}
/**
* @dev This struct defines a permit transfer parameters.
*
* @dev **permitProcessor**: The address of the PermitC-compliant permit processor to use for the transfer.
* @dev **nonce**: The permit nonce to use for the permit transfer signature validation.
* @dev **permitAmount**: The amount that the permit was signed for.
* @dev **expiration**: The time, in seconds since the Unix epoch, that the permit will expire.
* @dev **signedPermit**: The permit signature bytes authorizing the transfer.
*/
struct PermitTransfer {
address permitProcessor;
uint256 nonce;
uint256 permitAmount;
uint256 expiration;
bytes signedPermit;
}
/**
* @dev This struct defines sell order base parameters.
*
* @dev **tokenMasterToken**: The address of the TokenMaster token to sell.
* @dev **tokensToSell**: The amount of tokens to sell.
* @dev **minimumOut**: The minimum output of paired tokens to be received by the seller without the transaction reverting.
*/
struct SellOrder {
address tokenMasterToken;
uint256 tokensToSell;
uint256 minimumOut;
}
/**
* @dev This struct defines spend order base parameters.
*
* @dev **tokenMasterToken**: The address of the TokenMaster token to spend.
* @dev **multiplier**: The multiplier of the signed spend order's `baseValue`, adjusted by an oracle if specified, to be spent.
* @dev **maxAmountToSpend**: The maximum amount the spender will spend on the order without the transaction reverting.
*/
struct SpendOrder {
address tokenMasterToken;
uint256 multiplier;
uint256 maxAmountToSpend;
}
/**
* @dev This struct defines advanced order execution parameters.
*
* @dev **creatorIdentifier**: A value specified by the creator to identify the order for any onchain or offchain benefits
* @dev to the order executor for executing the order.
* @dev **tokenMasterOracle**: An address for an onchain oracle that can adjust the `baseValue` for an advanced order.
* @dev **baseToken**: An address for a token to base the `baseValue` on when adjusting value through a TokenMaster Oracle.
* @dev **baseValue**: The amount of token required for the order to be executed.
* @dev If `tokenMasterOracle` is set to `address(0)`, the `baseToken` will not be utilized and the advanced order will
* @dev execute with `baseValue` being the amount of the TokenMaster token to be required for the order.
* @dev **maxPerWallet**: The maximum amount per wallet that can be executed on the order. For buy and sell advanced orders
* @dev this amount is in the TokenMaster token amount, for spend orders it is multipliers.
* @dev **maxPerWallet**: The maximum amount for all wallets that can be executed on the order. For buy and sell advanced orders
* @dev this amount is in the TokenMaster token amount, for spend orders it is multipliers.
* @dev **expiration**: The time, in seconds since the Unix epoch, that the order will expire.
* @dev **hook**: An address for an onchain hook for an order to execute after the buy, sell or spend is executed.
* @dev **signature**: The signature from an allowed order signer to authorize the order.
* @dev **cosignature**: The cosignature from the cosigner specified by the order signer.
* @dev **hookExtraData**: Extra data to send with the call to the onchain hook contract.
* @dev **oracleExtraData**: Extra data to send with the call to the oracle contract.
*/
struct SignedOrder {
bytes32 creatorIdentifier;
address tokenMasterOracle;
address baseToken;
uint256 baseValue;
uint256 maxPerWallet;
uint256 maxTotal;
uint256 expiration;
address hook;
SignatureECDSA signature;
Cosignature cosignature;
bytes hookExtraData;
bytes oracleExtraData;
}
/**
* @dev The `v`, `r`, and `s` components of an ECDSA signature. For more information
* [refer to this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7).
*/
struct SignatureECDSA {
uint256 v;
bytes32 r;
bytes32 s;
}
/**
* @dev This struct defines the cosignature for verifying an order that is a cosigned order.
*
* @dev **signer**: The address that signed the cosigned order. This must match the cosigner that is part of the order signature.
* @dev **expiration**: The time, in seconds since the Unix epoch, that the cosignature will expire.
* @dev The `v`, `r`, and `s` components of an ECDSA signature. For more information
* [refer to this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7).
*/
struct Cosignature {
address signer;
uint256 expiration;
uint256 v;
bytes32 r;
bytes32 s;
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
/// @dev Thrown when a signature `v` value exceeds 255.
error Error__InvalidSignatureV();
// TokenMaster Errors
/// @dev Thrown when a deployment call is made to a token factory and it does not originate from the TokenMasterRouter.
error TokenMasterFactory__CallerMustBeRouter();
/// @dev Thrown when deploying a token factory and the TokenMasterRouter address is not set in the factory configuration contract.
error TokenMasterFactory__RouterAddressNotSet();
/// @dev Thrown when the amount of tokens to be spent exceeds the maximum amount specified by the spender.
error TokenMasterRouter__AmountToSpendExceedsMax();
/// @dev Thrown when calldata length does not match the expected calldata length.
error TokenMasterRouter__BadCalldataLength();
/// @dev Thrown when a caller is attempting to execute a permissioned function they are not permitted for.
error TokenMasterRouter__CallerNotAllowed();
/// @dev Thrown when the block time is after the expiration of a cosignature.
error TokenMasterRouter__CosignatureExpired();
/// @dev Thrown when a cosigner is specified on an advanced order and the cosignature is invalid.
error TokenMasterRouter__CosignatureInvalid();
/// @dev Thrown when the deterministic address specified in deployment parameters does not match the address returned from a token factory.
error TokenMasterRouter__DeployedTokenAddressMismatch();
/// @dev Thrown when a new TokenMaster token deployment expects to deposit native funds to the pool and the funds fail to transfer.
error TokenMasterRouter__FailedToDepositInitialPairedFunds();
/// @dev Thrown when the paired token value fails to transfer to or from a pool.
error TokenMasterRouter__FailedToTransferPairedToken();
/// @dev Thrown when deployment signing is enabled and the supplied signature is invalid.
error TokenMasterRouter__InvalidDeploymentSignature();
/// @dev Thrown when the TokenMaster admin sets the infrastructure fee greater than 10_000 or during deployment when the current fee exceeds the max specified fee.
error TokenMasterRouter__InvalidInfrastructureFeeBPS();
/// @dev Thrown when a TokenMaster token owner attempts to accept the zero address as the new partner fee recipient.
error TokenMasterRouter__InvalidRecipient();
/// @dev Thrown when the message value for a deployment pairing with native tokens does not match the specified initial paired token to deposit.
error TokenMasterRouter__InvalidMessageValue();
/// @dev Thrown when transferring ERC20 tokens to a pool and native value is sent with the call to the router.
error TokenMasterRouter__NativeValueNotAllowedOnERC20();
/// @dev Thrown when an advanced order has expired.
error TokenMasterRouter__OrderExpired();
/// @dev Thrown when an advanced order has been disabled.
error TokenMasterRouter__OrderDisabled();
/// @dev Thrown when the amount of tokens being bought or sold on an advanced order does not meet the order's minimum amount.
error TokenMasterRouter__OrderDoesNotMeetMinimum();
/// @dev Thrown when the cumulative amount being bought, sold or spent on an advanced order exceeds the order's maximum total.
error TokenMasterRouter__OrderMaxTotalExceeded();
/// @dev Thrown when the cumulative amount being bought, sold or spent on an advanced order by a user exceeds the order's maximum for one user.
error TokenMasterRouter__OrderMaxPerWalletExceeded();
/// @dev Thrown when the supplied signature for an advanced order recovers to an address that is not authorized as an order signer.
error TokenMasterRouter__OrderSignerUnauthorized();
/// @dev Thrown when pairing with a token that has enabled pairing restrictions and the deployer or token are not specified as allowed.
error TokenMasterRouter__PairedTokenPairingRestricted();
/// @dev Thrown when attempting to use PermitC transfers with a token that is paired with the chain native token.
error TokenMasterRouter__PermitNotCompatibleWithNativeValue();
/// @dev Thrown when the permit transfer fails to execute the transfer of tokens to the pool.
error TokenMasterRouter__PermitTransferFailed();
/// @dev Thrown when deploying a token with a token factory specified that is not allowed by TokenMasterRouter.
error TokenMasterRouter__TokenFactoryNotAllowed();
/// @dev Thrown when attempting to buy, sell or spend a token that was not deployed with TokenMaster.
error TokenMasterRouter__TokenNotDeployedByTokenMaster();
/// @dev Thrown when a token has disabled transactions from untrusted channels and the call originates from a caller that is not trusted.
error TokenMasterRouter__TransactionOriginatedFromUntrustedChannel();
/// @dev Thrown when an address other than the router calls a function in a token that must be called by the router.
error TokenMasterERC20__CallerMustBeRouter();
/// @dev Thrown when attempting to withdraw an unrelated token from the pool and the address specified is the paired token.
error TokenMasterERC20__CannotWithdrawPairedToken();
/// @dev Thrown when attempting to withdraw an unrelated ERC20 token from the pool and the transfer fails.
error TokenMasterERC20__ERC20TransferFailed();
/// @dev Thrown when attempting to reset the token approval for the router to transfer paired tokens and the approval fails.
error TokenMasterERC20__FailedToSetApproval();
/// @dev Thrown when attempting to forfeit claimable emissions in an amount greater than the current claimable amount.
error TokenMasterERC20__ForfeitAmountGreaterThanClaimable();
/// @dev Thrown when the initial paired amount supplied is zero.
error TokenMasterERC20__InitialPairedDepositCannotBeZero();
/// @dev Thrown when the initial supply amount specified is zero.
error TokenMasterERC20__InitialSupplyCannotBeZero();
/// @dev Thrown when the amount of value supplied for a purchase of tokens is insufficient for the cost.
error TokenMasterERC20__InsufficientBuyInput();
/// @dev Thrown when the output value of paired tokens does not meet the sellers supplied minimum output.
error TokenMasterERC20__InsufficientSellOutput();
/// @dev Thrown when attempting to deploy or purchase an exceptionally large quantity of tokens that could destablize the token.
error TokenMasterERC20__InvalidPairedValues();
/// @dev Thrown when parameters that are being set are not within a valid range.
error TokenMasterERC20__InvalidParameters();
/// @dev Thrown when a transfer of native token value fails to execute.
error TokenMasterERC20__NativeTransferFailed();
/// @dev Thrown when adjusting the hard cap on claimable emissions and the value supplied is greater than the current cap.
error TokenMasterERC20__NewHardCapGreaterThanCurrent();
/// @dev Thrown when attempting to renounce ownership of a token contract.
error TokenMasterERC20__RenounceNotAllowed();
/// @dev Thrown when attempting to withdraw or transfer a creator share to market in an amount greater than the current creator share.
error TokenMasterERC20__WithdrawOrTransferAmountGreaterThanShare();
/// @dev Thrown when calling a function in a token contract that is not supported by the pool type.
error TokenMasterERC20__OperationNotSupportedByPool();
/// @dev Thrown when deploying a pool and an insufficient amount of value is provided for the initial supply requested.
error TokenMasterERC20__InsufficientSeedFunding();
/// @dev Thrown when multiple arrays are expected to be of equal lengths and their lengths are not equal.
error TokenMasterERC20__ArrayLengthMismatch();
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
import "./Constants.sol";
import "./DataTypes.sol";
import "./Errors.sol";
import "./interfaces/ITokenMasterRouter.sol";
import "./interfaces/ITokenMasterFactory.sol";
import "./interfaces/ITokenMasterOracle.sol";
import "./interfaces/ITokenMasterBuyHook.sol";
import "./interfaces/ITokenMasterSellHook.sol";
import "./interfaces/ITokenMasterSpendHook.sol";
import "./interfaces/ITokenMasterERC20C.sol";
import "./libraries/LibOwnership.sol";
import "@limitbreak/tm-core-lib/src/utils/security/RoleSetClient.sol";
import "@limitbreak/tm-core-lib/src/utils/structs/EnumerableSet.sol";
import "@limitbreak/tm-core-lib/src/utils/cryptography/EfficientHash.sol";
import "@limitbreak/tm-core-lib/src/utils/security/TstorishReentrancyGuard.sol";
import "@limitbreak/permit-c/openzeppelin-optimized/EIP712.sol";
import "@limitbreak/tm-core-lib/src/token/erc20/utils/SafeERC20.sol";
import "@limitbreak/tm-core-lib/src/token/erc20/IERC20.sol";
import "@limitbreak/permit-c/interfaces/IPermitC.sol";
import "@limitbreak/trusted-forwarder/TrustedForwarderERC2771Context.sol";
import "@limitbreak/tm-core-lib/src/licenses/LicenseRef-PolyForm-Strict-1.0.0.sol";
/**
* @title TokenMasterRouter
* @author Limit Break, Inc.
* @notice The TokenMasterRouter contract is designed to deploy, manage and transact with ERC20C tokens that
* are paired with another asset.
*
* @dev <h4>Features</h4>
* - Deployment of ERC20C tokens.
* - Creator controls for token pairing.
* - Buy, sell and spend tokens created through TokenMasterRouter.
* - Advanced order hooks for buying, selling and spending tokens.
* - Oracle adjustments for advanced order cost thresholds.
*
* @dev <h4>Details</h4>
* Pairing Restrictions:
* TokenMasterRouter gives ERC20C token creators the ability to enforce pairing restrictions for their
* tokens with the ability to restrict new pair deployments to originating from a creator's Trusted Forwarder
* which allows the creator to sign off on the specific deployment parameters, restrict pairing to only tokens
* deployed by the creator, restrict what deployers may deploy a new pair, and restrict to specific token
* addresses being deployed using the new token's deterministic deployment address.
*
* By default, any ERC20 token that has not restricted its pairing will be eligible to be used as a paired
* token.
*
* Tokens launched through TokenMasterRouter can be restricted to only trade through authorized
* Trusted Forwarders for order attribution, permissioning and analytics.
*
* For the security of ERC20C tokens that whitelist TokenMasterRouter, transactions executing through
* TokenMasterRouter must be from pairs that were deployed by TokenMasterRouter and the factory used for
* deployment must be an allowed factory set by the `TOKENMASTER_ADMIN_ROLE`.
*
* Buy / Sell / Spend Tokens:
* Tokens deployed through TokenMasterRouter are special ERC20C token contracts that are paired with
* another token. Allowed factories may deploy pools with different mechanics - for example, one factory's
* tokens may have a fluctuating price based on buying and selling while another factory is fully stable.
* Buys will exchange a quantity of the paired token for the token being purchased, sells will exchange
* the token for the paired token, and spends remove tokens from the spender's account with an event
* emission for offchain actions by the creator and/or an onchain hook being executed.
*
* Advanced orders must be signed by an account that is authorized by the creator as an order signer.
* Advanced buys and sells execute an onchain hook, spends may execute an onchain hook but also emit an
* event the creator can utilize for offchain purposes. Advanced transactions include a `hookExtraData`
* parameter to provide additional data to the hook contract that is called.
*
* Oracles:
* Advanced orders may include an oracle contract address that adjusts the token cost of an order
* based on any factor that is relevant to the transaction being executed. The `oracleExtraData`
* parameter can be used provide additional data to the oracle contract that is called.
*/
contract TokenMasterRouter is ITokenMasterRouter, RoleSetClient, TrustedForwarderERC2771Context, EIP712, TstorishReentrancyGuard {
using EnumerableSet for EnumerableSet.AddressSet;
/// @dev Current infrastructure fee that will be included in newly deployed tokens.
uint16 public infrastructureFeeBPS;
/// @dev Mapping of allowed token factories, set by the TokenMaster Admin
mapping (address => bool) public allowedTokenFactory;
/// @dev Mapping of token settings set by the token owner or admin.
mapping (address => TokenSettings) private tokenSettings;
/// @dev Mapping of advanced order data to track amounts and status.
mapping (bytes32 => OrderTracking) public orderTracking;
/// @dev Role for the TokenMaster Admin in the Role Server.
bytes32 private immutable TOKENMASTER_ADMIN_ROLE;
/// @dev Role for the TokenMaster Deployment Signer in the Role Server.
bytes32 private immutable TOKENMASTER_SIGNER_ROLE;
/// @dev Role for the TokenMaster Fee Receiver in the Role Server.
bytes32 private immutable TOKENMASTER_FEE_RECEIVER_ROLE;
/// @dev Role for the TokenMaster Fee Collector in the Role Server.
bytes32 private immutable TOKENMASTER_FEE_COLLECTOR_ROLE;
modifier onlyAdminAuthority() {
if (msg.sender != _getRoleHolder(TOKENMASTER_ADMIN_ROLE)) {
revert TokenMasterRouter__CallerNotAllowed();
}
_;
}
constructor(
address roleServer,
bytes32 roleSet,
address trustedForwarderFactory
)
RoleSetClient(roleServer, roleSet)
TrustedForwarderERC2771Context(trustedForwarderFactory)
EIP712("TokenMasterRouter", "1") {
TOKENMASTER_ADMIN_ROLE = _hashRoleSetRole(roleSet, TOKENMASTER_ADMIN_BASE_ROLE);
TOKENMASTER_SIGNER_ROLE = _hashRoleSetRole(roleSet, TOKENMASTER_SIGNER_BASE_ROLE);
TOKENMASTER_FEE_RECEIVER_ROLE = _hashRoleSetRole(roleSet, TOKENMASTER_FEE_RECEIVER_BASE_ROLE);
TOKENMASTER_FEE_COLLECTOR_ROLE = _hashRoleSetRole(roleSet, TOKENMASTER_FEE_COLLECTOR_BASE_ROLE);
}
/**
* @dev Initializes role configuration during TokenMasterRouter deployment.
*/
function _setupRoles(bytes32 roleSet) internal override {
_setupRole(_hashRoleSetRole(roleSet, TOKENMASTER_ADMIN_BASE_ROLE), 0);
_setupRole(_hashRoleSetRole(roleSet, TOKENMASTER_SIGNER_BASE_ROLE), 0);
_setupRole(_hashRoleSetRole(roleSet, TOKENMASTER_FEE_RECEIVER_BASE_ROLE), 1 hours);
_setupRole(_hashRoleSetRole(roleSet, TOKENMASTER_FEE_COLLECTOR_BASE_ROLE), 24 hours);
}
/*************************************************************************/
/* BUY / SELL / SPEND FUNCTIONS */
/*************************************************************************/
/**
* @notice Executes a buy order for a token deployed through TokenMaster.
*
* @dev Throws when reentering the TokenMasterRouter contract before a prior call ends.
* @dev Throws when the calldata length does not match the expected length.
* @dev Throws when the token being purchased was not deployed through TokenMaster.
* @dev Throws when the token is configured to block transactions from untrusted
* @dev channels and the caller is not a trusted channel.
* @dev Throws when native value is sent for a token that is paired with an ERC20.
* @dev Throws when an ERC20 paired token fails to transfer to the token contract.
* @dev Throws when a refund is required by the router and the refund transfer fails.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Paired value is transferred from the buyer to the token contract.
* @dev 2. Tokens are minted to the buyer.
* @dev 3. A `BuyOrderFilled` event has been emitted.
*
* @param buyOrder Basic buy order details.
*/
function buyTokens(BuyOrder calldata buyOrder) external payable nonReentrant {
address executor = _getExecutor(BASE_MSG_LENGTH_BUY_ORDER);
(
ITokenMasterERC20C tokenMasterToken,
) = _validateTokenSettingsForTransaction(buyOrder.tokenMasterToken);
address pairedToken = tokenMasterToken.PAIRED_TOKEN();
uint256 pairedValueIn = _transferPairedValueToPool(pairedToken, address(tokenMasterToken), executor, buyOrder.pairedValueIn);
_executeBuy(tokenMasterToken, executor, pairedValueIn, buyOrder.tokensToBuy, pairedToken);
}
/**
* @notice Executes an advanced buy order for a token deployed through TokenMaster.
*
* @dev Throws when reentering the TokenMasterRouter contract before a prior call ends.
* @dev Throws when the calldata length does not match the expected length.
* @dev Throws when the token being purchased was not deployed through TokenMaster.
* @dev Throws when the token is configured to block transactions from untrusted
* @dev channels and the caller is not a trusted channel.
* @dev Throws when native value is sent for a token that is paired with an ERC20.
* @dev Throws when an ERC20 paired token fails to transfer to the token contract.
* @dev Throws when a refund is required by the router and the refund transfer fails.
* @dev If the advanced buy includes an advanced order -
* @dev Throws when the order has expired.
* @dev Throws when the order is not signed by an authorized signer.
* @dev Throws when a cosigner is specified and the cosignature is invalid.
* @dev Throws when the buy amount does not meet the order minimum.
* @dev Throws when the order has been disabled.
* @dev Throws when the order has a maximum total and the buy will exceed it.
* @dev Throws when the order has a maximum per wallet and the buy will exceed it.
* @dev Throws when the hook call reverts.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Paired value is transferred from the buyer to the token contract.
* @dev 2. Tokens are minted to the buyer.
* @dev 3. A `BuyOrderFilled` event has been emitted.
* @dev 4. Buy hook is called if the advanced buy includes an advanced order.
*
* @param buyOrder Basic buy order details.
* @param signedOrder Advanced order details and signatures.
* @param permitTransfer Permit transfer details if executing a permit transfer.
*/
function buyTokensAdvanced(
BuyOrder calldata buyOrder,
SignedOrder calldata signedOrder,
PermitTransfer calldata permitTransfer
) external payable nonReentrant {
address executor = _getExecutor(
_addAdjustedBytesLength(
_addAdjustedBytesLength(
_addAdjustedBytesLength(
BASE_MSG_LENGTH_BUY_ORDER_ADVANCED,
permitTransfer.signedPermit.length
),
signedOrder.oracleExtraData.length
),
signedOrder.hookExtraData.length
)
);
(
ITokenMasterERC20C tokenMasterToken,
TokenSettings storage settings
) = _validateTokenSettingsForTransaction(buyOrder.tokenMasterToken);
address pairedToken = tokenMasterToken.PAIRED_TOKEN();
uint256 pairedValueIn;
if (permitTransfer.permitProcessor == address(0)) {
pairedValueIn = _transferPairedValueToPool(pairedToken, address(tokenMasterToken), executor, buyOrder.pairedValueIn);
} else {
pairedValueIn = _permitTransferTokensToBuy(
executor,
pairedToken,
address(tokenMasterToken),
buyOrder,
signedOrder,
permitTransfer
);
}
(uint256 tokensToBuy, bool executeHook) = _validateBuyParameters(
address(tokenMasterToken),
settings.orderSigners,
executor,
buyOrder.tokensToBuy,
signedOrder
);
_executeBuy(tokenMasterToken, executor, pairedValueIn, tokensToBuy, pairedToken);
if (executeHook) {
ITokenMasterBuyHook(signedOrder.hook).tokenMasterBuyHook(
address(tokenMasterToken),
executor,
signedOrder.creatorIdentifier,
tokensToBuy,
signedOrder.hookExtraData
);
}
}
/**
* @notice Executes a sell order for a token deployed through TokenMaster.
*
* @dev Throws when reentering the TokenMasterRouter contract before a prior call ends.
* @dev Throws when the calldata length does not match the expected length.
* @dev Throws when the token being sold was not deployed through TokenMaster.
* @dev Throws when the token is configured to block transactions from untrusted
* @dev channels and the caller is not a trusted channel.
* @dev Throws when a transfer is required by the router and the transfer fails.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Sold tokens are burned by the token contract.
* @dev 2. Paired tokens are transferred to the seller.
* @dev 3. A `SellOrderFilled` event has been emitted.
*
* @param sellOrder Basic sell order details.
*/
function sellTokens(SellOrder calldata sellOrder) external nonReentrant {
address executor = _getExecutor(BASE_MSG_LENGTH_SELL_ORDER);
(
ITokenMasterERC20C tokenMasterToken,
) = _validateTokenSettingsForTransaction(sellOrder.tokenMasterToken);
_executeSell(tokenMasterToken, executor, sellOrder);
}
/**
* @notice Executes an advanced sell order for a token deployed through TokenMaster.
*
* @dev Throws when reentering the TokenMasterRouter contract before a prior call ends.
* @dev Throws when the calldata length does not match the expected length.
* @dev Throws when the token being sold was not deployed through TokenMaster.
* @dev Throws when the token is configured to block transactions from untrusted
* @dev channels and the caller is not a trusted channel.
* @dev Throws when a transfer is required by the router and the transfer fails.
* @dev Throws when the order has expired.
* @dev Throws when the order is not signed by an authorized signer.
* @dev Throws when a cosigner is specified and the cosignature is invalid.
* @dev Throws when the sell amount does not meet the order minimum.
* @dev Throws when the order has been disabled.
* @dev Throws when the order has a maximum total and the sell will exceed it.
* @dev Throws when the order has a maximum per wallet and the sell will exceed it.
* @dev Throws when the hook call reverts.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Sold tokens are burned by the token contract.
* @dev 2. Paired tokens are transferred to the seller.
* @dev 3. A `SellOrderFilled` event has been emitted.
* @dev 4. Sell hook is called.
*
* @param sellOrder Basic sell order details.
* @param signedOrder Advanced order details and signatures.
*/
function sellTokensAdvanced(SellOrder calldata sellOrder, SignedOrder calldata signedOrder) external nonReentrant {
address executor = _getExecutor(
_addAdjustedBytesLength(
_addAdjustedBytesLength(
BASE_MSG_LENGTH_SELL_ORDER_ADVANCED,
signedOrder.oracleExtraData.length
),
signedOrder.hookExtraData.length
)
);
(
ITokenMasterERC20C tokenMasterToken,
TokenSettings storage settings
) = _validateTokenSettingsForTransaction(sellOrder.tokenMasterToken);
_validateSellParameters(
address(tokenMasterToken),
settings.orderSigners,
executor,
sellOrder.tokensToSell,
signedOrder
);
_executeSell(tokenMasterToken, executor, sellOrder);
ITokenMasterSellHook(signedOrder.hook).tokenMasterSellHook(
address(tokenMasterToken),
executor,
signedOrder.creatorIdentifier,
sellOrder.tokensToSell,
signedOrder.hookExtraData
);
}
/**
* @notice Executes a spend order for a token deployed through TokenMaster.
*
* @dev Tokens spent are calculated by the base value on the signed order, adjusted
* @dev by an oracle if specified by the creator, times the multiplier on the spend
* @dev order.
*
* @dev Throws when reentering the TokenMasterRouter contract before a prior call ends.
* @dev Throws when the calldata length does not match the expected length.
* @dev Throws when the token being spent was not deployed through TokenMaster.
* @dev Throws when the token is configured to block transactions from untrusted
* @dev channels and the caller is not a trusted channel.
* @dev Throws when the order has expired.
* @dev Throws when the order is not signed by an authorized signer.
* @dev Throws when a cosigner is specified and the cosignature is invalid.
* @dev Throws when the amount to spend exceeds the user specified maximum.
* @dev Throws when the order has been disabled.
* @dev Throws when the order has a maximum total and the sell will exceed it.
* @dev Throws when the order has a maximum per wallet and the sell will exceed it.
* @dev Throws when the hook call reverts.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Spent tokens are burned by the token contract.
* @dev 2. A `SpendOrderFilled` event has been emitted.
* @dev 3. Spend hook is called, if spend order includes a hook.
*
* @param spendOrder Basic spend details.
* @param signedOrder Advanced spend details and signature.
*/
function spendTokens(
SpendOrder calldata spendOrder,
SignedOrder calldata signedOrder
) external nonReentrant {
address executor = _getExecutor(
_addAdjustedBytesLength(
_addAdjustedBytesLength(
BASE_MSG_LENGTH_SPEND_ORDER,
signedOrder.oracleExtraData.length
),
signedOrder.hookExtraData.length
)
);
(
ITokenMasterERC20C tokenMasterToken,
TokenSettings storage settings
) = _validateTokenSettingsForTransaction(spendOrder.tokenMasterToken);
(uint256 amountToSpend, uint256 multiplier) = _validateSpendParameters(
settings.orderSigners,
executor,
spendOrder,
signedOrder
);
if (amountToSpend > spendOrder.maxAmountToSpend) {
revert TokenMasterRouter__AmountToSpendExceedsMax();
}
tokenMasterToken.spendTokens(executor, amountToSpend);
if (signedOrder.hook != address(0)) {
ITokenMasterSpendHook(signedOrder.hook).tokenMasterSpendHook(
address(tokenMasterToken),
executor,
signedOrder.creatorIdentifier,
multiplier,
signedOrder.hookExtraData
);
}
emit SpendOrderFilled(address(tokenMasterToken), signedOrder.creatorIdentifier, executor, amountToSpend, multiplier);
}
/*************************************************************************/
/* CREATOR FUNCTIONS */
/*************************************************************************/
/**
* @notice Deploys a TokenMaster token using the parameters specified by the deployer.
*
* @dev Token deployments are deterministic, the provided address in deployment
* @dev parameters *MUST* match the actual deployment address from the factory.
*
* @dev Partner fee recipient may only be changed after deployment by the
* @dev the current partner proposing and the token owner accepting the
* @dev proposed recipient. If no partner address is specified or the recipient
* @dev is a contract that cannot call the proposal function then the address
* @dev will not be changeable in the future.
*
* @dev Throws when the calldata length does not match the expected length.
* @dev Throws when the paired token is configured to block transactions from
* @dev untrusted channels and the caller is not a trusted channel.
* @dev Throws when the paired token is configured to limit pairings to lists
* @dev and the deployer or deterministic address of the token being deployed
* @dev are not on the list.
* @dev Throws when the specified token factory is not allowed by TokenMaster.
* @dev Throws when a signing authority is configured and the provided
* @dev signature is not valid.
* @dev Throws when the initial paired value fails to transfer to the token
* @dev contract address.
* @dev Throws when deploying a native-backed token and the supplied value
* @dev is not equal to the initial pairing amount.
* @dev Throws when the specified maximum infrastructure fee is less than
* @dev the current infrastructure fee setting.
* @dev Throws when the actual deployed address does not match the supplied
* @dev token address value.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Initial paired value is transferred to the deployed token.
* @dev 2. The token contract is created.
* @dev 3. The token is marked as deployed by TokenMaster and initial settings stored.
* @dev 4. A `TokenMasterTokenDeployed` event has been emitted.
*
* @param deploymentParameters The parameters for the token being deployed.
* @param signature If a signing authority is set, the signature from
* the signing authority.
* If no signing authority is set, this may be any value.
*/
function deployToken(
DeploymentParameters calldata deploymentParameters,
SignatureECDSA calldata signature
) external payable {
address deployer = _getExecutor(
_addAdjustedBytesLength(
_addAdjustedBytesLength(
_addAdjustedBytesLength(
BASE_MSG_LENGTH_DEPLOY_TOKEN,
bytes(deploymentParameters.poolParams.name).length
),
bytes(deploymentParameters.poolParams.symbol).length
),
deploymentParameters.poolParams.encodedInitializationArgs.length
)
);
_validateTokenSettingsForDeployment(
deployer,
deploymentParameters.tokenAddress,
deploymentParameters.poolParams.pairedToken
);
if (!allowedTokenFactory[deploymentParameters.tokenFactory]) {
revert TokenMasterRouter__TokenFactoryNotAllowed();
}
address signerAuthority = _getRoleHolder(TOKENMASTER_SIGNER_ROLE);
if (signerAuthority != address(0)) {
_validateDeploymentSignature(deploymentParameters, signature, signerAuthority);
}
uint256 pairedTokenIn = _transferPairedValueToPool(
deploymentParameters.poolParams.pairedToken,
deploymentParameters.tokenAddress,
deployer,
deploymentParameters.poolParams.initialPairedTokenToDeposit
);
if (deploymentParameters.poolParams.pairedToken == address(0)) {
if (msg.value != deploymentParameters.poolParams.initialPairedTokenToDeposit) {
revert TokenMasterRouter__InvalidMessageValue();
}
(bool success,) = deploymentParameters.tokenAddress.call{value: msg.value}("");
if (!success) {
revert TokenMasterRouter__FailedToDepositInitialPairedFunds();
}
}
uint16 _infrastructureFeeBPS = infrastructureFeeBPS;
if (_infrastructureFeeBPS > deploymentParameters.maxInfrastructureFeeBPS) {
revert TokenMasterRouter__InvalidInfrastructureFeeBPS();
}
address tokenMasterToken = ITokenMasterFactory(deploymentParameters.tokenFactory).deployToken(
deploymentParameters.tokenSalt,
deploymentParameters.poolParams,
pairedTokenIn,
_infrastructureFeeBPS
);
if (tokenMasterToken == address(0) || tokenMasterToken != deploymentParameters.tokenAddress) {
revert TokenMasterRouter__DeployedTokenAddressMismatch();
}
uint8 flags = FLAG_DEPLOYED_BY_TOKENMASTER;
if (deploymentParameters.blockTransactionsFromUntrustedChannels) {
flags |= FLAG_BLOCK_TRANSACTIONS_FROM_UNTRUSTED_CHANNELS;
}
if (deploymentParameters.restrictPairingToLists) {
flags |= FLAG_RESTRICT_PAIRING_TO_LISTS;
}
tokenSettings[tokenMasterToken].flags = flags;
tokenSettings[tokenMasterToken].partnerFeeRecipient = deploymentParameters.poolParams.partnerFeeRecipient;
emit TokenMasterTokenDeployed(tokenMasterToken, deploymentParameters.poolParams.pairedToken, deploymentParameters.tokenFactory);
}
/**
* @notice Updates settings for a token for any transaction executed on TokenMasterRouter.
*
* @dev Settings may be set for tokens that were not deployed through TokenMaster
* @dev so that a token owner may control pairings for their token.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Token settings have been updated.
* @dev 2. A `TokenSettingsUpdated` event has been emitted.
*
* @param tokenAddress The address of the token to set settings for.
* @param blockTransactionsFromUntrustedChannels If true, requires transactions to be executed through
* a trusted channel.
* @param restrictPairingToLists If true, tokens can only be deployed if the deployer or
* token address are on an approved list.
*/
function updateTokenSettings(
address tokenAddress,
bool blockTransactionsFromUntrustedChannels,
bool restrictPairingToLists
) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(tokenAddress);
TokenSettings storage settings = tokenSettings[tokenAddress];
settings.flags =
_setFlag(
_setFlag(
settings.flags,
FLAG_BLOCK_TRANSACTIONS_FROM_UNTRUSTED_CHANNELS,
blockTransactionsFromUntrustedChannels
),
FLAG_RESTRICT_PAIRING_TO_LISTS,
restrictPairingToLists
);
emit TokenSettingsUpdated(tokenAddress, blockTransactionsFromUntrustedChannels, restrictPairingToLists);
}
/**
* @notice Sets or removes an address as an allowed signer for advanced orders for a token.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The signer list for the token has been updated.
* @dev 2. A `OrderSignerUpdated` event has been emitted.
*
* @param tokenMasterToken The address of the token to update the signer for.
* @param signer The address of the account to set or remove as a signer.
* @param allowed If true, adds the account as a signer. If false, removes the account.
*/
function setOrderSigner(address tokenMasterToken, address signer, bool allowed) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(tokenMasterToken);
if (allowed) {
if (tokenSettings[tokenMasterToken].orderSigners.add(signer)) {
emit OrderSignerUpdated(tokenMasterToken, signer, allowed);
}
} else {
if (tokenSettings[tokenMasterToken].orderSigners.remove(signer)) {
emit OrderSignerUpdated(tokenMasterToken, signer, allowed);
}
}
}
/**
* @notice Sets or removes an address as a trusted channel for a token.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The trusted channel list for the token has been updated.
* @dev 2. A `TrustedChannelUpdated` event has been emitted.
*
* @param tokenAddress The address of the token to update the trusted channels for.
* @param channel The address of the channel to set or remove as trusted.
* @param allowed If true, adds the channel as a trusted. If false, removes the channel.
*/
function setTokenAllowedTrustedChannel(address tokenAddress, address channel, bool allowed) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(tokenAddress);
if (allowed) {
if (tokenSettings[tokenAddress].trustedChannels.add(channel)) {
emit TrustedChannelUpdated(tokenAddress, channel, allowed);
}
} else {
if (tokenSettings[tokenAddress].trustedChannels.remove(channel)) {
emit TrustedChannelUpdated(tokenAddress, channel, allowed);
}
}
}
/**
* @notice Sets or removes an address as an allowed pair deployer.
*
* @dev Allowed pair deployers are allowed to deploy a token on TokenMaster
* @dev that pairs to the specified token when the token has pairing restrictions
* @dev enabled.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The allowed pair to deployer list for the token has been updated.
* @dev 2. A `AllowedPairToDeployersUpdated` event has been emitted.
*
* @param tokenAddress The address of the token to update the allowed pair deployers for.
* @param deployer The address of the deployer to set or remove as allowed.
* @param allowed If true, adds the deployer as a allowed. If false, removes the deployer.
*/
function setTokenAllowedPairToDeployer(address tokenAddress, address deployer, bool allowed) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(tokenAddress);
if (allowed) {
if (tokenSettings[tokenAddress].allowedPairToDeployers.add(deployer)) {
emit AllowedPairToDeployersUpdated(tokenAddress, deployer, allowed);
}
} else {
if (tokenSettings[tokenAddress].allowedPairToDeployers.remove(deployer)) {
emit AllowedPairToDeployersUpdated(tokenAddress, deployer, allowed);
}
}
}
/**
* @notice Sets or removes an address as an allowed pair token.
*
* @dev Allowed pair tokens are specific token addresses that are allowed to be
* @dev with the specified token as the paired token. Token deployments in
* @dev TokenMaster are deterministic so all of the settings may be validated
* @dev and the address precomputed to add as an allowed pair to token before
* @dev the token is deployed.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The allowed pair to token list for the token has been updated.
* @dev 2. A `AllowedPairToTokensUpdated` event has been emitted.
*
* @param tokenAddress The address of the token to update the allowed pair tokens for.
* @param tokenAllowedToPair The address of the token to set or remove as allowed.
* @param allowed If true, adds the token as a allowed. If false, removes the token.
*/
function setTokenAllowedPairToToken(address tokenAddress, address tokenAllowedToPair, bool allowed) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(tokenAddress);
if (allowed) {
if (tokenSettings[tokenAddress].allowedPairToTokens.add(tokenAllowedToPair)) {
emit AllowedPairToTokensUpdated(tokenAddress, tokenAllowedToPair, allowed);
}
} else {
if (tokenSettings[tokenAddress].allowedPairToTokens.remove(tokenAllowedToPair)) {
emit AllowedPairToTokensUpdated(tokenAddress, tokenAllowedToPair, allowed);
}
}
}
/**
* @notice Disables or re-enables a specific advanced buy order.
*
* @dev Throws when the caller is not the token, owner, an admin, or
* @dev order manager role holder for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The specified order has been disabled or re-enabled.
* @dev 2. A `BuyOrderDisabled` event has been emitted.
*
* @param tokenMasterToken The address of the token to disable or re-enable the advanced order for.
* @param signedOrder The advanced order details.
* @param disabled If true, the order is disabled. If false, the order is re-enabled.
*/
function disableBuyOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdminOrRole(
tokenMasterToken,
ORDER_MANAGER_ROLE
);
bytes32 buyOrderHash = _hashSignedOrder(BUY_TYPEHASH, tokenMasterToken, signedOrder);
orderTracking[buyOrderHash].orderDisabled = disabled;
emit BuyOrderDisabled(tokenMasterToken, signedOrder.creatorIdentifier, disabled);
}
/**
* @notice Disables or re-enables a specific advanced sell order.
*
* @dev Throws when the caller is not the token, owner, an admin, or
* @dev order manager role holder for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The specified order has been disabled or re-enabled.
* @dev 2. A `SellOrderDisabled` event has been emitted.
*
* @param tokenMasterToken The address of the token to disable or re-enable the advanced order for.
* @param signedOrder The advanced order details.
* @param disabled If true, the order is disabled. If false, the order is re-enabled.
*/
function disableSellOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdminOrRole(
tokenMasterToken,
ORDER_MANAGER_ROLE
);
bytes32 sellOrderHash = _hashSignedOrder(SELL_TYPEHASH, tokenMasterToken, signedOrder);
orderTracking[sellOrderHash].orderDisabled = disabled;
emit SellOrderDisabled(tokenMasterToken, signedOrder.creatorIdentifier, disabled);
}
/**
* @notice Disables or re-enables a specific spend order.
*
* @dev Throws when the caller is not the token, owner, an admin, or
* @dev order manager role holder for the token.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The specified order has been disabled or re-enabled.
* @dev 2. A `SpendOrderDisabled` event has been emitted.
*
* @param tokenMasterToken The address of the token to disable or re-enable the advanced order for.
* @param signedOrder The advanced order details.
* @param disabled If true, the order is disabled. If false, the order is re-enabled.
*/
function disableSpendOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdminOrRole(
tokenMasterToken,
ORDER_MANAGER_ROLE
);
bytes32 spendOrderHash = _hashSignedOrder(SPEND_TYPEHASH, tokenMasterToken, signedOrder);
orderTracking[spendOrderHash].orderDisabled = disabled;
emit SpendOrderDisabled(tokenMasterToken, signedOrder.creatorIdentifier, disabled);
}
/**
* @notice Withdraws an amount of creator earnings from a TokenMaster token to a specified address.
*
* @dev Partner earnings and infrastructure fees are withdrawn at the same time.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
* @dev Throws when paired tokens are to be transferred by the router and a transfer fails.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The amount of creator share has been withdrawn.
* @dev 2. The partner share has been withdrawn.
* @dev 3. The infrastructure fees have been withdrawn.
*
* @param tokenMasterToken The address of the token to withdraw creator share from.
* @param withdrawTo The address to withdraw creator share to.
* @param withdrawAmount The amount of creator share to withdraw.
*/
function withdrawCreatorShare(ITokenMasterERC20C tokenMasterToken, address withdrawTo, uint256 withdrawAmount) external nonReentrant {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(address(tokenMasterToken));
address partnerFeeRecipient = tokenSettings[address(tokenMasterToken)].partnerFeeRecipient;
address infrastructureFeeRecipient = _getRoleHolder(TOKENMASTER_FEE_RECEIVER_ROLE);
(
address pairedToken,
uint256 transferByRouterAmountCreator,
uint256 transferByRouterAmountInfrastructure,
uint256 transferByRouterAmountPartner
) = tokenMasterToken.withdrawCreatorShare(withdrawTo, withdrawAmount, infrastructureFeeRecipient, partnerFeeRecipient);
if (transferByRouterAmountCreator > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
withdrawTo,
transferByRouterAmountCreator
);
}
if (transferByRouterAmountInfrastructure > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
infrastructureFeeRecipient,
transferByRouterAmountInfrastructure
);
}
if (transferByRouterAmountPartner > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
partnerFeeRecipient,
transferByRouterAmountPartner
);
}
}
/**
* @notice Transfers an amount of creator earnings for a TokenMaster token to the token's market share.
*
* @dev Partner earnings and infrastructure fees are withdrawn during this transaction.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
* @dev Throws when paired tokens are to be transferred by the router and a transfer fails.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The amount of creator share has been transfered to market share.
* @dev 2. The partner share has been withdrawn.
* @dev 3. The infrastructure fees have been withdrawn.
*
* @param tokenMasterToken The address of the token to transfer creator share to market share on.
* @param transferAmount The amount of creator share to transfer to the market share.
*/
function transferCreatorShareToMarket(ITokenMasterERC20C tokenMasterToken, uint256 transferAmount) external nonReentrant {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(address(tokenMasterToken));
address partnerFeeRecipient = tokenSettings[address(tokenMasterToken)].partnerFeeRecipient;
address infrastructureFeeRecipient = _getRoleHolder(TOKENMASTER_FEE_RECEIVER_ROLE);
(
address pairedToken,
uint256 transferByRouterAmountInfrastructure,
uint256 transferByRouterAmountPartner
) = tokenMasterToken.transferCreatorShareToMarket(transferAmount, infrastructureFeeRecipient, partnerFeeRecipient);
if (transferByRouterAmountInfrastructure > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
infrastructureFeeRecipient,
transferByRouterAmountInfrastructure
);
}
if (transferByRouterAmountPartner > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
partnerFeeRecipient,
transferByRouterAmountPartner
);
}
}
/**
* @notice Accepts a proposed partner fee receiver update from the partner fee receiver.
*
* @dev Throws when the caller is not the token, owner or an admin for the token.
* @dev Throws when the proposed partner fee recipient address is the zero address.
* @dev Throws when the proposed partner fee recipient address does not match the expected address.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The partner fee recipient has been updated.
* @dev 2. A `PartnerFeeRecipientUpdated` event has been emitted.
*
* @param tokenMasterToken The address of the token to accept the proposed fee receiver on.
* @param expectedPartnerFeeRecipient The address the caller expects to be the proposed address.
*/
function acceptProposedPartnerFeeReceiver(address tokenMasterToken, address expectedPartnerFeeRecipient) external {
LibOwnership.requireCallerIsTokenOrContractOwnerOrAdmin(tokenMasterToken);
TokenSettings storage settings = tokenSettings[tokenMasterToken];
address proposedPartnerFeeRecipient = settings.proposedPartnerFeeRecipient;
if (
proposedPartnerFeeRecipient == address(0)
|| proposedPartnerFeeRecipient != expectedPartnerFeeRecipient
) {
revert TokenMasterRouter__InvalidRecipient();
}
settings.partnerFeeRecipient = proposedPartnerFeeRecipient;
settings.proposedPartnerFeeRecipient = address(0);
emit PartnerFeeRecipientUpdated(tokenMasterToken, proposedPartnerFeeRecipient);
}
/*************************************************************************/
/* PARTNER FUNCTIONS */
/*************************************************************************/
/**
* @notice Proposes a new partner fee receiver for a token.
*
* @dev Throws when the caller is not the current partner fee receiver.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The proposed partner fee recipient has been updated.
* @dev 2. A `PartnerFeeRecipientProposed` event has been emitted.
*
* @param tokenMasterToken The address of the token to propose the fee receiver on.
* @param proposedPartnerFeeRecipient The address to propose.
*/
function partnerProposeFeeReceiver(
address tokenMasterToken,
address proposedPartnerFeeRecipient
) external {
TokenSettings storage settings = tokenSettings[tokenMasterToken];
address partnerFeeRecipient = settings.partnerFeeRecipient;
if (msg.sender != partnerFeeRecipient) {
revert TokenMasterRouter__CallerNotAllowed();
}
settings.proposedPartnerFeeRecipient = proposedPartnerFeeRecipient;
emit PartnerFeeRecipientProposed(tokenMasterToken, proposedPartnerFeeRecipient);
}
/*************************************************************************/
/* FEE MGMT FUNCTIONS */
/*************************************************************************/
/**
* @notice Withdraws partner shares and infrastructure fees from many TokenMaster tokens.
*
* @dev Throws when the caller is not the TokenMaster fee collector or partner fee recipient.
* @dev Throws when a transfer is to be made by the router and the transfer fails.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. Partner fees for each token have been transferred to their receiver.
* @dev 2. Infrastructure fees for each token have been transferred to the fee receiver.
*
* @param tokenMasterTokens Array of token addresses to withdraw fees from.
*/
function withdrawFees(ITokenMasterERC20C[] calldata tokenMasterTokens) external nonReentrant {
bool authorized = msg.sender == _getRoleHolder(TOKENMASTER_FEE_COLLECTOR_ROLE);
address infrastructureFeeRecipient = _getRoleHolder(TOKENMASTER_FEE_RECEIVER_ROLE);
for (uint256 i; i < tokenMasterTokens.length; ++i) {
ITokenMasterERC20C tokenMasterToken = tokenMasterTokens[i];
TokenSettings storage settings = tokenSettings[address(tokenMasterToken)];
address partnerFeeRecipient = settings.partnerFeeRecipient;
if (!authorized) {
if (msg.sender != partnerFeeRecipient) {
revert TokenMasterRouter__CallerNotAllowed();
}
}
(
address pairedToken,
uint256 transferByRouterAmountInfrastructure,
uint256 transferByRouterAmountPartner
) = tokenMasterToken.withdrawFees(infrastructureFeeRecipient, partnerFeeRecipient);
if (transferByRouterAmountInfrastructure > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
infrastructureFeeRecipient,
transferByRouterAmountInfrastructure
);
}
if (transferByRouterAmountPartner > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
partnerFeeRecipient,
transferByRouterAmountPartner
);
}
}
}
/*************************************************************************/
/* ADMIN FUNCTIONS */
/*************************************************************************/
/**
* @notice Sets or removes an allowed token factory for token deployments.
*
* @dev Throws when the caller is not the TokenMaster admin.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The token factory allowed state has been updated.
* @dev 2. A `AllowedTokenFactoryUpdated` event has been emitted.
*
* @param tokenFactory Address of the token factory to update.
* @param allowed If true, the factory will be allowed to deploy tokens.
*/
function setAllowedTokenFactory(
address tokenFactory,
bool allowed
) external onlyAdminAuthority {
allowedTokenFactory[tokenFactory] = allowed;
emit AllowedTokenFactoryUpdated(tokenFactory, allowed);
}
/**
* @notice Sets the infrastructure fee for new TokenMaster deployments.
*
* @dev Throws when the caller is not the TokenMaster admin.
*
* @dev <h4>Postconditions:</h4>
* @dev 1. The infrastructure fee has been updated for new deployments.
* @dev 2. A `InfrastructureFeeUpdated` event has been emitted.
*
* @param _infrastructureFeeBPS The fee rate, in BPS, to apply to new deployments.
*/
function setInfrastructureFee(uint16 _infrastructureFeeBPS) external onlyAdminAuthority {
if (_infrastructureFeeBPS > BPS) {
revert TokenMasterRouter__InvalidInfrastructureFeeBPS();
}
infrastructureFeeBPS = _infrastructureFeeBPS;
emit InfrastructureFeeUpdated(_infrastructureFeeBPS);
}
/*************************************************************************/
/* VIEW FUNCTIONS */
/*************************************************************************/
/**
* @notice Gets the current status of a buy order and checks signature, cosignature validity.
*
* @dev Throws when the a supplied signature has an invalid `v` value.
* @dev Throws when a signed order has a cosignature and the cosignature is expired.
*
* @param tokenMasterToken The address of the token the buy order is for.
* @param signedOrder The signed buy order to get tracking data for.
* @param buyer The address of the buyer.
*
* @return totalBought If the buy order has a max total, the amount that has been purchased.
* @return totalWalletBought If the buy order has a max per wallet, the amount that has been purchased by the buyer.
* @return orderDisabled True if the order has been disabled by the creator.
* @return signatureValid True if the supplied signature is valid for the order.
* @return cosignatureValid True if there is no cosignature required or if the cosignature is valid for the order.
*/
function getBuyTrackingData(
address tokenMasterToken,
SignedOrder calldata signedOrder,
address buyer
) external view returns (
uint256 totalBought,
uint256 totalWalletBought,
bool orderDisabled,
bool signatureValid,
bool cosignatureValid
) {
bytes32 buyOrderHash = _hashSignedOrder(BUY_TYPEHASH, tokenMasterToken, signedOrder);
OrderTracking storage orderData = orderTracking[buyOrderHash];
orderDisabled = orderData.orderDisabled;
totalBought = orderData.orderTotal;
totalWalletBought = orderData.orderTotalPerWallet[buyer];
if (signedOrder.expiration >= block.timestamp) {
signatureValid = _validateOrderSignature(
tokenSettings[tokenMasterToken].orderSigners,
buyOrderHash,
signedOrder.signature
);
if (signedOrder.cosignature.signer == address(0)) {
cosignatureValid = true;
} else {
cosignatureValid = _validateCosignature(buyer, signedOrder.signature, signedOrder.cosignature);
}
}
}
/**
* @notice Gets the current status of a sell order and checks signature, cosignature validity.
*
* @dev Throws when the a supplied signature has an invalid `v` value.
* @dev Throws when a signed order has a cosignature and the cosignature is expired.
*
* @param tokenMasterToken The address of the token the sell order is for.
* @param signedOrder The signed sell order to get tracking data for.
* @param seller The address of the seller.
*
* @return totalSold If the sell order has a max total, the amount that has been sold.
* @return totalWalletSold If the sell order has a max per wallet, the amount that has been sold by the seller.
* @return orderDisabled True if the order has been disabled by the creator.
* @return signatureValid True if the supplied signature is valid for the order.
* @return cosignatureValid True if there is no cosignature required or if the cosignature is valid for the order.
*/
function getSellTrackingData(
address tokenMasterToken,
SignedOrder calldata signedOrder,
address seller
) external view returns (
uint256 totalSold,
uint256 totalWalletSold,
bool orderDisabled,
bool signatureValid,
bool cosignatureValid
) {
bytes32 sellOrderHash = _hashSignedOrder(SELL_TYPEHASH, tokenMasterToken, signedOrder);
OrderTracking storage orderData = orderTracking[sellOrderHash];
orderDisabled = orderData.orderDisabled;
totalSold = orderData.orderTotal;
totalWalletSold = orderData.orderTotalPerWallet[seller];
if (signedOrder.expiration >= block.timestamp) {
signatureValid = _validateOrderSignature(
tokenSettings[tokenMasterToken].orderSigners,
sellOrderHash,
signedOrder.signature
);
if (signedOrder.cosignature.signer == address(0)) {
cosignatureValid = true;
} else {
cosignatureValid = _validateCosignature(seller, signedOrder.signature, signedOrder.cosignature);
}
}
}
/**
* @notice Gets the current status of a spend order and checks signature, cosignature validity.
*
* @dev Throws when the a supplied signature has an invalid `v` value.
* @dev Throws when a signed order has a cosignature and the cosignature is expired.
*
* @param tokenMasterToken The address of the token the spend order is for.
* @param signedOrder The signed spend order to get tracking data for.
* @param spender The address of the spender.
*
* @return totalMultipliersSpent If the spend order has a max total, the amount of multipliers spent.
* @return totalWalletMultipliersSpent If the spend order has a max per wallet, the amount of multipliers spent by the spender.
* @return orderDisabled True if the order has been disabled by the creator.
* @return signatureValid True if the supplied signature is valid for the order.
* @return cosignatureValid True if there is no cosignature required or if the cosignature is valid for the order.
*/
function getSpendTrackingData(
address tokenMasterToken,
SignedOrder calldata signedOrder,
address spender
) external view returns (
uint256 totalMultipliersSpent,
uint256 totalWalletMultipliersSpent,
bool orderDisabled,
bool signatureValid,
bool cosignatureValid
) {
bytes32 spendOrderHash = _hashSignedOrder(SPEND_TYPEHASH, tokenMasterToken, signedOrder);
OrderTracking storage orderData = orderTracking[spendOrderHash];
orderDisabled = orderData.orderDisabled;
totalMultipliersSpent = orderData.orderTotal;
totalWalletMultipliersSpent = orderData.orderTotalPerWallet[spender];
if (signedOrder.expiration >= block.timestamp) {
signatureValid = _validateOrderSignature(
tokenSettings[tokenMasterToken].orderSigners,
spendOrderHash,
signedOrder.signature
);
if (signedOrder.cosignature.signer == address(0)) {
cosignatureValid = true;
} else {
cosignatureValid = _validateCosignature(spender, signedOrder.signature, signedOrder.cosignature);
}
}
}
/**
* @notice Gets the current settings for a token.
*
* @param tokenAddress The address of the token to get settings for.
*
* @return deployedByTokenMaster True if the token was deployed through TokenMaster.
* @return blockTransactionsFromUntrustedChannels True if transactions must be executed through a trusted channel.
* @return restrictPairingToLists True if tokens pairing with the token must be on an approved list.
* @return partnerFeeRecipient The address of the partner fee recipient.
*/
function getTokenSettings(
address tokenAddress
) external view returns (
bool deployedByTokenMaster,
bool blockTransactionsFromUntrustedChannels,
bool restrictPairingToLists,
address partnerFeeRecipient
) {
TokenSettings storage settings = tokenSettings[tokenAddress];
uint8 flags = settings.flags;
deployedByTokenMaster = _isFlagSet(flags, FLAG_DEPLOYED_BY_TOKENMASTER);
blockTransactionsFromUntrustedChannels = _isFlagSet(flags, FLAG_BLOCK_TRANSACTIONS_FROM_UNTRUSTED_CHANNELS);
restrictPairingToLists = _isFlagSet(flags, FLAG_RESTRICT_PAIRING_TO_LISTS);
partnerFeeRecipient = settings.partnerFeeRecipient;
}
/**
* @notice Returns an array of all active order signers for a token.
*
* @param tokenMasterToken The address of the token to get signers for.
*
* @return orderSigners An array of signer addresses.
*/
function getOrderSigners(address tokenMasterToken) external view returns (address[] memory orderSigners) {
orderSigners = tokenSettings[tokenMasterToken].orderSigners.values();
}
/**
* @notice Returns an array of trusted channels for a token.
*
* @param tokenMasterToken The address of the token to get trusted channels for.
*
* @return trustedChannels An array of trusted channel addresses.
*/
function getTrustedChannels(address tokenMasterToken) external view returns (address[] memory trustedChannels) {
trustedChannels = tokenSettings[tokenMasterToken].trustedChannels.values();
}
/**
* @notice Returns an array of all active pair deployers for a token.
*
* @param tokenMasterToken The address of the token to get pair deployers for.
*
* @return allowedPairToDeployers An array of pair deployer addresses.
*/
function getAllowedPairToDeployers(address tokenMasterToken) external view returns (address[] memory allowedPairToDeployers) {
allowedPairToDeployers = tokenSettings[tokenMasterToken].allowedPairToDeployers.values();
}
/**
* @notice Returns an array of all active token addresses that may pair to a token.
*
* @param tokenMasterToken The address of the token to get pair token addresses for.
*
* @return allowedPairToTokens An array of token addresses that may pair to the TokenMaster token.
*/
function getAllowedPairToTokens(address tokenMasterToken) external view returns (address[] memory allowedPairToTokens) {
allowedPairToTokens = tokenSettings[tokenMasterToken].allowedPairToTokens.values();
}
/*************************************************************************/
/* INTERNAL FUNCTIONS */
/*************************************************************************/
/**
* @dev Validates that a token was deployed by TokenMaster and, if untrusted channels are blocked,
* @dev that the caller is a trusted channel.
*
* @return tokenMasterToken Cast version of the token address to ITokenMasterERC20C for stack optimization.
* @return settings Storage pointer to the token settings for stack optimization.
*/
function _validateTokenSettingsForTransaction(
address token
) internal view returns (ITokenMasterERC20C tokenMasterToken, TokenSettings storage settings) {
tokenMasterToken = ITokenMasterERC20C(token);
settings = tokenSettings[token];
uint8 flags = settings.flags;
if (!_isFlagSet(flags, FLAG_DEPLOYED_BY_TOKENMASTER)) {
revert TokenMasterRouter__TokenNotDeployedByTokenMaster();
}
if (_isFlagSet(flags, FLAG_BLOCK_TRANSACTIONS_FROM_UNTRUSTED_CHANNELS)) {
if (!settings.trustedChannels.contains(msg.sender)) {
revert TokenMasterRouter__TransactionOriginatedFromUntrustedChannel();
}
}
}
/**
* @dev Validates the paired token's settings to ensure the deployment being executed
* @dev is permitted by the paired token's creator.
*/
function _validateTokenSettingsForDeployment(
address deployer,
address tokenAddress,
address pairedToken
) internal view {
TokenSettings storage settings = tokenSettings[pairedToken];
uint8 flags = settings.flags;
if (_isFlagSet(flags, FLAG_BLOCK_TRANSACTIONS_FROM_UNTRUSTED_CHANNELS)) {
if (!settings.trustedChannels.contains(msg.sender)) {
revert TokenMasterRouter__TransactionOriginatedFromUntrustedChannel();
}
}
if (_isFlagSet(flags, FLAG_RESTRICT_PAIRING_TO_LISTS)) {
if (!LibOwnership.isCallerTokenOrContractOwnerOrAdmin(deployer, pairedToken)) {
if (!settings.allowedPairToDeployers.contains(deployer)) {
if (!settings.allowedPairToTokens.contains(tokenAddress)) {
revert TokenMasterRouter__PairedTokenPairingRestricted();
}
}
}
}
}
/**
* @dev Transfers an amount of the paired token from the executor to the TokenMaster token.
*
* @dev Throws when native value is sent for a token that is paired with an ERC20.
* @dev Throws when an ERC20 paired token fails to transfer to the token contract.
* @dev Throws when the paired token balance in the pool decreases after the transfer.
*
* @param pairedToken Address of the paired token to transfer to the pool.
* @param tokenMasterToken Address of the TokenMaster token to transfer paired tokens to.
* @param executor Address of the account executing the transaction to withdraw paired tokens from.
* @param transferAmount Amount of tokens to transfer to the pool.
*
* @return pairedValueIn The amount of paired value that has been sent to the pool.
*/
function _transferPairedValueToPool(
address pairedToken,
address tokenMasterToken,
address executor,
uint256 transferAmount
) internal returns (uint256 pairedValueIn) {
pairedValueIn = msg.value;
if (pairedToken != address(0)) {
if (msg.value > 0) {
revert TokenMasterRouter__NativeValueNotAllowedOnERC20();
}
uint256 pairedBalanceBefore = IERC20(pairedToken).balanceOf(address(tokenMasterToken));
bool isError = SafeERC20.safeTransferFrom(pairedToken, executor, address(tokenMasterToken), transferAmount);
if (isError) {
revert TokenMasterRouter__FailedToTransferPairedToken();
}
uint256 pairedBalanceAfter = IERC20(pairedToken).balanceOf(address(tokenMasterToken));
if (pairedBalanceAfter < pairedBalanceBefore) {
revert TokenMasterRouter__FailedToTransferPairedToken();
}
unchecked {
pairedValueIn = pairedBalanceAfter - pairedBalanceBefore;
}
}
}
/**
* @dev Transfers an amount of the paired token from the executor to the TokenMaster token
* @dev using PermitC's permit transfer function.
*
* @dev Throws when the paired token address is the native token.
* @dev Throws when an ERC20 paired token fails to transfer to the token contract.
* @dev Throws when the paired token balance in the pool decreases after the transfer.
*
* @param executor Address of the account executing the transaction to withdraw paired tokens from.
* @param pairedToken Address of the paired token to transfer to the pool.
* @param tokenMasterToken Address of the TokenMaster token to transfer paired tokens to.
* @param buyOrder Basic buy order details.
* @param signedOrder Advanced order details and signatures.
* @param permitTransfer Permit transfer details for the permit transfer.
*
* @return pairedValueIn The amount of paired value that has been sent to the pool.
*/
function _permitTransferTokensToBuy(
address executor,
address pairedToken,
address tokenMasterToken,
BuyOrder calldata buyOrder,
SignedOrder calldata signedOrder,
PermitTransfer calldata permitTransfer
) internal returns (uint256 pairedValueIn) {
if (pairedToken == address(0)) {
revert TokenMasterRouter__PermitNotCompatibleWithNativeValue();
}
uint256 pairedBalanceBefore = IERC20(pairedToken).balanceOf(tokenMasterToken);
bool isError = IPermitC(permitTransfer.permitProcessor).permitTransferFromWithAdditionalDataERC20(
pairedToken,
permitTransfer.nonce,
permitTransfer.permitAmount,
permitTransfer.expiration,
executor,
tokenMasterToken,
buyOrder.pairedValueIn,
_hashBuyOrderPermitAdvancedData(buyOrder, signedOrder),
PERMITTED_TRANSFER_ADDITIONAL_DATA_BUY_TYPEHASH,
permitTransfer.signedPermit
);
if (isError) {
revert TokenMasterRouter__PermitTransferFailed();
}
uint256 pairedBalanceAfter = IERC20(pairedToken).balanceOf(tokenMasterToken);
if (pairedBalanceAfter < pairedBalanceBefore) {
revert TokenMasterRouter__FailedToTransferPairedToken();
}
unchecked {
pairedValueIn = pairedBalanceAfter - pairedBalanceBefore;
}
}
/**
* @dev Executes a buy for tokens from a TokenMaster token.
*
* @param tokenMasterToken The token to buy tokens of.
* @param pairedValueIn Amount of paired value to buy tokens with.
* @param tokensToBuy Amount of tokens to purchase.
* @param pairedToken Address of the token paired with the TokenMaster token.
*/
function _executeBuy(
ITokenMasterERC20C tokenMasterToken,
address executor,
uint256 pairedValueIn,
uint256 tokensToBuy,
address pairedToken
) internal {
(
uint256 totalCost,
uint256 refundByRouterAmount
) = tokenMasterToken.buyTokens{value: msg.value}(executor, pairedValueIn, tokensToBuy);
if (refundByRouterAmount > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
executor,
refundByRouterAmount
);
}
emit BuyOrderFilled(address(tokenMasterToken), executor, tokensToBuy, totalCost);
}
/**
* @dev Validation function for an advanced buy order.
*
* @dev Throws when the order has expired.
* @dev Throws when the order is not signed by an authorized signer.
* @dev Throws when a cosigner is specified and the cosignature is invalid.
* @dev Throws when the buy amount does not meet the order minimum.
* @dev Throws when the order has been disabled.
* @dev Throws when the order has a maximum total and the buy will exceed it.
* @dev Throws when the order has a maximum per wallet and the buy will exceed it.
*
* @param tokenMasterToken The token to buy tokens of.
* @param orderSigners Storage pointer for allowed signer addresses.
* @param executor Address of the advanced order buyer.
* @param orderTokensToBuy Amount of tokens being bought.
* @param signedOrder Advanced order details and signatures.
*
* @return tokensToBuy Amount of tokens being purchased, returned for stack optimization.
* @return executeHook If true, the advanced order hook will execute after the buy.
*/
function _validateBuyParameters(
address tokenMasterToken,
EnumerableSet.AddressSet storage orderSigners,
address executor,
uint256 orderTokensToBuy,
SignedOrder calldata signedOrder
) internal returns (uint256 tokensToBuy, bool executeHook) {
tokensToBuy = orderTokensToBuy;
executeHook = signedOrder.hook != address(0);
if (executeHook) {
if (signedOrder.expiration < block.timestamp) {
revert TokenMasterRouter__OrderExpired();
}
bytes32 orderHash = _hashSignedOrder(BUY_TYPEHASH, tokenMasterToken, signedOrder);
if (!_validateOrderSignature(orderSigners, orderHash, signedOrder.signature)) {
revert TokenMasterRouter__OrderSignerUnauthorized();
}
if (signedOrder.cosignature.signer != address(0)) {
if (!_validateCosignature(executor, signedOrder.signature, signedOrder.cosignature)) {
revert TokenMasterRouter__CosignatureInvalid();
}
}
uint256 minimumToBuy = signedOrder.baseValue;
if (signedOrder.tokenMasterOracle != address(0)) {
minimumToBuy = ITokenMasterOracle(signedOrder.tokenMasterOracle).adjustValue(
ORACLE_BUY_TRANSACTION_TYPE,
executor,
tokenMasterToken,
signedOrder.baseToken,
signedOrder.baseValue,
signedOrder.oracleExtraData
);
}
if (tokensToBuy < minimumToBuy) {
revert TokenMasterRouter__OrderDoesNotMeetMinimum();
}
OrderTracking storage orderData = orderTracking[orderHash];
if (orderData.orderDisabled) {
revert TokenMasterRouter__OrderDisabled();
}
if (signedOrder.maxTotal > 0) {
uint256 newTotal = orderData.orderTotal + tokensToBuy;
if (newTotal > signedOrder.maxTotal) {
revert TokenMasterRouter__OrderMaxTotalExceeded();
}
orderData.orderTotal = newTotal;
}
if (signedOrder.maxPerWallet > 0) {
uint256 newTotal = orderData.orderTotalPerWallet[executor] + tokensToBuy;
if (newTotal > signedOrder.maxPerWallet) {
revert TokenMasterRouter__OrderMaxPerWalletExceeded();
}
orderData.orderTotalPerWallet[executor] = newTotal;
}
}
}
/**
* @dev Validation function for an advanced sell order.
*
* @dev Throws when the order has expired.
* @dev Throws when the order is not signed by an authorized signer.
* @dev Throws when a cosigner is specified and the cosignature is invalid.
* @dev Throws when the sell amount does not meet the order minimum.
* @dev Throws when the order has been disabled.
* @dev Throws when the order has a maximum total and the sell will exceed it.
* @dev Throws when the order has a maximum per wallet and the sell will exceed it.
*
* @param tokenMasterToken The token to buy tokens of.
* @param orderSigners Storage pointer for allowed signer addresses.
* @param executor Address of the advanced order seller.
* @param tokensToSell Amount of tokens being sold.
* @param signedOrder Advanced order details and signatures.
*/
function _validateSellParameters(
address tokenMasterToken,
EnumerableSet.AddressSet storage orderSigners,
address executor,
uint256 tokensToSell,
SignedOrder calldata signedOrder
) internal {
if (signedOrder.expiration < block.timestamp) {
revert TokenMasterRouter__OrderExpired();
}
bytes32 orderHash = _hashSignedOrder(SELL_TYPEHASH, tokenMasterToken, signedOrder);
if (!_validateOrderSignature(orderSigners, orderHash, signedOrder.signature)) {
revert TokenMasterRouter__OrderSignerUnauthorized();
}
if (signedOrder.cosignature.signer != address(0)) {
if (!_validateCosignature(executor, signedOrder.signature, signedOrder.cosignature)) {
revert TokenMasterRouter__CosignatureInvalid();
}
}
uint256 minimumToSell = signedOrder.baseValue;
if (signedOrder.tokenMasterOracle != address(0)) {
minimumToSell = ITokenMasterOracle(signedOrder.tokenMasterOracle).adjustValue(
ORACLE_SELL_TRANSACTION_TYPE,
executor,
tokenMasterToken,
signedOrder.baseToken,
signedOrder.baseValue,
signedOrder.oracleExtraData
);
}
if (tokensToSell < minimumToSell) {
revert TokenMasterRouter__OrderDoesNotMeetMinimum();
}
OrderTracking storage orderData = orderTracking[orderHash];
if (orderData.orderDisabled) {
revert TokenMasterRouter__OrderDisabled();
}
if (signedOrder.maxTotal > 0) {
uint256 newTotal = orderData.orderTotal + tokensToSell;
if (newTotal > signedOrder.maxTotal) {
revert TokenMasterRouter__OrderMaxTotalExceeded();
}
orderData.orderTotal = newTotal;
}
if (signedOrder.maxPerWallet > 0) {
uint256 newTotal = orderData.orderTotalPerWallet[executor] + tokensToSell;
if (newTotal > signedOrder.maxPerWallet) {
revert TokenMasterRouter__OrderMaxPerWalletExceeded();
}
orderData.orderTotalPerWallet[executor] = newTotal;
}
}
/**
* @dev Executes a sell for tokens from a TokenMaster token.
*
* @param tokenMasterToken The token to buy tokens of.
* @param executor Address of the seller.
* @param sellOrder Basic sell order details.
*/
function _executeSell(
ITokenMasterERC20C tokenMasterToken,
address executor,
SellOrder calldata sellOrder
) internal {
(
address pairedToken,
uint256 totalReceived,
uint256 transferByRouterAmount
) = tokenMasterToken.sellTokens(executor, sellOrder.tokensToSell, sellOrder.minimumOut);
emit SellOrderFilled(address(tokenMasterToken), executor, sellOrder.tokensToSell, totalReceived);
if (transferByRouterAmount > 0) {
_transferPoolPairedToken(
tokenMasterToken,
pairedToken,
executor,
transferByRouterAmount
);
}
}
/**
* @dev Validation function for an advanced sell order.
*
* @dev Throws when the order has expired.
* @dev Throws when the order is not signed by an authorized signer.
* @dev Throws when a cosigner is specified and the cosignature is invalid.
* @dev Throws when the amount to spend exceeds the user specified maximum.
* @dev Throws when the order has been disabled.
* @dev Throws when the order has a maximum total and the sell will exceed it.
* @dev Throws when the order has a maximum per wallet and the sell will exceed it.
*
* @param orderSigners Storage pointer for allowed signer addresses.
* @param executor Address of the spender.
* @param spendOrder Basic spend details.
* @param signedOrder Advanced spend details and signature.
*
* @return adjustedAmountToSpend Amount to spend, adjusted by oracle if necessary.
* @return multiplier Multiplier of spend order being executed.
*/
function _validateSpendParameters(
EnumerableSet.AddressSet storage orderSigners,
address executor,
SpendOrder calldata spendOrder,
SignedOrder calldata signedOrder
) internal returns (uint256 adjustedAmountToSpend, uint256 multiplier) {
if (signedOrder.expiration < block.timestamp) {
revert TokenMasterRouter__OrderExpired();
}
bytes32 spendOrderHash = _hashSignedOrder(SPEND_TYPEHASH, spendOrder.tokenMasterToken, signedOrder);
if (!_validateOrderSignature(orderSigners, spendOrderHash, signedOrder.signature)) {
revert TokenMasterRouter__OrderSignerUnauthorized();
}
if (signedOrder.cosignature.signer != address(0)) {
if (!_validateCosignature(executor, signedOrder.signature, signedOrder.cosignature)) {
revert TokenMasterRouter__CosignatureInvalid();
}
}
multiplier = spendOrder.multiplier;
adjustedAmountToSpend = signedOrder.baseValue * multiplier;
if (signedOrder.tokenMasterOracle != address(0)) {
adjustedAmountToSpend = ITokenMasterOracle(signedOrder.tokenMasterOracle).adjustValue(
ORACLE_SPEND_TRANSACTION_TYPE,
executor,
spendOrder.tokenMasterToken,
signedOrder.baseToken,
adjustedAmountToSpend,
signedOrder.oracleExtraData
);
}
OrderTracking storage orderData = orderTracking[spendOrderHash];
if (orderData.orderDisabled) {
revert TokenMasterRouter__OrderDisabled();
}
if (signedOrder.maxTotal > 0) {
uint256 newTotal = orderData.orderTotal + multiplier;
if (newTotal > signedOrder.maxTotal) {
revert TokenMasterRouter__OrderMaxTotalExceeded();
}
orderData.orderTotal = newTotal;
}
if (signedOrder.maxPerWallet > 0) {
uint256 newTotal = orderData.orderTotalPerWallet[executor] + multiplier;
if (newTotal > signedOrder.maxPerWallet) {
revert TokenMasterRouter__OrderMaxPerWalletExceeded();
}
orderData.orderTotalPerWallet[executor] = newTotal;
}
}
/**
* @dev Hashes an advanced order for EIP-712 signature validation.
*
* @param typehash The EIP712 struct typehash for the order type.
* @param tokenMasterToken Address of the TokenMaster token the advanced order is for.
* @param signedOrder Advanced order details.
*
* @return orderHash The struct hash for EIP-712 signature validation.
*/
function _hashSignedOrder(
bytes32 typehash,
address tokenMasterToken,
SignedOrder calldata signedOrder
) internal pure returns (bytes32 orderHash) {
orderHash = EfficientHash.efficientHashElevenStep2(
EfficientHash.efficientHashElevenStep1(
typehash,
signedOrder.creatorIdentifier,
bytes32(uint256(uint160(tokenMasterToken))),
bytes32(uint256(uint160(signedOrder.tokenMasterOracle))),
bytes32(uint256(uint160(signedOrder.baseToken))),
bytes32(signedOrder.baseValue),
bytes32(signedOrder.maxPerWallet),
bytes32(signedOrder.maxTotal)
),
bytes32(signedOrder.expiration),
bytes32(uint256(uint160(signedOrder.hook))),
bytes32(uint256(uint160(signedOrder.cosignature.signer)))
);
}
/**
* @dev Hashes the advanced permit transfer data for a PermitC transfer of paired tokens.
*
* @param buyOrder Basic buy details.
* @param signedOrder Advanced order details.
*
* @return hash The struct hash validation in PermitC.
*/
function _hashBuyOrderPermitAdvancedData(
BuyOrder calldata buyOrder,
SignedOrder calldata signedOrder
) internal pure returns(bytes32 hash) {
hash = EfficientHash.efficientHashNineStep2(
EfficientHash.efficientHashNineStep1(
PERMITTED_TRANSFER_BUY_TYPEHASH,
bytes32(uint256(uint160(buyOrder.tokenMasterToken))),
bytes32(buyOrder.tokensToBuy),
bytes32(buyOrder.pairedValueIn),
signedOrder.creatorIdentifier,
bytes32(uint256(uint160(signedOrder.hook))),
bytes32(signedOrder.signature.v),
signedOrder.signature.r
),
signedOrder.signature.s
);
}
/**
* @dev Validates that an order signature is from an allowed order signer for the TokenMaster token.
*
* @dev Throws when the signature's supplied `v` value is greater than 255.
*
* @param orderSigners Storage pointer for allowed signer addresses.
* @param orderHash The order struct hash for EIP-712 signature validation.
* @param signature Signature r, s, and v values for signer address recovery.
*
* @return isValid True if the recovered signing address is a valid signer.
*/
function _validateOrderSignature(
EnumerableSet.AddressSet storage orderSigners,
bytes32 orderHash,
SignatureECDSA calldata signature
) internal view returns (bool isValid) {
if (signature.v > type(uint8).max) {
revert Error__InvalidSignatureV();
}
address signer = ecrecover(_hashTypedDataV4(orderHash), uint8(signature.v), signature.r, signature.s);
isValid = orderSigners.contains(signer);
}
/**
* @dev Validates that an authority signature if signing authority is enabled for token deployments.
*
* @dev Throws when the signature's supplied `v` value is greater than 255.
*
* @param digest EIP-712 digest for signer address recovery.
* @param signature Signature r, s, and v values for signer address recovery.
* @param authority The signing authority set in the role server.
*
* @return isValid True if the recovered signing address is the signing authority.
*/
function _validateAuthoritySignature(
bytes32 digest,
SignatureECDSA calldata signature,
address authority
) internal pure returns (bool isValid) {
if (signature.v > type(uint8).max) {
revert Error__InvalidSignatureV();
}
isValid = authority == ecrecover(digest, uint8(signature.v), signature.r, signature.s);
}
/**
* @dev Validates that a cosignature is valid for an advanced order.
*
* @dev Throws when the cosignature has expired.
* @dev Throws when the signature's supplied `v` value is greater than 255.
*
* @param executor Address of the transaction executor.
* @param signature Signature r, s, and v values for the advanced order.
* @param cosignature Cosignature to validate.
*
* @return isValid True if the cosignature is valid.
*/
function _validateCosignature(
address executor,
SignatureECDSA calldata signature,
Cosignature calldata cosignature
) internal view returns (bool isValid) {
if (cosignature.expiration < block.timestamp) {
revert TokenMasterRouter__CosignatureExpired();
}
if (cosignature.v > type(uint8).max) {
revert Error__InvalidSignatureV();
}
bytes32 cosignatureHash = _hashTypedDataV4(
EfficientHash.efficientHash(
COSIGNATURE_TYPEHASH,
bytes32(signature.v),
signature.r,
signature.s,
bytes32(cosignature.expiration),
bytes32(uint256(uint160(executor)))
)
);
isValid = ecrecover(cosignatureHash, uint8(cosignature.v), cosignature.r, cosignature.s) == cosignature.signer;
}
/**
* @dev Transfers an amount of paired token from a TokenMaster token to a recipient.
* @dev This function will attempt to reset token approvals to handle unusual approval
* @dev formats in paired tokens.
*
* @dev Throws when the transfer fails.
*
* @param tokenMasterToken Address of the TokenMaster token to transfer paired tokens from.
* @param pairedToken Address of the paired token to transfer from the pool.
* @param to Address to send the paired tokens to.
* @param amount Amount of tokens to transfer from the pool.
*/
function _transferPoolPairedToken(
ITokenMasterERC20C tokenMasterToken,
address pairedToken,
address to,
uint256 amount
) internal {
bool isError = SafeERC20.safeTransferFrom(pairedToken, address(tokenMasterToken), to, amount);
if (isError) {
// Potential approval issue with non-standard ERC20 paired token
// attempt to reset approvals and transfer again.
tokenMasterToken.resetPairedTokenApproval();
isError = SafeERC20.safeTransferFrom(pairedToken, address(tokenMasterToken), to, amount);
if (isError) {
revert TokenMasterRouter__FailedToTransferPairedToken();
}
}
}
/**
* @dev Returns the executor for a transaction with trusted forwarder context if appended data length is 20 bytes.
*
* @dev Throws when appended data length is not zero or 20 bytes.
*
* @param expectedDataLength The length of calldata expected for the transaction.
*
* @return executor The address of the executor for the transaction.
*/
function _getExecutor(
uint256 expectedDataLength
) internal view returns(address executor) {
unchecked {
uint256 appendedDataLength = msg.data.length - expectedDataLength;
executor = msg.sender;
if (appendedDataLength > 0) {
if (appendedDataLength != 20) revert TokenMasterRouter__BadCalldataLength();
executor = _msgSender();
}
}
}
/**
* @dev Convenience function to adjust a calldata parameter's expected length rounding up to
* @dev the nearest 32 byte amount.
*
* @dev Throws when the supplied bytes length is greater than type(uint32).max.
*
* @param currentDataLength Current length of expected calldata.
* @param bytesLength Length of the bytes field.
*
* @return totalLength Length of expected calldata with the bytes length rounded up.
*/
function _addAdjustedBytesLength(uint256 currentDataLength, uint256 bytesLength) internal pure returns (uint256 totalLength) {
if (bytesLength > type(uint32).max) {
revert TokenMasterRouter__BadCalldataLength();
}
unchecked {
totalLength = currentDataLength + ((bytesLength + 31) & ~uint256(31));
}
}
/**
* @dev Hashes the deployment parameters and validates the signature against the signing authority.
*
* @dev Throws when the signature is not from the signing authority.
*
* @param deploymentParameters The parameters for the token being deployed.
* @param signature The signature from the signing authority.
* @param deploymentAuthority The address set as the signing authority for deployments in the role server.
*/
function _validateDeploymentSignature(
DeploymentParameters calldata deploymentParameters,
SignatureECDSA calldata signature,
address deploymentAuthority
) internal view {
bytes32 digest = _hashTypedDataV4(
_hashDeploymentParameters(deploymentParameters)
);
if (!_validateAuthoritySignature(digest, signature, deploymentAuthority)) {
revert TokenMasterRouter__InvalidDeploymentSignature();
}
}
/**
* @dev Hashes the deployment parameters for a new TokenMaster token deployment for EIP-712 signature validation.
*
* @param deploymentParameters The parameters for the token being deployed.
*
* @return hash The struct hash of the deployment parameters.
*/
function _hashDeploymentParameters(DeploymentParameters calldata deploymentParameters) internal pure returns (bytes32 hash) {
hash = EfficientHash.efficientHash(
DEPLOYMENT_TYPEHASH,
bytes32(uint256(uint160(deploymentParameters.tokenFactory))),
deploymentParameters.tokenSalt,
bytes32(uint256(uint160(deploymentParameters.tokenAddress))),
bytes32(uint256(deploymentParameters.blockTransactionsFromUntrustedChannels ? 1 : 0)),
bytes32(uint256(deploymentParameters.restrictPairingToLists ? 1 : 0))
);
}
/**
* @dev Returns true if the `flagValue` has the `flag` set, false otherwise.
*
* @dev This function uses the bitwise AND operator to check if the `flag` is set in `flagValue`.
*
* @param flagValue The value to check for the presence of the `flag`.
* @param flag The flag to check for in the `flagValue`.
*/
function _isFlagSet(uint8 flagValue, uint8 flag) internal pure returns (bool flagSet) {
flagSet = (flagValue & flag) != 0;
}
/**
* @dev Sets the `flag` in `flagValue` to `flagSet` and returns the updated value.
*
* @dev This function uses the bitwise OR and AND operators to set or unset the `flag` in `flagValue`.
*
* @param flagValue The value to set the `flag` in.
* @param flag The flag to set in the `flagValue`.
* @param flagSet True to set the `flag`, false to unset the `flag`.
*/
function _setFlag(uint8 flagValue, uint8 flag, bool flagSet) internal pure returns (uint8) {
if (flagSet) {
return (flagValue | flag);
} else {
unchecked {
return (flagValue & (255 - flag));
}
}
}
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
/**
* @title ITokenMasterBuyHook
* @author Limit Break, Inc.
* @notice Interface that must be implemented by contracts acting as a buy hook
* @notice for advanced buy orders.
*/
interface ITokenMasterBuyHook {
function tokenMasterBuyHook(
address tokenMasterToken,
address buyer,
bytes32 creatorBuyIdentifier,
uint256 amountPurchased,
bytes calldata hookExtraData
) external;
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
/**
* @title ITokenMasterERC20C
* @author Limit Break, Inc.
* @notice Interface that must be implemented by token contracts that will be
* @notice deployed through TokenMasterRouter.
*/
interface ITokenMasterERC20C {
/// @dev Emitted when a creator withdraws their share or when fees are withdrawn.
event CreatorShareWithdrawn(address to, uint256 withdrawAmount, uint256 infrastructureAmount, uint256 partnerAmount);
/// @dev Emitted when a creator transfers a portion of their share to the market bonded value. Infrastructure and partner amounts are transferred to their respective receivers.
event CreatorShareTransferredToMarket(address to, uint256 transferAmount, uint256 infrastructureAmount, uint256 partnerAmount);
function PAIRED_TOKEN() external view returns(address);
function buyTokens(
address buyer,
uint256 pairedTokenIn,
uint256 pooledTokenToBuy
) external payable returns(uint256 totalCost, uint256 refundByRouterAmount);
function sellTokens(
address seller,
uint256 pooledTokenToSell,
uint256 pairedTokenMinimumOut
) external returns (address pairedToken, uint256 pairedValueToSeller, uint256 transferByRouterAmount);
function spendTokens(address spender, uint256 pooledTokenToSpend) external;
function withdrawCreatorShare(
address withdrawTo,
uint256 withdrawAmount,
address infrastructureFeeRecipient,
address partnerFeeRecipient
) external returns (
address pairedToken,
uint256 transferByRouterAmountCreator,
uint256 transferByRouterAmountInfrastructure,
uint256 transferByRouterAmountPartner
);
function transferCreatorShareToMarket(
uint256 transferAmount,
address infrastructureFeeRecipient,
address partnerFeeRecipient
) external returns(address pairedToken, uint256 transferByRouterAmountInfrastructure, uint256 transferByRouterAmountPartner);
function withdrawFees(
address infrastructureFeeRecipient,
address partnerFeeRecipient
) external returns (
address pairedToken,
uint256 transferByRouterAmountInfrastructure,
uint256 transferByRouterAmountPartner
);
function withdrawUnrelatedToken(address tokenAddress, address withdrawTo, uint256 withdrawAmount) external;
function resetPairedTokenApproval() external;
function pairedTokenShares() external view returns(uint256 marketShare, uint256 creatorShare, uint256 infrastructureShare, uint256 partnerShare);
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
import "../DataTypes.sol";
/**
* @title ITokenMasterFactory
* @author Limit Break, Inc.
* @notice Interface that must be implemented by contracts that are factories
* @notice for tokens that will be deployed through TokenMasterRouter.
*/
interface ITokenMasterFactory {
function deployToken(
bytes32 tokenSalt,
PoolDeploymentParameters calldata poolParams,
uint256 pairedValueIn,
uint256 infrastructureFeeBPS
) external returns(address deployedAddress);
function computeDeploymentAddress(
bytes32 tokenSalt,
PoolDeploymentParameters calldata poolParams,
uint256 pairedValueIn,
uint256 infrastructureFeeBPS
) external view returns(address deploymentAddress);
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
/**
* @title ITokenMasterOracle
* @author Limit Break, Inc.
* @notice Interface that must be implemented by contracts acting as an oracle
* @notice for advanced orders.
*/
interface ITokenMasterOracle {
function adjustValue(
uint256 transactionType,
address executor,
address tokenMasterToken,
address baseToken,
uint256 baseValue,
bytes calldata oracleExtraData
) external view returns(uint256 tokenValue);
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
import "../DataTypes.sol";
import "./ITokenMasterERC20C.sol";
/**
* @title ITokenMasterRouter
* @author Limit Break, Inc.
* @notice Interface definition for the TokenMasterRouter contract.
*/
interface ITokenMasterRouter {
/// @dev Emitted when the TokenMaster admin updates the infrastructure fee for new token deployments.
event InfrastructureFeeUpdated(uint16 infrastructureFeeBPS);
/// @dev Emitted when the TokenMaster admin updates an allowed token factory.
event AllowedTokenFactoryUpdated(address indexed tokenFactory, bool allowed);
/// @dev Emitted when a token has been deployed.
event TokenMasterTokenDeployed(address indexed tokenMasterToken, address indexed pairedToken, address indexed tokenFactory);
/// @dev Emitted when a token's settings have been updated.
event TokenSettingsUpdated(
address indexed tokenMasterToken,
bool blockTransactionsFromUntrustedChannels,
bool restrictPairingToLists
);
/// @dev Emitted when a trusted channel has been added or removed.
event TrustedChannelUpdated(
address indexed tokenAddress,
address indexed channel,
bool allowed
);
/// @dev Emitted when a token's partner has proposed a new fee recipient address.
event PartnerFeeRecipientProposed(
address indexed tokenAddress,
address proposedPartnerFeeRecipient
);
/// @dev Emitted when the creator has accepted the token partner's proposed fee recipient address.
event PartnerFeeRecipientUpdated(
address indexed tokenAddress,
address partnerFeeRecipient
);
/// @dev Emitted when a deployer has been added or removed as an allowed deployer for tokens pairing to a creator's token.
event AllowedPairToDeployersUpdated(
address indexed tokenAddress,
address indexed deployer,
bool allowed
);
/// @dev Emitted when a specific token has been added or removed as an allowed token for pairing to a creator's token.
event AllowedPairToTokensUpdated(
address indexed tokenAddress,
address indexed tokenAllowedToPair,
bool allowed
);
/// @dev Emitted when a buy tokens order has been filled.
event BuyOrderFilled(
address indexed tokenMasterToken,
address indexed buyer,
uint256 amountPurchased,
uint256 totalCost
);
/// @dev Emitted when a sell tokens order has been filled.
event SellOrderFilled(
address indexed tokenMasterToken,
address indexed seller,
uint256 amountSold,
uint256 totalReceived
);
/// @dev Emitted when a spend tokens order has been filled.
event SpendOrderFilled(
address indexed tokenMasterToken,
bytes32 indexed creatorSpendIdentifier,
address indexed spender,
uint256 amountSpent,
uint256 multiplier
);
/// @dev Emitted when a order signer has been updated.
event OrderSignerUpdated(
address indexed tokenMasterToken,
address indexed signer,
bool allowed
);
/// @dev Emitted when an advanced buy order has been disabled or enabled.
event BuyOrderDisabled(
address indexed tokenMasterToken,
bytes32 indexed creatorBuyIdentifier,
bool disabled
);
/// @dev Emitted when an advanced sell order has been disabled or enabled.
event SellOrderDisabled(
address indexed tokenMasterToken,
bytes32 indexed creatorSellIdentifier,
bool disabled
);
/// @dev Emitted when an spend order has been disabled or enabled.
event SpendOrderDisabled(
address indexed tokenMasterToken,
bytes32 indexed creatorSpendIdentifier,
bool disabled
);
function buyTokens(BuyOrder calldata buyOrder) external payable;
function buyTokensAdvanced(
BuyOrder calldata buyOrder,
SignedOrder calldata signedOrder,
PermitTransfer calldata permitTransfer
) external payable;
function sellTokens(SellOrder calldata sellOrder) external;
function sellTokensAdvanced(SellOrder calldata sellOrder, SignedOrder calldata signedOrder) external;
function spendTokens(
SpendOrder calldata spendOrder,
SignedOrder calldata signedOrder
) external;
function deployToken(
DeploymentParameters calldata deploymentParameters,
SignatureECDSA calldata signature
) external payable;
function updateTokenSettings(
address tokenAddress,
bool blockTransactionsFromUntrustedChannels,
bool restrictPairingToLists
) external;
function setOrderSigner(address tokenMasterToken, address signer, bool allowed) external;
function setTokenAllowedPairToDeployer(address tokenAddress, address deployer, bool allowed) external;
function setTokenAllowedPairToToken(address tokenAddress, address tokenAllowedToPair, bool allowed) external;
function disableBuyOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external;
function disableSellOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external;
function disableSpendOrder(address tokenMasterToken, SignedOrder calldata signedOrder, bool disabled) external;
function withdrawCreatorShare(ITokenMasterERC20C tokenMasterToken, address withdrawTo, uint256 withdrawAmount) external;
function transferCreatorShareToMarket(ITokenMasterERC20C tokenMasterToken, uint256 transferAmount) external;
function acceptProposedPartnerFeeReceiver(address tokenMasterToken, address expectedPartnerFeeRecipient) external;
function partnerProposeFeeReceiver(address tokenMasterToken, address proposedPartnerFeeRecipient) external;
function setAllowedTokenFactory(address tokenFactory, bool allowed) external;
function setInfrastructureFee(uint16 _infrastructureFeeBPS) external;
function withdrawFees(ITokenMasterERC20C[] calldata tokenMasterTokens) external;
function getBuyTrackingData(
address tokenMasterToken,
SignedOrder calldata signedOrder,
address buyer
) external view returns (
uint256 totalBought,
uint256 totalWalletBought,
bool orderDisabled,
bool signatureValid,
bool cosignatureValid
);
function getSellTrackingData(
address tokenMasterToken,
SignedOrder calldata signedOrder,
address seller
) external view returns (
uint256 totalSold,
uint256 totalWalletSold,
bool orderDisabled,
bool signatureValid,
bool cosignatureValid
);
function getSpendTrackingData(
address tokenMasterToken,
SignedOrder calldata signedOrder,
address spender
) external view returns (
uint256 totalMultipliersSpent,
uint256 totalWalletMultipliersSpent,
bool orderDisabled,
bool signatureValid,
bool cosignatureValid
);
function getTokenSettings(
address tokenAddress
) external view returns (
bool deployedByTokenMaster,
bool blockTransactionsFromUntrustedChannels,
bool restrictPairingToLists,
address partnerFeeRecipient
);
function getOrderSigners(address tokenMasterToken) external view returns (address[] memory orderSigners);
function getTrustedChannels(address tokenMasterToken) external view returns (address[] memory trustedChannels);
function getAllowedPairToDeployers(address tokenMasterToken) external view returns (address[] memory allowedPairToDeployers);
function getAllowedPairToTokens(address tokenMasterToken) external view returns (address[] memory allowedPairToTokens);
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
/**
* @title ITokenMasterSellHook
* @author Limit Break, Inc.
* @notice Interface that must be implemented by contracts acting as a sell hook
* @notice for advanced sell orders.
*/
interface ITokenMasterSellHook {
function tokenMasterSellHook(
address tokenMasterToken,
address seller,
bytes32 creatorSellIdentifier,
uint256 amountSold,
bytes calldata hookExtraData
) external;
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
/**
* @title ITokenMasterSpendHook
* @author Limit Break, Inc.
* @notice Interface that must be implemented by contracts acting as a spend hook
* @notice for spend orders.
*/
interface ITokenMasterSpendHook {
function tokenMasterSpendHook(
address tokenMasterToken,
address spender,
bytes32 creatorSpendIdentifier,
uint256 multiplier,
bytes calldata hookExtraData
) external;
}
//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
library LibOwnership {
bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;
error Ownership__CallerIsNotTokenOrOwnerOrAdmin();
error Ownership__CallerIsNotTokenOrOwnerOrAdminOrRole();
/**
* @notice Reverts the transaction if the caller is not the owner or assigned the default
* @notice admin role of the contract at `tokenAddress`.
*
* @dev Throws when the caller is neither owner nor assigned the default admin role.
*
* @param tokenAddress The contract address of the token to check permissions for.
*/
function requireCallerIsTokenOrContractOwnerOrAdmin(address tokenAddress) internal view {
if (msg.sender == tokenAddress) {
return;
}
(address contractOwner,) = safeOwner(tokenAddress);
if (msg.sender == contractOwner) {
return;
}
(bool callerIsContractAdmin,) = safeHasRole(tokenAddress, DEFAULT_ACCESS_CONTROL_ADMIN_ROLE, msg.sender);
if (callerIsContractAdmin) {
return;
}
revert Ownership__CallerIsNotTokenOrOwnerOrAdmin();
}
/**
* @notice Returns if the caller is the token contract, owner or assigned the default
* @notice admin role of the contract at `tokenAddress`.
*
* @param caller The address calling the contract
* @param tokenAddress The contract address of the token to check permissions for.
*
* @return isTokenOwnerOrAdmin True if caller is token, owner or admin, false otherwise
*/
function isCallerTokenOrContractOwnerOrAdmin(
address caller,
address tokenAddress
) internal view returns (bool isTokenOwnerOrAdmin) {
if (caller == tokenAddress) {
return true;
}
(address contractOwner,) = safeOwner(tokenAddress);
if (caller == contractOwner) {
return true;
}
(bool callerIsContractAdmin,) = safeHasRole(tokenAddress, DEFAULT_ACCESS_CONTROL_ADMIN_ROLE, caller);
return callerIsContractAdmin;
}
/**
* @notice Reverts the transaction if the caller is not the owner or assigned the default
* @notice admin role of the contract at `tokenAddress`.
*
* @dev Throws when the caller is neither owner nor assigned the default admin role.
*
* @param tokenAddress The contract address of the token to check permissions for.
*/
function requireCallerIsTokenOrContractOwnerOrAdminOrRole(address tokenAddress, bytes32 role) internal view {
if (msg.sender == tokenAddress) {
return;
}
(address contractOwner,) = safeOwner(tokenAddress);
if (msg.sender == contractOwner) {
return;
}
(bool callerIsContractAdmin,) = safeHasRole(tokenAddress, DEFAULT_ACCESS_CONTROL_ADMIN_ROLE, msg.sender);
if (callerIsContractAdmin) {
return;
}
(bool callerHasRole,) = safeHasRole(tokenAddress, role, msg.sender);
if (callerHasRole) {
return;
}
revert Ownership__CallerIsNotTokenOrOwnerOrAdminOrRole();
}
/**
* @dev A gas efficient, and fallback-safe way to call the owner function on a token contract.
* This will get the owner if it exists - and when the function is unimplemented, the
* presence of a fallback function will not result in halted execution.
*
* @param tokenAddress The address of the token collection to get the owner of.
*
* @return owner The owner of the token collection contract.
* @return isError True if there was an error in retrieving the owner, false if the call was successful.
*/
function safeOwner(
address tokenAddress
) private view returns(address owner, bool isError) {
assembly ("memory-safe") {
function _callOwner(_tokenAddress) -> _owner, _isError {
mstore(0x00, 0x8da5cb5b)
if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _tokenAddress, 0x1C, 0x04, 0x00, 0x20)) {
_owner := mload(0x00)
leave
}
_isError := true
}
owner, isError := _callOwner(tokenAddress)
}
}
/**
* @dev A gas efficient, and fallback-safe way to call the hasRole function on a token contract.
* This will check if the account `hasRole` if `hasRole` exists - and when the function is unimplemented, the
* presence of a fallback function will not result in halted execution.
*
* @param tokenAddress The address of the token collection to call hasRole on.
* @param role The role to check if the account has on the collection.
* @param account The address of the account to check if they have a specified role.
*
* @return hasRole The owner of the token collection contract.
* @return isError True if there was an error in retrieving the owner, false if the call was successful.
*/
function safeHasRole(
address tokenAddress,
bytes32 role,
address account
) private view returns(bool hasRole, bool isError) {
assembly ("memory-safe") {
function _callHasRole(_tokenAddress, _role, _account) -> _hasRole, _isError {
let ptr := mload(0x40)
mstore(0x40, add(ptr, 0x60))
mstore(ptr, 0x91d14854)
mstore(add(0x20, ptr), _role)
mstore(add(0x40, ptr), _account)
if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _tokenAddress, add(ptr, 0x1C), 0x44, 0x00, 0x20)) {
_hasRole := mload(0x00)
leave
}
_isError := true
}
hasRole, isError := _callHasRole(tokenAddress, role, account)
}
}
}