Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
pragma solidity ^0.8.0 ^0.8.1 ^0.8.2 ^0.8.20 ^0.8.28;
// lib/openzeppelin-contracts-upgradeable/contracts/utils/AddressUpgradeable.sol
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.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
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile 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 {MessageHashUtils-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]
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
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.
assembly ("memory-safe") {
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, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile 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 {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
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[ERC-2098 short signatures]
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
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.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// 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, s);
}
// 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, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @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, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
// src/interfaces/IAccountingPausable.sol
/**
* @title AccountingPausable
* @notice Interface for pausable accounting functionality
* @dev Defines the interface for pausing and unpausing minting and burning operations independently from transfers
*/
interface IAccountingPausable {
/**
* @dev Pauses accounting operations (minting and burning)
* @notice Can only be called by authorized addresses (defined in implementation)
*/
function accountingPause() external;
/**
* @dev Unpauses accounting operations
* @notice Can only be called by authorized addresses (defined in implementation)
*/
function accountingUnpause() external;
/**
* @dev Returns whether accounting is currently paused
* @return True if accounting is paused, false otherwise
*/
function isAccountingPaused() external view returns (bool);
/**
* @dev Emitted when accounting is paused by `pauser`
*/
event AccountingPaused(address indexed pauser);
/**
* @dev Emitted when accounting is unpaused by `unpauser`
*/
event AccountingUnpaused(address indexed unpauser);
/**
* @dev Error thrown when attempting to perform accounting operations while paused
*/
error AccountingIsPaused();
/**
* @dev Error thrown when attempting to unpause accounting when it's not paused
*/
error AccountingIsNotPaused();
}
// src/interfaces/allowlist/IAllowlistAddressPermissions.sol
/**
* @title IAllowlistAddressPermissions
* @notice Core interface for address permissions and entity ID lookups
* @dev This interface provides the essential allowlist functions needed for
* checking address permissions and entity mappings
*/
interface IAllowlistAddressPermissions {
/**
* @notice Gets the entityId for the provided address
* @param addr The address to get the entityId for
*/
function addressEntityIds(address addr) external view returns (uint256);
/**
* @notice Checks whether an address is allowed to use a private instrument
* @param addr The address to check permissions for
* @param instrument The private instrument symbol to check permissions for
*/
function isAddressAllowedForPrivateInstrument(address addr, string calldata instrument) external view returns (bool);
/**
* @dev Checks if an address is allowed to interact with public instrument
* @param addr The address to check
* @return True if the address is allowed, false otherwise
*/
function isAddressAllowedForPublicInstrument(address addr) external view returns (bool);
}
// src/interfaces/IAllowlistable.sol
/**
* @title IAllowlistable
* @notice Interface for allowlist functionality
* @dev Defines the interface for checking if addresses have permission to interact with the token
*/
interface IAllowlistable {
/**
* @dev Returns the current allowlist contract
* @return The allowlist contract address
*/
function allowlist() external view returns (address);
/**
* @dev Returns whether the instrument is public or private
* @return True if the instrument is public, false if private
*/
function isPublicInstrument() external view returns (bool);
/**
* @dev Checks if an address is allowed to interact with a private instrument
* @param addr The address to check
* @param instrument The private instrument identifier (typically a symbol)
* @return True if the address is allowed, false otherwise
*/
function isAddressAllowedForPrivateInstrument(address addr, string calldata instrument) external view returns (bool);
/**
* @dev Checks if an address is allowed to interact with public instrument
* @param addr The address to check
* @return True if the address is allowed, false otherwise
*/
function isAddressAllowedForPublicInstrument(address addr) external view returns (bool);
/**
* @dev Checks if two addresses belong to the same entity
* @param addr1 The first address
* @param addr2 The second address
* @return True if both addresses belong to the same entity, false otherwise
*/
function isSameEntity(address addr1, address addr2) external view returns (bool);
/**
* @dev Emitted when the allowlist is updated
*/
event AllowlistUpdated(address indexed oldAllowlist, address indexed newAllowlist);
/**
* @dev Emitted when the instrument type (public/private) is updated
*/
event IsPublicInstrumentUpdated(bool oldIsPublicInstrument, bool newIsPublicInstrument);
/**
* @dev Error thrown when an address doesn't have sufficient permissions
*/
error InsufficientPermissions();
/**
* @dev Error thrown when entity IDs don't match
*/
error MismatchEntityIds();
error ZeroAddressNotAllowed();
}
// src/interfaces/IBridgeable.sol
/**
* @title IBridgeable
* @notice Interface for cross-chain bridging functionality
* @dev Defines the interface for burning tokens on one chain and minting on another
*/
interface IBridgeable {
/**
* @dev Returns whether a chain ID is supported for bridging
* @param chainId The chain ID to check
* @return True if the chain ID is supported, false otherwise
*/
function isChainIdSupported(uint256 chainId) external view returns (bool);
/**
* @dev Sets support status for a specific chain ID
* @param chainId The chain ID to update
* @param supported Whether the chain ID should be supported
*/
function setChainIdSupport(uint256 chainId, bool supported) external;
/**
* @notice Burns tokens from the caller to bridge to another chain
* @dev If destination address on chainId isn't on allowlist, or chainID isn't supported, tokens burn to book entry.
* @dev chainId as 0 indicates wanting to burn tokens to book entry, for use through the Superstate UI.
* @param amount Amount of tokens to burn
* @param ethDestinationAddress ETH address to send to on another chain
* @param otherDestinationAddress Non-EVM addresses to send to on another chain
* @param chainId Numerical identifier of destination chain to send tokens to
*/
function bridge(
uint256 amount,
address ethDestinationAddress,
string memory otherDestinationAddress,
uint256 chainId
) external;
/**
* @dev Burns tokens from the caller to bridge to Superstate book entry
* @param amount Amount of tokens to burn
*/
function bridgeToBookEntry(uint256 amount) external;
/// @notice Emitted when a chain ID's support status is updated
event SetChainIdSupport(uint256 indexed chainId, bool oldSupported, bool newSupported);
/// @dev Event emitted when the user wants to bridge their tokens to another chain or book entry
event Bridge(
address caller,
address indexed src,
uint256 amount,
address indexed ethDestinationAddress,
string otherDestinationAddress,
uint256 chainId
);
/**
* @dev Error thrown when destination chain is not supported
*/
error BridgeChainIdDestinationNotSupported();
/**
* @dev Error thrown when both ETH and non-ETH destination addresses are provided
*/
error TwoDestinationsInvalid();
/**
* @dev Error thrown when zero tokens are provided for bridging
*/
error ZeroSuperstateTokensOutBridgeable();
/**
* @dev Error thrown when destination is set for bridge to book entry
*/
error OnchainDestinationSetForBridgeToBookEntry();
/**
* @dev Error thrown for invalid function arguments
*/
error BadArgsBridgeable();
}
// lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}
// lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @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);
}
// src/interfaces/IPermittable.sol
/**
* @title IPermittable
* @notice Interface for the Permittable contract implementing EIP-712 compliant permit functionality
* @dev Defines functions for approving token spending through signatures (EIP-2612)
*/
interface IPermittable {
/**
* @dev Error thrown when signature has expired
*/
error SignatureExpired();
/**
* @dev Error thrown when S value in signature is invalid
*/
error InvalidSignatureS();
/**
* @dev Error thrown when signature verification fails
*/
error BadSignatory();
/**
* @dev Returns the current nonce for an address
* @param owner The address to get the nonce for
* @return The current nonce
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Sets approval amount for a spender via signature from signatory
* @param owner The address that signed the signature
* @param spender The address to authorize (or rescind authorization from)
* @param value Amount that `owner` is approving for `spender`
* @param deadline Expiration time for the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the domain separator used in the encoding of the signature for permit
* @return bytes32 The domain separator
*/
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// src/AccountingPausable.sol
/**
* @title AccountingPausable
* @notice Abstract contract implementing pausable accounting functionality with ERC-7201 namespaced storage
* @dev Allows pausing of minting and burning operations independently from transfers
*/
abstract contract AccountingPausable is IAccountingPausable {
/**
* @dev Storage struct using ERC-7201 namespaced pattern
* @custom:storage-location erc7201:superstate.storage.accountingPausable
*/
struct AccountingPausableStorage {
bool accountingPaused;
}
// keccak256(abi.encode(uint256(keccak256(bytes("superstate.storage.accountingPausable"))) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ACCOUNTING_PAUSABLE_STORAGE_LOCATION =
0xa9bb159d35c3405794a368b076cfb461c3cfec91ff00ca1c544d07e26a468e00;
/* Requirements for implementation:
1. _requireAuth() must be implemented by the inheriting contract
*/
/**
* @dev Hook to require authorization for pausing/unpausing
* @notice Must be implemented by inheriting contract
*/
function _requireAuth() internal view virtual;
/*
Provides the following to be used:
1. _requireNotAccountingPaused() - reverts if accounting is paused
2. accountingPause() - pauses accounting operations, _requireAuth
3. accountingUnpause() - unpauses accounting operations, _requireAuth
*/
/**
* @dev Pauses accounting operations (minting and burning)
* @notice Can only be called by authorized addresses (defined in inheriting contract)
*/
function accountingPause() external virtual {
_requireAuth(); // This would be implemented by the inheriting contract
if (isAccountingPaused()) revert AccountingIsPaused();
_setAccountingPaused(true);
emit AccountingPaused(msg.sender);
}
/**
* @dev Unpauses accounting operations
* @notice Can only be called by authorized addresses (defined in inheriting contract)
*/
function accountingUnpause() external virtual {
_requireAuth(); // This would be implemented by the inheriting contract
if (!isAccountingPaused()) revert AccountingIsNotPaused();
_setAccountingPaused(false);
emit AccountingUnpaused(msg.sender);
}
/**
* @dev Returns the AccountingPausableStorage struct
* @return $ Storage pointer to the AccountingPausableStorage struct
*/
function _getAccountingPausableStorage() private pure returns (AccountingPausableStorage storage $) {
assembly {
$.slot := ACCOUNTING_PAUSABLE_STORAGE_LOCATION
}
}
/**
* @dev Returns whether accounting is currently paused
* @return True if accounting is paused, false otherwise
*/
function isAccountingPaused() public view virtual returns (bool) {
AccountingPausableStorage storage $ = _getAccountingPausableStorage();
return $.accountingPaused;
}
/**
* @dev Sets the paused state of accounting
* @param paused The new paused state
*/
function _setAccountingPaused(bool paused) internal {
AccountingPausableStorage storage $ = _getAccountingPausableStorage();
$.accountingPaused = paused;
}
function __AccountingPausable_init() internal {
_setAccountingPaused(false); // Unnecessary as started as false by default, done for clarity
}
}
// src/Bridgeable.sol
/**
* @title Bridgeable
* @notice Abstract contract implementing cross-chain bridging functionality with ERC-7201 namespaced storage
* @dev Allows tokens to be burned on one chain and minted on another chain
*/
abstract contract Bridgeable is IBridgeable {
/**
* @dev Storage struct using ERC-7201 namespaced pattern
* @custom:storage-location erc7201:superstate.storage.bridgeable
*/
struct BridgeableStorage {
mapping(uint256 chainId => bool supported) supportedChainIds;
}
// keccak256(abi.encode(uint256(keccak256(bytes("superstate.storage.bridgeable"))) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant BRIDGEABLE_STORAGE_LOCATION =
0xe4e4f071f9b1db3b4b73cfb89f93997eef9d90809fdd474c473bf010d9361e00;
/*
Requirements for implementation:
1. _requireAuth() must be implemented by the inheriting contract
2. _requireNotAccountingPaused() must be implemented by the inheriting contract
3. _requireAllowed() must be implemented by the inheriting contract
4. _burn() must be implemented by the inheriting contract
*/
/**
* @dev Hook to require authorization for administrative actions
* @notice Must be implemented by inheriting contract
*/
function _requireAuth() internal view virtual;
/**
* @dev Hook to verify if accounting is not paused
* @notice Must be implemented by inheriting contract
*/
function _requireNotAccountingPaused() internal view virtual;
/**
* @dev Hook to verify if an address is allowed
* @param addr The address to check
* @notice Must be implemented by inheriting contract
*/
function _requireAllowed(address addr) internal view virtual;
/**
* @dev Hook to burn tokens
* @param from The address to burn tokens from
* @param amount The amount of tokens to burn
* @notice Must be implemented by inheriting contract
*/
function _burn(address from, uint256 amount) internal virtual;
/*
Provides the following to be used:
1. setChainIdSupport(uint256 chainId, bool supported) - sets support status for a specific chain ID, _requireAuth
2. isChainIdSupported(uint256 chainId) public view
3. bridge(uint256 amount,
address ethDestinationAddress,
string memory otherDestinationAddress,
uint256 chainId)
4. bridgeToBookEntry(uint256 amount)
*/
/**
* @dev Returns whether a chain ID is supported for bridging
* @param chainId The chain ID to check
* @return True if the chain ID is supported, false otherwise
*/
function isChainIdSupported(uint256 chainId) public view returns (bool) {
BridgeableStorage storage $ = _getBridgeableStorage();
return $.supportedChainIds[chainId];
}
/**
* @dev Sets support status for a specific chain ID
* @param chainId The chain ID to update
* @param supported Whether the chain ID should be supported
*/
function setChainIdSupport(uint256 chainId, bool supported) external virtual {
_requireAuth();
if (isChainIdSupported(chainId) == supported) revert BadArgsBridgeable();
if (chainId == block.chainid) revert BridgeChainIdDestinationNotSupported();
emit SetChainIdSupport({
chainId: chainId,
oldSupported: !supported,
newSupported: supported
});
_setChainIdSupport(chainId, supported);
}
/**
* @notice Burns tokens from the caller to bridge to another chain
* @dev If destination address on chainId isn't on allowlist, or chainID isn't supported, tokens burn to book entry.
* @dev chainId as 0 indicates wanting to burn tokens to book entry, for use through the Superstate UI.
* @param amount Amount of tokens to burn
* @param ethDestinationAddress ETH address to send to on another chain
* @param otherDestinationAddress Non-EVM addresses to send to on another chain
* @param chainId Numerical identifier of destination chain to send tokens to
*/
function bridge(
uint256 amount,
address ethDestinationAddress,
string memory otherDestinationAddress,
uint256 chainId
) public virtual {
_requireNotAccountingPaused();
_requireAllowed(msg.sender);
if (amount == 0) revert ZeroSuperstateTokensOutBridgeable();
if (ethDestinationAddress != address(0) && bytes(otherDestinationAddress).length != 0) {
revert TwoDestinationsInvalid();
}
if (chainId == 0 && (ethDestinationAddress != address(0) || bytes(otherDestinationAddress).length != 0)) {
revert OnchainDestinationSetForBridgeToBookEntry();
}
if (!isChainIdSupported(chainId)) revert BridgeChainIdDestinationNotSupported();
_burn(msg.sender, amount);
emit Bridge({
caller: msg.sender,
src: msg.sender,
amount: amount,
ethDestinationAddress: ethDestinationAddress,
otherDestinationAddress: otherDestinationAddress,
chainId: chainId
});
}
/**
* @dev Burns tokens from the caller to bridge to Superstate book entry
* @param amount Amount of tokens to burn
*/
function bridgeToBookEntry(uint256 amount) external virtual {
bridge({
amount: amount,
ethDestinationAddress: address(0),
otherDestinationAddress: string(new bytes(0)),
chainId: 0
});
}
/**
* @dev Returns the BridgeableStorage struct
* @return $ Storage pointer to the BridgeableStorage struct
*/
function _getBridgeableStorage() private pure returns (BridgeableStorage storage $) {
assembly {
$.slot := BRIDGEABLE_STORAGE_LOCATION
}
}
/**
* @dev Sets support status for a chain ID
* @param chainId The chain ID to update
* @param supported Whether the chain ID should be supported
*/
function _setChainIdSupport(uint256 chainId, bool supported) internal {
BridgeableStorage storage $ = _getBridgeableStorage();
$.supportedChainIds[chainId] = supported;
}
function __Bridgeable_init() internal {
// Support chainId 0 for book entry
_setChainIdSupport(0, true);
}
}
// lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
// lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
// lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/IERC20MetadataUpgradeable.sol
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}
// src/Allowlistable.sol
/**
* @title Allowlistable.sol
* @notice Abstract contract implementing allowlist functionality with ERC-7201 namespaced storage
* @dev Enables checking if addresses have permission to interact with the token
*/
abstract contract Allowlistable is IAllowlistable {
/**
* @dev Storage struct using ERC-7201 namespaced pattern
* @custom:storage-location erc7201:superstate.storage.allowlistable
*/
struct AllowlistableStorage {
address allowlist;
bool isPublicInstrument;
}
// keccak256(abi.encode(uint256(keccak256(bytes("superstate.storage.allowlistable"))) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ALLOWLISTABLE_STORAGE_LOCATION =
0x20f9d398b060d72baa41f68a4a570507058bbfd6bb960620e2b61718f38ded00;
/* Requirements for implementation:
1. No requirements needed after redesign
*/
/*
Provides the following to be used on inheritance:
1. allowlist() - returns the current allowlist contract address, view only
2. _setAllowlist(address _newAllowlist) - sets the allowlist contract address
3. isAddressAllowedForPrivateInstrument(address addr, string instrument) - checks if an address is allowed to interact with a private instrument/fund
4. isAddressAllowedForPublicInstrument(address addr) - checks if an address is allowed to interact with public instrument/equity
5. isPublicInstrument() - checks if the allowlist is used for public instrument
6. _setIsPublicInstrument(bool _isPublicInstrument) - sets whether the instrument is public or private
*/
/**
* @dev Returns the current allowlist contract
* @return The allowlist contract instance
*/
function allowlist() public view virtual returns (address) {
AllowlistableStorage storage $ = _getAllowlistableStorage();
return $.allowlist;
}
/**
* @dev Returns whether the instrument is public or private
* @return True if the instrument is public, false if private
*/
function isPublicInstrument() public view virtual returns (bool) {
AllowlistableStorage storage $ = _getAllowlistableStorage();
return $.isPublicInstrument;
}
/**
* @dev Sets the allowlist contract
* @param _newAllowlist The new allowlist contract to use
*/
function _setAllowlist(address _newAllowlist) internal {
if (_newAllowlist == address(0)) revert ZeroAddressNotAllowed();
AllowlistableStorage storage $ = _getAllowlistableStorage();
emit AllowlistUpdated(address($.allowlist), address(_newAllowlist));
$.allowlist = _newAllowlist;
}
/**
* @dev Sets whether the instrument is public or private
* @param _isPublicInstrument True if the instrument is public, false if private
*/
function _setIsPublicInstrument(bool _isPublicInstrument) internal {
AllowlistableStorage storage $ = _getAllowlistableStorage();
emit IsPublicInstrumentUpdated($.isPublicInstrument, _isPublicInstrument);
$.isPublicInstrument = _isPublicInstrument;
}
/**
* @dev Checks if an address is allowed to interact with a private instrument
* @param addr The address to check
* @param instrument The private instrument identifier (typically a symbol)
* @return True if the address is allowed, false otherwise
* @notice If allowlist is not set, returns false
*/
function isAddressAllowedForPrivateInstrument(address addr, string memory instrument) public view returns (bool) {
address _allowlist = allowlist();
if (_allowlist == address(0)) return false;
return IAllowlistAddressPermissions(_allowlist).isAddressAllowedForPrivateInstrument(addr, instrument);
}
/**
* @dev Checks if an address is allowed to interact with public instrument
* @param addr The address to check
* @return True if the address is allowed, false otherwise
*/
function isAddressAllowedForPublicInstrument(address addr) public view returns (bool) {
address _allowlist = allowlist();
if (_allowlist == address(0)) return false;
return IAllowlistAddressPermissions(_allowlist).isAddressAllowedForPublicInstrument(addr);
}
/**
* @dev Returns the AllowlistableStorage struct
* @return $ Storage pointer to the AllowlistableStorage struct
*/
function _getAllowlistableStorage() private pure returns (AllowlistableStorage storage $) {
assembly {
$.slot := ALLOWLISTABLE_STORAGE_LOCATION
}
}
/**
* @dev Checks if two addresses belong to the same entity
* @param addr1 The first address
* @param addr2 The second address
* @return True if both addresses belong to the same entity, false otherwise
*/
function isSameEntity(address addr1, address addr2) public view returns (bool) {
if (addr1 == addr2) return true; // Same address is always same entity
address _allowlist = allowlist();
if (_allowlist == address(0)) return false;
return IAllowlistAddressPermissions(_allowlist).addressEntityIds(addr1) ==
IAllowlistAddressPermissions(_allowlist).addressEntityIds(addr2);
}
/**
* @dev Requires that two addresses belong to the same entity, reverts otherwise
* @param addr1 The first address
* @param addr2 The second address
*/
function _requireSameEntity(address addr1, address addr2) virtual internal view {
if (!isSameEntity(addr1, addr2)) revert MismatchEntityIds();
}
/**
* @dev Initialize the allowlist during contract initialization
* @param _allowlist The allowlist contract to set
* @param _isPublicInstrument Whether the instrument is public (true) or private (false)
*/
function __Allowlistable_init(address _allowlist, bool _isPublicInstrument) internal {
_setAllowlist(_allowlist);
_setIsPublicInstrument(_isPublicInstrument);
}
}
// lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
// src/Permittable.sol
/**
* @title Permittable
* @notice Abstract contract implementing EIP-712 compliant permit functionality with ERC-7201 namespaced storage
* @dev Allows approval of token spending through signatures (EIP-2612)
* @dev Combining Nonces with Permits together as a unit
*/
abstract contract Permittable is IPermittable {
/**
* @dev Storage struct using ERC-7201 namespaced pattern
* @custom:storage-location erc7201:superstate.storage.permittable
*/
struct PermittableStorage {
/// @notice The next expected nonce for an address, for validating authorizations via signature
mapping(address => uint256) nonces;
}
// keccak256(abi.encode(uint256(keccak256(bytes("superstate.storage.permittable"))) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PERMITTABLE_STORAGE_LOCATION =
0x61eb30d84d4dfa1c5d0f066f60f5c357c05c83c2a042c60a480d47da76483800;
/* Requirements for implementation:
1. _approve() must be implemented by the inheriting contract
2. _name() must be implemented by the inheriting contract, referencing to token name to be used for domain separator
3. _version() must be implemented by the inheriting contract, referencing to contract version to be used for domain separator
*/
/**
* @dev Hook to approve token spending
* @param owner The token owner
* @param spender The token spender
* @param amount The amount of tokens to approve
* @notice Must be implemented by inheriting contract
*/
function _approve(address owner, address spender, uint256 amount) internal virtual;
/**
* @dev Hook to get the token name
* @notice Must be implemented by inheriting contract
* @return The name of the token
*/
function _name() internal view virtual returns (string memory);
/**
* @dev Hook to get the version
* @notice Must be implemented by inheriting contract
* @return The version string
*/
function _version() internal view virtual returns (string memory);
/*
Provides the following to be used on inheritance:
1. nonces(address owner) public view virtual returns (uint256)
2. permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
3. DOMAIN_SEPARATOR() public view virtual returns (bytes32)
*
*/
/**
* @dev Returns the current nonce for an address
* @param owner The address to get the nonce for
* @return The current nonce
*/
function nonces(address owner) public view virtual returns (uint256) {
PermittableStorage storage $ = _getPermittableStorage();
return $.nonces[owner];
}
/**
* @dev Sets approval amount for a spender via signature from signatory
* @param owner The address that signed the signature
* @param spender The address to authorize (or rescind authorization from)
* @param value Amount that `owner` is approving for `spender`
* @param deadline Expiration time for the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external virtual {
if (block.timestamp > deadline) revert SignatureExpired();
uint256 currentNonce = nonces(owner);
bytes32 structHash = keccak256(abi.encode(
AUTHORIZATION_TYPEHASH,
owner,
spender,
value,
currentNonce,
deadline
));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), structHash));
if (_isValidSignature(owner, digest, v, r, s)) {
_incrementNonce(owner);
_approve(owner, spender, value);
}
}
/**
* @dev Returns the domain separator used in the encoding of the signature for permit
* @return bytes32 The domain separator
*/
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_TYPEHASH,
keccak256(bytes(_name())),
keccak256(bytes(_version())),
block.chainid,
address(this)
)
);
}
/// @dev The EIP-712 typehash for authorization via permit
bytes32 internal constant AUTHORIZATION_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/// @dev The EIP-712 typehash for the contract's domain
bytes32 internal constant DOMAIN_TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/**
* @dev Returns the PermittableStorage struct
* @return $ Storage pointer to the PermittableStorage struct
*/
function _getPermittableStorage() private pure returns (PermittableStorage storage $) {
assembly {
$.slot := PERMITTABLE_STORAGE_LOCATION
}
}
/**
* @dev Increments the nonce for an address
* @param owner The address to increment the nonce for
*/
function _incrementNonce(address owner) internal {
PermittableStorage storage $ = _getPermittableStorage();
$.nonces[owner]++;
}
/**
* @dev Checks if a signature is valid
* @param signer The address that signed the signature
* @param digest The hashed message that is signed
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
* @return bool Whether the signature is valid
*/
function _isValidSignature(
address signer,
bytes32 digest,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (bool) {
(address recoveredSigner, ECDSA.RecoverError recoverError,) = ECDSA.tryRecover(digest, v, r, s);
if (recoverError == ECDSA.RecoverError.InvalidSignatureS) revert InvalidSignatureS();
if (recoverError == ECDSA.RecoverError.InvalidSignature) revert BadSignatory();
if (recoveredSigner != signer) revert BadSignatory();
return true;
}
function __Permittable_init() internal {}
}
// lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address i
Submitted on: 2025-09-25 20:52:42
Comments
Log in to comment.
No comments yet.