Source Code
Overview
MON Balance
MON Value
$0.00View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
KuruForwarder
Compiler Version
v0.8.30+commit.73712a01
Optimization Enabled:
Yes with 1000 runs
Other Settings:
prague EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
//SPDX-License-Identifier: BUSL-1.1
// ░█████░ ░█████▓ ███
// ███████ ▓███████▒ ███▒
// ███████ █████████▒ ███▒
// ███████ ██████████░ ███▒ ████ ████ ████ ▓████████ ░███░ ░███▓
// ░████▒██████▒░ ███▒ ████ ████ ████ ▓████████ ▒███▒ ░███▓
// ███▒ ▒████ ████ ████ ▓████ ▒███░ ░███▓
// ▓████▒███████▓ █████████ ████ ████ ▓███░ ▒███░ ░███▓
// ███████ ██████████▒ █████████▒ ████ ████ ▓███ ░███▒ ░███▒
// ███████ █████████▒ ███▒ ████▓ ▓███▒ ░████ ▓███ ████ ████░
// ███████ ▓███████▒ ███▒ ▒████ ███████████ ▓███ ░██████████▒
// ▒▓▓▓▓ ▒▓▓▓▓▒ ▓██ ███▒ ░█████░ ██▓ ▒████▒
pragma solidity ^0.8.20;
// ============ External Imports ============
import {ECDSA} from "solady/src/utils/ECDSA.sol";
import {EIP712} from "solady/src/utils/EIP712.sol";
import {Initializable} from "solady/src/utils/Initializable.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
import {UUPSUpgradeable} from "solady/src/utils/UUPSUpgradeable.sol";
// ============ Internal Interface Imports ============
import {IOrderBook} from "./interfaces/IOrderBook.sol";
import {KuruForwarderErrors} from "./libraries/Errors.sol";
contract KuruForwarder is EIP712, Initializable, Ownable, UUPSUpgradeable {
using ECDSA for bytes32;
error ExecutionFailed(bytes);
error PriceDependentRequestFailed(uint256 _currentPrice, uint256 _breakpointPrice);
error DeadlineExpired();
mapping(bytes4 => bool) public allowedInterface;
mapping(bytes32 => bool) public executedPriceDependentRequest;
struct ForwardRequest {
address from;
address market;
uint256 value;
uint256 nonce;
uint256 deadline;
bytes4 selector;
bytes data;
}
struct PriceDependentRequest {
address from;
address market;
uint256 price;
uint256 value;
uint256 nonce;
uint256 deadline;
bool isBelowPrice;
bytes4 selector;
bytes data;
}
struct CancelPriceDependentRequest {
address from;
uint256 nonce;
uint256 deadline;
}
struct MarginAccountRequest {
address from;
address marginAccount;
uint256 value;
uint256 nonce;
uint256 deadline;
bytes4 selector;
bytes data;
}
bytes32 private constant _FORWARD_TYPEHASH = keccak256(
"ForwardRequest(address from,address market,uint256 value,uint256 nonce,uint256 deadline,bytes4 selector,bytes data)"
);
bytes32 private constant _PRICE_DEPENDENT_TYPEHASH = keccak256(
"PriceDependentRequest(address from,address market,uint256 price,uint256 value,uint256 nonce,uint256 deadline,bool isBelowPrice,bytes4 selector,bytes data)"
);
bytes32 private constant _CANCEL_PRICE_DEPENDENT_TYPEHASH =
keccak256("CancelPriceDependentRequest(address from,uint256 nonce,uint256 deadline)");
bytes32 private constant _MARGIN_ACCOUNT_REQUEST_TYPEHASH = keccak256(
"MarginAccountRequest(address from,address marginAccount,uint256 value,uint256 nonce,uint256 deadline,bytes4 selector,bytes data)"
);
mapping(address => uint256) private _nonces;
constructor() {
_disableInitializers();
}
// ============ auth =================
function _guardInitializeOwner() internal pure override returns (bool guard) {
guard = true;
}
function _authorizeUpgrade(address) internal view override {
_checkOwner();
}
// ============ initializer =================
function initialize(address _owner, bytes4[] memory _allowedInterfaces) public initializer {
require(_owner != address(0), KuruForwarderErrors.ZeroAddressNotAllowed());
_initializeOwner(_owner);
for (uint256 i = 0; i < _allowedInterfaces.length; i++) {
allowedInterface[_allowedInterfaces[i]] = true;
}
}
// ============ owner functions =============
function setAllowedInterfaces(bytes4[] memory _allowedInterfaces) external onlyOwner {
for (uint256 i = 0; i < _allowedInterfaces.length; i++) {
allowedInterface[_allowedInterfaces[i]] = true;
}
}
function removeAllowedInterfaces(bytes4[] memory _allowedInterfaces) external onlyOwner {
for (uint256 i = 0; i < _allowedInterfaces.length; i++) {
allowedInterface[_allowedInterfaces[i]] = false;
}
}
function _domainNameAndVersionMayChange() internal pure override returns (bool result) {
return true;
}
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
name = "KuruForwarder";
version = "1.0.0";
}
function getNonce(address from) public view returns (uint256) {
return _nonces[from];
}
function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
require(block.timestamp <= req.deadline, DeadlineExpired());
address signer = _hashTypedData(
keccak256(
abi.encode(
_FORWARD_TYPEHASH,
req.from,
req.market,
req.value,
req.nonce,
req.deadline,
req.selector,
keccak256(req.data)
)
)
).recoverCalldata(signature);
return req.nonce >= _nonces[req.from] && signer == req.from;
}
//Does not check nonce because price dependent transaction nonces are not iterative
//executePriceDependent() has special checks for nonces
function verifyPriceDependent(PriceDependentRequest calldata req, bytes calldata signature)
public
view
returns (bool)
{
require(block.timestamp <= req.deadline, DeadlineExpired());
address signer = _hashTypedData(
keccak256(
abi.encode(
_PRICE_DEPENDENT_TYPEHASH,
req.from,
req.market,
req.price,
req.value,
req.nonce,
req.deadline,
req.isBelowPrice,
req.selector,
keccak256(req.data)
)
)
).recoverCalldata(signature);
require(
!executedPriceDependentRequest[keccak256(abi.encodePacked(req.from, req.nonce))],
KuruForwarderErrors.NonceAlreadyUsed()
);
return signer == req.from;
}
function verifyCancelPriceDependent(CancelPriceDependentRequest calldata req, bytes calldata signature)
public
view
returns (bool)
{
require(block.timestamp <= req.deadline, DeadlineExpired());
address signer = _hashTypedData(
keccak256(abi.encode(_CANCEL_PRICE_DEPENDENT_TYPEHASH, req.from, req.nonce, req.deadline))
).recoverCalldata(signature);
require(
!executedPriceDependentRequest[keccak256(abi.encodePacked(req.from, req.nonce))],
KuruForwarderErrors.NonceAlreadyUsed()
);
return signer == req.from;
}
function verifyMarginAccountRequest(MarginAccountRequest calldata req, bytes calldata signature)
public
view
returns (bool)
{
require(block.timestamp <= req.deadline, DeadlineExpired());
address signer = _hashTypedData(
keccak256(
abi.encode(
_MARGIN_ACCOUNT_REQUEST_TYPEHASH,
req.from,
req.marginAccount,
req.value,
req.nonce,
req.deadline,
req.selector,
keccak256(req.data)
)
)
).recoverCalldata(signature);
return req.nonce >= _nonces[req.from] && signer == req.from;
}
function executeMarginAccountRequest(MarginAccountRequest calldata req, bytes calldata signature)
public
payable
returns (bytes memory)
{
require(verifyMarginAccountRequest(req, signature), KuruForwarderErrors.SignatureMismatch());
require(msg.value >= req.value, KuruForwarderErrors.InsufficientValue());
require(allowedInterface[req.selector], KuruForwarderErrors.InterfaceNotAllowed());
_nonces[req.from] = req.nonce + 1;
(bool success, bytes memory returndata) =
req.marginAccount.call{value: req.value}(abi.encodePacked(req.selector, req.data, req.from));
if (!success) {
revert ExecutionFailed(returndata);
}
return returndata;
}
function execute(ForwardRequest calldata req, bytes calldata signature) public payable returns (bytes memory) {
require(verify(req, signature), KuruForwarderErrors.SignatureMismatch());
require(msg.value >= req.value, KuruForwarderErrors.InsufficientValue());
require(allowedInterface[req.selector], KuruForwarderErrors.InterfaceNotAllowed());
_nonces[req.from] = req.nonce + 1;
(bool success, bytes memory returndata) =
req.market.call{value: req.value}(abi.encodePacked(req.selector, req.data, req.from));
if (!success) {
revert ExecutionFailed(returndata);
}
return returndata;
}
function executePriceDependent(PriceDependentRequest calldata req, bytes calldata signature)
public
payable
returns (bytes memory)
{
require(verifyPriceDependent(req, signature), KuruForwarderErrors.SignatureMismatch());
require(msg.value >= req.value, KuruForwarderErrors.InsufficientValue());
require(allowedInterface[req.selector], KuruForwarderErrors.InterfaceNotAllowed());
executedPriceDependentRequest[keccak256(abi.encodePacked(req.from, req.nonce))] = true;
(uint256 _currentBidPrice,) = IOrderBook(req.market).bestBidAsk();
require(
(req.isBelowPrice && req.price <= _currentBidPrice) || (!req.isBelowPrice && req.price >= _currentBidPrice),
PriceDependentRequestFailed(_currentBidPrice, req.price)
);
(bool success, bytes memory returndata) =
req.market.call{value: req.value}(abi.encodePacked(req.selector, req.data, req.from));
if (!success) {
revert ExecutionFailed(returndata);
}
return returndata;
}
function cancelPriceDependent(CancelPriceDependentRequest calldata req, bytes calldata signature) public {
require(verifyCancelPriceDependent(req, signature), KuruForwarderErrors.SignatureMismatch());
executedPriceDependentRequest[keccak256(abi.encodePacked(req.from, req.nonce))] = true;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Gas optimized ECDSA wrapper.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol)
///
/// @dev Note:
/// - The recovery functions use the ecrecover precompile (0x1).
/// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure.
/// This is for more safety by default.
/// Use the `tryRecover` variants if you need to get the zero address back
/// upon recovery failure instead.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
/// This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT directly use signatures as unique identifiers:
/// - The recovery operations do NOT check if a signature is non-malleable.
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
/// EIP-712 also enables readable signing of typed data for better user safety.
/// - If you need a unique hash from a signature, please use the `canonicalHash` functions.
library ECDSA {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The order of the secp256k1 elliptic curve.
uint256 internal constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
/// @dev `N/2 + 1`. Used for checking the malleability of the signature.
uint256 private constant _HALF_N_PLUS_1 =
0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The signature is invalid.
error InvalidSignature();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RECOVERY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
function recover(bytes32 hash, bytes memory signature) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
for { let m := mload(0x40) } 1 {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
} {
switch mload(signature)
case 64 {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
}
case 65 {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
}
default { continue }
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if returndatasize() { break }
}
}
}
/// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
function recoverCalldata(bytes32 hash, bytes calldata signature)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
for { let m := mload(0x40) } 1 {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
} {
switch signature.length
case 64 {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
}
case 65 {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`.
}
default { continue }
mstore(0x00, hash)
result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if returndatasize() { break }
}
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the EIP-2098 short form signature defined by `r` and `vs`.
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r)
mstore(0x60, shr(1, shl(1, vs))) // `s`.
result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the signature defined by `v`, `r`, `s`.
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, hash)
mstore(0x20, and(v, 0xff))
mstore(0x40, r)
mstore(0x60, s)
result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(returndatasize()) {
mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* TRY-RECOVER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// WARNING!
// These functions will NOT revert upon recovery failure.
// Instead, they will return the zero address upon recovery failure.
// It is critical that the returned address is NEVER compared against
// a zero address (e.g. an uninitialized address variable).
/// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
function tryRecover(bytes32 hash, bytes memory signature)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
for { let m := mload(0x40) } 1 {} {
switch mload(signature)
case 64 {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
}
case 65 {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
}
default { break }
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
mstore(0x60, 0) // Restore the zero slot.
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
function tryRecoverCalldata(bytes32 hash, bytes calldata signature)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
for { let m := mload(0x40) } 1 {} {
switch signature.length
case 64 {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
}
case 65 {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`.
}
default { break }
mstore(0x00, hash)
pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
mstore(0x60, 0) // Restore the zero slot.
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the EIP-2098 short form signature defined by `r` and `vs`.
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r)
mstore(0x60, shr(1, shl(1, vs))) // `s`.
pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
mstore(0x60, 0) // Restore the zero slot.
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the signature defined by `v`, `r`, `s`.
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, hash)
mstore(0x20, and(v, 0xff))
mstore(0x40, r)
mstore(0x60, s)
pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
mstore(0x60, 0) // Restore the zero slot.
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(xor(0x60, returndatasize()))
mstore(0x40, m) // Restore the free memory pointer.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, hash) // Store into scratch space for keccak256.
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign)
/// JSON-RPC method as part of EIP-191.
/// Note: Supports lengths of `s` up to 999999 bytes.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let sLength := mload(s)
let o := 0x20
mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
for { let temp := sLength } 1 {} {
o := sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CANONICAL HASH FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// The following functions return the hash of the signature in its canonicalized format,
// which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28.
// If `s` is greater than `N / 2` then it will be converted to `N - s`
// and the `v` value will be flipped.
// If the signature has an invalid length, or if `v` is invalid,
// a uniquely corrupt hash will be returned.
// These functions are useful for "poor-mans-VRF".
/// @dev Returns the canonical hash of `signature`.
function canonicalHash(bytes memory signature) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let l := mload(signature)
for {} 1 {} {
mstore(0x00, mload(add(signature, 0x20))) // `r`.
let s := mload(add(signature, 0x40))
let v := mload(add(signature, 0x41))
if eq(l, 64) {
v := add(shr(255, s), 27)
s := shr(1, shl(1, s))
}
if iszero(lt(s, _HALF_N_PLUS_1)) {
v := xor(v, 7)
s := sub(N, s)
}
mstore(0x21, v)
mstore(0x20, s)
result := keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
break
}
// If the length is neither 64 nor 65, return a uniquely corrupted hash.
if iszero(lt(sub(l, 64), 2)) {
// `bytes4(keccak256("InvalidSignatureLength"))`.
result := xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2)
}
}
}
/// @dev Returns the canonical hash of `signature`.
function canonicalHashCalldata(bytes calldata signature)
internal
pure
returns (bytes32 result)
{
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
mstore(0x00, calldataload(signature.offset)) // `r`.
let s := calldataload(add(signature.offset, 0x20))
let v := calldataload(add(signature.offset, 0x21))
if eq(signature.length, 64) {
v := add(shr(255, s), 27)
s := shr(1, shl(1, s))
}
if iszero(lt(s, _HALF_N_PLUS_1)) {
v := xor(v, 7)
s := sub(N, s)
}
mstore(0x21, v)
mstore(0x20, s)
result := keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
break
}
// If the length is neither 64 nor 65, return a uniquely corrupted hash.
if iszero(lt(sub(signature.length, 64), 2)) {
calldatacopy(mload(0x40), signature.offset, signature.length)
// `bytes4(keccak256("InvalidSignatureLength"))`.
result := xor(keccak256(mload(0x40), signature.length), 0xd62f1ab2)
}
}
}
/// @dev Returns the canonical hash of `signature`.
function canonicalHash(bytes32 r, bytes32 vs) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, r) // `r`.
let v := add(shr(255, vs), 27)
let s := shr(1, shl(1, vs))
mstore(0x21, v)
mstore(0x20, s)
result := keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the canonical hash of `signature`.
function canonicalHash(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, r) // `r`.
if iszero(lt(s, _HALF_N_PLUS_1)) {
v := xor(v, 7)
s := sub(N, s)
}
mstore(0x21, v)
mstore(0x20, s)
result := keccak256(0x00, 0x41)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Contract for EIP-712 typed structured data hashing and signing.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol)
/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol)
///
/// @dev Note, this implementation:
/// - Uses `address(this)` for the `verifyingContract` field.
/// - Does NOT use the optional EIP-712 salt.
/// - Does NOT use any EIP-712 extensions.
/// This is for simplicity and to save gas.
/// If you need to customize, please fork / modify accordingly.
abstract contract EIP712 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS AND IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 internal constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev `keccak256("EIP712Domain(string name,string version,address verifyingContract)")`.
/// This is only used in `_hashTypedDataSansChainId`.
bytes32 internal constant _DOMAIN_TYPEHASH_SANS_CHAIN_ID =
0x91ab3d17e3a50a9d89e63fd30b92be7f5336b03b287bb946787a83a9d62a2766;
/// @dev `keccak256("EIP712Domain(string name,string version)")`.
/// This is only used in `_hashTypedDataSansChainIdAndVerifyingContract`.
bytes32 internal constant _DOMAIN_TYPEHASH_SANS_CHAIN_ID_AND_VERIFYING_CONTRACT =
0xb03948446334eb9b2196d5eb166f69b9d49403eb4a12f36de8d3f9f3cb8e15c3;
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId)")`.
/// This is only used in `_hashTypedDataSansVerifyingContract`.
bytes32 internal constant _DOMAIN_TYPEHASH_SANS_VERIFYING_CONTRACT =
0xc2f8787176b8ac6bf7215b4adcc1e069bf4ab82d9ab1df05a57a91d425935b6e;
uint256 private immutable _cachedThis;
uint256 private immutable _cachedChainId;
bytes32 private immutable _cachedNameHash;
bytes32 private immutable _cachedVersionHash;
bytes32 private immutable _cachedDomainSeparator;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Cache the hashes for cheaper runtime gas costs.
/// In the case of upgradeable contracts (i.e. proxies),
/// or if the chain id changes due to a hard fork,
/// the domain separator will be seamlessly calculated on-the-fly.
constructor() {
_cachedThis = uint256(uint160(address(this)));
_cachedChainId = block.chainid;
string memory name;
string memory version;
if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion();
bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name));
bytes32 versionHash =
_domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version));
_cachedNameHash = nameHash;
_cachedVersionHash = versionHash;
bytes32 separator;
if (!_domainNameAndVersionMayChange()) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
_cachedDomainSeparator = separator;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* FUNCTIONS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Please override this function to return the domain name and version.
/// ```
/// function _domainNameAndVersion()
/// internal
/// pure
/// virtual
/// returns (string memory name, string memory version)
/// {
/// name = "Solady";
/// version = "1";
/// }
/// ```
///
/// Note: If the returned result may change after the contract has been deployed,
/// you must override `_domainNameAndVersionMayChange()` to return true.
function _domainNameAndVersion()
internal
view
virtual
returns (string memory name, string memory version);
/// @dev Returns if `_domainNameAndVersion()` may change
/// after the contract has been deployed (i.e. after the constructor).
/// Default: false.
function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _domainSeparator() internal view virtual returns (bytes32 separator) {
if (_domainNameAndVersionMayChange()) {
separator = _buildDomainSeparator();
} else {
separator = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator();
}
}
/// @dev Returns the hash of the fully encoded EIP-712 message for this domain,
/// given `structHash`, as defined in
/// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct.
///
/// The hash can be used together with {ECDSA-recover} to obtain the signer of a message:
/// ```
/// bytes32 digest = _hashTypedData(keccak256(abi.encode(
/// keccak256("Mail(address to,string contents)"),
/// mailTo,
/// keccak256(bytes(mailContents))
/// )));
/// address signer = ECDSA.recover(digest, signature);
/// ```
function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) {
// We will use `digest` to store the domain separator to save a bit of gas.
if (_domainNameAndVersionMayChange()) {
digest = _buildDomainSeparator();
} else {
digest = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator();
}
/// @solidity memory-safe-assembly
assembly {
// Compute the digest.
mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
mstore(0x1a, digest) // Store the domain separator.
mstore(0x3a, structHash) // Store the struct hash.
digest := keccak256(0x18, 0x42)
// Restore the part of the free memory slot that was overwritten.
mstore(0x3a, 0)
}
}
/// @dev Variant of `_hashTypedData` that excludes the chain ID.
/// Included for the niche use case of cross-chain workflows.
function _hashTypedDataSansChainId(bytes32 structHash)
internal
view
virtual
returns (bytes32 digest)
{
(string memory name, string memory version) = _domainNameAndVersion();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(0x00, _DOMAIN_TYPEHASH_SANS_CHAIN_ID)
mstore(0x20, keccak256(add(name, 0x20), mload(name)))
mstore(0x40, keccak256(add(version, 0x20), mload(version)))
mstore(0x60, address())
// Compute the digest.
mstore(0x20, keccak256(0x00, 0x80)) // Store the domain separator.
mstore(0x00, 0x1901) // Store "\x19\x01".
mstore(0x40, structHash) // Store the struct hash.
digest := keccak256(0x1e, 0x42)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Variant of `_hashTypedData` that excludes the chain ID and verifying contract.
/// Included for the niche use case of cross-chain and multi-verifier workflows.
function _hashTypedDataSansChainIdAndVerifyingContract(bytes32 structHash)
internal
view
virtual
returns (bytes32 digest)
{
(string memory name, string memory version) = _domainNameAndVersion();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(0x00, _DOMAIN_TYPEHASH_SANS_CHAIN_ID_AND_VERIFYING_CONTRACT)
mstore(0x20, keccak256(add(name, 0x20), mload(name)))
mstore(0x40, keccak256(add(version, 0x20), mload(version)))
// Compute the digest.
mstore(0x20, keccak256(0x00, 0x60)) // Store the domain separator.
mstore(0x00, 0x1901) // Store "\x19\x01".
mstore(0x40, structHash) // Store the struct hash.
digest := keccak256(0x1e, 0x42)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Variant of `_hashTypedData` that excludes the chain ID and verifying contract.
/// Included for the niche use case of multi-verifier workflows.
function _hashTypedDataSansVerifyingContract(bytes32 structHash)
internal
view
virtual
returns (bytes32 digest)
{
(string memory name, string memory version) = _domainNameAndVersion();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(0x00, _DOMAIN_TYPEHASH_SANS_VERIFYING_CONTRACT)
mstore(0x20, keccak256(add(name, 0x20), mload(name)))
mstore(0x40, keccak256(add(version, 0x20), mload(version)))
mstore(0x60, chainid())
// Compute the digest.
mstore(0x20, keccak256(0x00, 0x80)) // Store the domain separator.
mstore(0x00, 0x1901) // Store "\x19\x01".
mstore(0x40, structHash) // Store the struct hash.
digest := keccak256(0x1e, 0x42)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-5267 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev See: https://eips.ethereum.org/EIPS/eip-5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
fields = hex"0f"; // `0b01111`.
(name, version) = _domainNameAndVersion();
chainId = block.chainid;
verifyingContract = address(this);
salt = salt; // `bytes32(0)`.
extensions = extensions; // `new uint256[](0)`.
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _buildDomainSeparator() private view returns (bytes32 separator) {
// We will use `separator` to store the name hash to save a bit of gas.
bytes32 versionHash;
if (_domainNameAndVersionMayChange()) {
(string memory name, string memory version) = _domainNameAndVersion();
separator = keccak256(bytes(name));
versionHash = keccak256(bytes(version));
} else {
separator = _cachedNameHash;
versionHash = _cachedVersionHash;
}
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), separator) // Name hash.
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
/// @dev Returns if the cached domain separator has been invalidated.
function _cachedDomainSeparatorInvalidated() private view returns (bool result) {
uint256 cachedChainId = _cachedChainId;
uint256 cachedThis = _cachedThis;
/// @solidity memory-safe-assembly
assembly {
result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis)))
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Initializable mixin for the upgradeable contracts.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Initializable.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/utils/Initializable.sol)
abstract contract Initializable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The contract is already initialized.
error InvalidInitialization();
/// @dev The contract is not initializing.
error NotInitializing();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Triggered when the contract has been initialized.
event Initialized(uint64 version);
/// @dev `keccak256(bytes("Initialized(uint64)"))`.
bytes32 private constant _INITIALIZED_EVENT_SIGNATURE =
0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The default initializable slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_INITIALIZABLE_SLOT")))))`.
///
/// Bits Layout:
/// - [0] `initializing`
/// - [1..64] `initializedVersion`
bytes32 private constant _INITIALIZABLE_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf601132;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
constructor() {
// Construction time check to ensure that `_initializableSlot()` is not
// overridden to zero. Will be optimized away if there is no revert.
require(_initializableSlot() != bytes32(0));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return a non-zero custom storage slot if required.
function _initializableSlot() internal pure virtual returns (bytes32) {
return _INITIALIZABLE_SLOT;
}
/// @dev Guards an initializer function so that it can be invoked at most once.
///
/// You can guard a function with `onlyInitializing` such that it can be called
/// through a function guarded with `initializer`.
///
/// This is similar to `reinitializer(1)`, except that in the context of a constructor,
/// an `initializer` guarded function can be invoked multiple times.
/// This can be useful during testing and is not expected to be used in production.
///
/// Emits an {Initialized} event.
modifier initializer() virtual {
bytes32 s = _initializableSlot();
/// @solidity memory-safe-assembly
assembly {
let i := sload(s)
// Set `initializing` to 1, `initializedVersion` to 1.
sstore(s, 3)
// If `!(initializing == 0 && initializedVersion == 0)`.
if i {
// If `!(address(this).code.length == 0 && initializedVersion == 1)`.
if iszero(lt(extcodesize(address()), eq(shr(1, i), 1))) {
mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`.
revert(0x1c, 0x04)
}
s := shl(shl(255, i), s) // Skip initializing if `initializing == 1`.
}
}
_;
/// @solidity memory-safe-assembly
assembly {
if s {
// Set `initializing` to 0, `initializedVersion` to 1.
sstore(s, 2)
// Emit the {Initialized} event.
mstore(0x20, 1)
log1(0x20, 0x20, _INITIALIZED_EVENT_SIGNATURE)
}
}
}
/// @dev Guards a reinitializer function so that it can be invoked at most once.
///
/// You can guard a function with `onlyInitializing` such that it can be called
/// through a function guarded with `reinitializer`.
///
/// Emits an {Initialized} event.
modifier reinitializer(uint64 version) virtual {
bytes32 s = _initializableSlot();
/// @solidity memory-safe-assembly
assembly {
// Clean upper bits, and shift left by 1 to make space for the initializing bit.
version := shl(1, and(version, 0xffffffffffffffff))
let i := sload(s)
// If `initializing == 1 || initializedVersion >= version`.
if iszero(lt(and(i, 1), lt(i, version))) {
mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`.
revert(0x1c, 0x04)
}
// Set `initializing` to 1, `initializedVersion` to `version`.
sstore(s, or(1, version))
}
_;
/// @solidity memory-safe-assembly
assembly {
// Set `initializing` to 0, `initializedVersion` to `version`.
sstore(s, version)
// Emit the {Initialized} event.
mstore(0x20, shr(1, version))
log1(0x20, 0x20, _INITIALIZED_EVENT_SIGNATURE)
}
}
/// @dev Guards a function such that it can only be called in the scope
/// of a function guarded with `initializer` or `reinitializer`.
modifier onlyInitializing() virtual {
_checkInitializing();
_;
}
/// @dev Reverts if the contract is not initializing.
function _checkInitializing() internal view virtual {
bytes32 s = _initializableSlot();
/// @solidity memory-safe-assembly
assembly {
if iszero(and(1, sload(s))) {
mstore(0x00, 0xd7e6bcf8) // `NotInitializing()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Locks any future initializations by setting the initialized version to `2**64 - 1`.
///
/// Calling this in the constructor will prevent the contract from being initialized
/// or reinitialized. 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 called.
function _disableInitializers() internal virtual {
bytes32 s = _initializableSlot();
/// @solidity memory-safe-assembly
assembly {
let i := sload(s)
if and(i, 1) {
mstore(0x00, 0xf92ee8a9) // `InvalidInitialization()`.
revert(0x1c, 0x04)
}
let uint64max := 0xffffffffffffffff
if iszero(eq(shr(1, i), uint64max)) {
// Set `initializing` to 0, `initializedVersion` to `2**64 - 1`.
sstore(s, shl(1, uint64max))
// Emit the {Initialized} event.
mstore(0x20, uint64max)
log1(0x20, 0x20, _INITIALIZED_EVENT_SIGNATURE)
}
}
}
/// @dev Returns the highest version that has been initialized.
function _getInitializedVersion() internal view virtual returns (uint64 version) {
bytes32 s = _initializableSlot();
/// @solidity memory-safe-assembly
assembly {
version := shr(1, sload(s))
}
}
/// @dev Returns whether the contract is currently initializing.
function _isInitializing() internal view virtual returns (bool result) {
bytes32 s = _initializableSlot();
/// @solidity memory-safe-assembly
assembly {
result := and(1, sload(s))
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {CallContextChecker} from "./CallContextChecker.sol";
/// @notice UUPS proxy mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol)
/// @author Modified from OpenZeppelin
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol)
///
/// @dev Note:
/// - This implementation is intended to be used with ERC1967 proxies.
/// See: `LibClone.deployERC1967` and related functions.
/// - This implementation is NOT compatible with legacy OpenZeppelin proxies
/// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`.
abstract contract UUPSUpgradeable is CallContextChecker {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The upgrade failed.
error UpgradeFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when the proxy's implementation is upgraded.
event Upgraded(address indexed implementation);
/// @dev `keccak256(bytes("Upgraded(address)"))`.
uint256 private constant _UPGRADED_EVENT_SIGNATURE =
0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ERC-1967 storage slot for the implementation in the proxy.
/// `uint256(keccak256("eip1967.proxy.implementation")) - 1`.
bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* UUPS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Please override this function to check if `msg.sender` is authorized
/// to upgrade the proxy to `newImplementation`, reverting if not.
/// ```
/// function _authorizeUpgrade(address) internal override onlyOwner {}
/// ```
function _authorizeUpgrade(address newImplementation) internal virtual;
/// @dev Returns the storage slot used by the implementation,
/// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822).
///
/// Note: The `notDelegated` modifier prevents accidental upgrades to
/// an implementation that is a proxy contract.
function proxiableUUID() public view virtual notDelegated returns (bytes32) {
// This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967.
return _ERC1967_IMPLEMENTATION_SLOT;
}
/// @dev Upgrades the proxy's implementation to `newImplementation`.
/// Emits a {Upgraded} event.
///
/// Note: Passing in empty `data` skips the delegatecall to `newImplementation`.
function upgradeToAndCall(address newImplementation, bytes calldata data)
public
payable
virtual
onlyProxy
{
_authorizeUpgrade(newImplementation);
/// @solidity memory-safe-assembly
assembly {
newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits.
mstore(0x00, returndatasize())
mstore(0x01, 0x52d1902d) // `proxiableUUID()`.
let s := _ERC1967_IMPLEMENTATION_SLOT
// Check if `newImplementation` implements `proxiableUUID` correctly.
if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) {
mstore(0x01, 0x55299b49) // `UpgradeFailed()`.
revert(0x1d, 0x04)
}
// Emit the {Upgraded} event.
log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation)
sstore(s, newImplementation) // Updates the implementation.
// Perform a delegatecall to `newImplementation` if `data` is non-empty.
if data.length {
// Forwards the `data` to `newImplementation` via delegatecall.
let m := mload(0x40)
calldatacopy(m, data.offset, data.length)
if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00))
{
// Bubble up the revert if the call reverts.
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
}
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.20;
interface IOrderBook {
enum OrderBookType {
NO_NATIVE,
NATIVE_IN_BASE,
NATIVE_IN_QUOTE
}
enum OrderStatus {
ACTIVE,
FILLED,
CANCELED
}
enum MarketState {
ACTIVE,
SOFT_PAUSED,
HARD_PAUSED
}
struct Order {
address ownerAddress;
uint96 size;
uint40 prev;
uint40 next;
uint40 flippedId;
uint32 price;
uint32 flippedPrice;
bool isBuy;
}
event MarketStateUpdated(MarketState previousState, MarketState newState);
/**
* @dev Emitted when a new order is created.
* @param orderId Unique identifier for the newly created order.
* @param owner Address of the user who created the order.
* @param price Price point of the order in the specified precision.
* @param size Size of the order in the specified precision.
* @param isBuy Boolean indicating if the order is a buy (true) or sell (false) order.
*/
event OrderCreated(uint40 orderId, address owner, uint96 size, uint32 price, bool isBuy);
/**
* @dev Emitted when a flip order is created
* @param orderId Unique identifier for the newly created order.
* @param flippedId Unique identifier for the flipped order.
* @param owner Address of the user who created the order.
* @param size Size of the order in the specified precision.
* @param price Price point of the order in the specified precision.
* @param flippedPrice Price point of the flipped order in the specified precision.
* @param isBuy Boolean indicating if the order is a buy (true) or sell (false) order.
*/
event FlipOrderCreated(
uint40 orderId, uint40 flippedId, address owner, uint96 size, uint32 price, uint32 flippedPrice, bool isBuy
);
/**
* @dev Emitted when a flip order is partially/completely filled and it results in a new order
* @param orderId Unique identifier for the newly created order.
* @param flippedId Unique identifier for the flipped order.
* @param owner Address of the user who created the order.
* @param size Size of the order in the specified precision.
* @param price Price point of the order in the specified precision.
* @param flippedPrice Price point of the flipped order in the specified precision.
* @param isBuy Boolean indicating if the order is a buy (true) or sell (false) order.
*/
event FlippedOrderCreated(
uint40 orderId, uint40 flippedId, address owner, uint96 size, uint32 price, uint32 flippedPrice, bool isBuy
);
/**
* @dev Emitted when a flip order is updated
* @param orderId Unique identifier for the order.
* @param size Size of the order in the specified precision.
*/
event FlipOrderUpdated(uint40 orderId, uint96 size);
/**
* @dev Emitted when one or more flip orders are canceled
* @param orderIds Array of order identifiers that were canceled.
* @param owner Address of the user who canceled the orders.
*/
event FlipOrdersCanceled(uint40[] orderIds, address owner);
/**
* @dev Emitted when one or more orders are completed or canceled.
* @param orderId Array of order identifiers that were completed or canceled.
*/
event OrdersCanceled(uint40[] orderId, address owner);
/**
* @dev Emitted for each cancel
*/
event OrderCanceled(uint40 orderId, address owner, uint32 price, uint96 size, bool isBuy);
/**
* @dev Emitted when the vault params are updated
* @param _vaultAskOrderSize Size of the vault ask order
* @param _vaultAskPartiallyFilledSize Size of the vault ask partially filled order
* @param _vaultBidOrderSize Size of the vault bid order
* @param _vaultBidPartiallyFilledSize Size of the vault bid partially filled order
* @param _askPrice The vault best ask price
* @param _bidPrice The vault best bid price
*/
event VaultParamsUpdated(
uint96 _vaultAskOrderSize,
uint96 _vaultAskPartiallyFilledSize,
uint96 _vaultBidOrderSize,
uint96 _vaultBidPartiallyFilledSize,
uint256 _askPrice,
uint256 _bidPrice
);
/**
*
* @dev Emitted when a trade goes through.
* @param orderId Order Id of the order that was filled.
* PS. All data regarding the original order can be found out from the order ID
* @param updatedSize New size of the order
* @param takerAddress Address of the taker.
* @param filledSize Size taken by the taker.
*/
event Trade(
uint40 orderId,
address makerAddress,
bool isBuy,
uint256 price,
uint96 updatedSize,
address takerAddress,
address txOrigin,
uint96 filledSize
);
function initialize(
address _factory,
OrderBookType _type,
address _baseAssetAddress,
uint256 _baseAssetDecimals,
address _quoteAssetAddress,
uint256 _quoteAssetDecimals,
address _marginAccountAddress,
uint96 _sizePrecision,
uint32 _pricePrecision,
uint32 _tickSize,
uint96 _minSize,
uint96 _maxSize,
uint256 _takerFeeBps,
uint256 _makerFeeBps,
address _kuruAmmVault,
uint96 _kuruAmmSpread,
address __trustedForwarder
) external;
function toggleMarket(MarketState _state) external;
function addBuyOrder(uint32 _price, uint96 size, bool _postOnly) external;
function addFlipBuyOrder(uint32 _price, uint32 _flippedPrice, uint96 _size, bool _provisionOrRevert) external;
function addSellOrder(uint32 _price, uint96 size, bool _postOnly) external;
function addFlipSellOrder(uint32 _price, uint32 _flippedPrice, uint96 _size, bool _provisionOrRevert) external;
function batchCancelOrders(uint40[] calldata _orderIds) external;
function batchCancelFlipOrders(uint40[] calldata _orderIds) external;
function batchUpdate(
uint32[] calldata buyPrices,
uint96[] calldata buySizes,
uint32[] calldata sellPrices,
uint96[] calldata sellSizes,
uint40[] calldata orderIdsToCancel,
bool postOnly
) external;
function placeAndExecuteMarketBuy(uint96 _quoteAmount, uint256 _minAmountOut, bool _isMargin, bool _isFillOrKill)
external
payable
returns (uint256);
function placeAndExecuteMarketSell(uint96 _size, uint256 _minAmountOut, bool _isMargin, bool _isFillOrKill)
external
payable
returns (uint256);
function bestBidAsk() external view returns (uint256, uint256);
function updateVaultOrdSz(
uint96 _vaultAskOrderSize,
uint96 _vaultBidOrderSize,
uint256 _askPrice,
uint256 _bidPrice,
bool _nullifyPartialFills
) external;
function getMarketParams()
external
view
returns (uint32, uint96, address, uint256, address, uint256, uint32, uint96, uint96, uint256, uint256);
function getVaultParams()
external
view
returns (address, uint256, uint96, uint256, uint96, uint96, uint96, uint96);
function vaultAskOrderSize() external view returns (uint96);
function vaultBestAsk() external view returns (uint256);
}//SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.20;
library OrderBookErrors {
/**
* @dev Thrown when a user is not the owner and tries to execute a privileged function
*/
error Unauthorized();
/**
* @dev Thrown when a market is paused and a user tries to execute an action or if the owner passes an already existing market state for toggling
*/
error MarketStateError();
/**
* @dev Thrown when maker fee passed to initializer is too high/invalid
*/
error MarketFeeError();
/**
* @dev Thrown when minSize = 0 or maxSize < minSize
*/
error MarketSizeError();
/**
* @dev Thrown when available size is rounded down to zero
*/
error VaultSizeZero();
/**
* @dev Thrown when Kuru AMM Vault spread passed to initializer is too high or too low or is not a multiple of 10
*/
error InvalidSpread();
/**
* @dev Thrown when the inputted price while adding an order is invalid
*/
error PriceError();
/**
* @dev Thrown when vault initialization price crosses the book
*/
error VaultInitializationPriceCrossesBook();
/**
* @dev Thrown when the size inputted is invalid, i.e, < minSize or > maxSize
*/
error SizeError();
/**
* @dev Thrown when price is not divisible by tick
*/
error TickSizeError();
/**
* @dev Thrown when a post only order gets filled
*/
error PostOnlyError();
/**
* @dev Thrown when a flip order matches with a price
*/
error ProvisionError();
/**
* @dev Thrown when a non-owner tries to execute a privileged function, i.e, if non owner tries to pause/unpause a market or
* if a user tries to cancel an order that they did not place
*/
error OnlyOwnerAllowedError();
/**
* @dev Thrown when wrong interface is called for a cancel
*/
error WrongOrderTypeCancel();
/**
* @dev Thrown when cancelOrder is called on an order which is already filled or cancelled
*/
error OrderAlreadyFilledOrCancelled();
/**
* @dev Thrown when length mismatch occurs between inputted arrays
*/
error LengthMismatch();
/**
* @dev Thrown when msg.value is insufficient in market orders
*/
error NativeAssetInsufficient();
/**
* @dev Thrown when msg.value is surplus in market orders
*/
error NativeAssetSurplus();
/**
* @dev Thrown when msg.value is greater than 0 when native assets are not required
*/
error NativeAssetNotRequired();
/**
* @dev Thrown when native asset transfer fails
*/
error NativeAssetTransferFail();
/**
* @dev Thrown when IOC orders do not get filled by the market
*/
error InsufficientLiquidity();
/**
* @dev Throws when slippage is exceeded in market orders
*/
error SlippageExceeded();
/**
* @dev Thrown when quote size in uint32 overflows
*/
error TooMuchSizeFilled();
/**
* @dev Thrown when safe transfer from fails
*/
error TransferFromFailed();
/**
* @dev Thrown when the call is not made by the vault
*/
error OnlyVaultAllowed();
/**
* @dev Thrown when safe cast to uint96 fails
*/
error Uint96Overflow();
/**
* @dev Thrown when safe cast to uint32 fails
*/
error Uint32Overflow();
}
library MarginAccountErrors {
/**
* @dev Thrown when a non-router tries to update markets
*/
error OnlyRouterAllowed();
/**
* @dev Thrown when a non-verified market tries to execute a market action
*/
error OnlyVerifiedMarketsAllowed();
/**
* @dev Thrown when a user has insufficient margin account balance
*/
error InsufficientBalance();
/**
* @dev Thrown when native asset transfer fails
*/
error NativeAssetTransferFail();
/**
* @dev Thrown when msg.value is not zero when native assets are not required
*/
error NativeAssetMismatch();
/**
* @dev Thrown when zero address is passed as a parameter
*/
error ZeroAddressNotAllowed();
/**
* @dev Thrown when protocol is paused
*/
error ProtocolPaused();
/**
* @dev Thrown when protocol state is not changed
*/
error ProtocolStateNotChanged();
/**
* @dev Thrown when fee collector is not set
*/
error FeeCollectorNotChanged();
}
library RouterErrors {
/**
* @dev Thrown when zero address is passed as a parameter
*/
error ZeroAddressNotAllowed();
/**
* @dev Thrown when base and quote asset addresses are the same
*/
error BaseAndQuoteAssetSame();
/**
* @dev Thrown when market type given and token addresses are not compatible
*/
error MarketTypeMismatch();
/**
* @dev Thrown when tick size is 0
*/
error InvalidTickSize();
/**
* @dev Thrown when size precision is not a power of 10
*/
error InvalidSizePrecision();
/**
* @dev Thrown when price precision is not a power of 10
*/
error InvalidPricePrecision();
/**
* @dev Thrown when no markets are passed as input
*/
error NoMarketsPassed();
/**
* @dev Thrown when the length of market addresses, isBuy, and nativeSend arrays are not the same
*/
error LengthMismatch();
/**
* @dev Thrown when the market is invalid
*/
error InvalidMarket();
/**
* @dev Thrown when the slippage exceeds the expected value
*/
error SlippageExceeded();
/**
* @dev Thrown when the native asset transfer fails
*/
error NativeAssetTransferFail();
/**
* @dev Thrown when safe cast to uint96 fails
*/
error Uint96Overflow();
/**
* @dev Thrown when there is no change in the implementation
*/
error ImplementationNotChanged();
}
library KuruAMMVaultErrors {
/**
* @dev Thrown when a user is not the owner and tries to execute a privileged function
*/
error Unauthorized();
/**
* @dev Thrown when native token passed as argument and msg.value does not match
*/
error NativeAssetMismatch();
/**
* @dev Thrown when first deposit ask price is 0
*/
error AskPriceZero();
/**
* @dev Thrown when vault sizes are 0
*/
error InvalidVaultSize();
/**
* @dev Thrown when amount of quote tokens passed is insufficient
*/
error InsufficientQuoteToken();
/**
* @dev Thrown when insufficient liquidity is minted
*/
error InsufficientLiquidityMinted();
/**
* @dev Thrown when balance of owner is too less
*/
error InsufficientBalance();
/**
* @dev Thrown when quote used in a deposit is too low
*/
error InsufficientQuoteUsed();
/**
* @dev Thrown when native asset transfer fails
*/
error NativeAssetTransferFail();
/**
* @dev Thrown when new size exceeds partially filled size
*/
error NewSizeExceedsPartiallyFilledSize();
/**
* @dev Thrown when the amounts withdrawn are negative
*/
error NegativeAmountWithdrawn();
/**
* @dev Thrown when safe cast to uint96 fails
*/
error Uint96Overflow();
}
library KuruForwarderErrors {
/**
* @dev Thrown when zero address is passed as a parameter
*/
error ZeroAddressNotAllowed();
/**
* @dev Thrown when the signature does not match the request
*/
error SignatureMismatch();
/**
* @dev Thrown when the interface is not allowed
*/
error InterfaceNotAllowed();
/**
* @dev Thrown when the execution fails
*/
error ExecutionFailed();
/**
* @dev Thrown when the nonce is already used
*/
error NonceAlreadyUsed();
/**
* @dev Thrown when the value is insufficient
*/
error InsufficientValue();
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Call context checker mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/CallContextChecker.sol)
contract CallContextChecker {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The call is from an unauthorized call context.
error UnauthorizedCallContext();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For checking if the context is a delegate call.
///
/// Note: To enable use cases with an immutable default implementation in the bytecode,
/// (see: ERC6551Proxy), we don't require that the proxy address must match the
/// value stored in the implementation slot, which may not be initialized.
uint256 private immutable __self = uint256(uint160(address(this)));
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CALL CONTEXT CHECKS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// A proxy call can be either via a `delegatecall` to an implementation,
// or a 7702 call on an authority that points to a delegation.
/// @dev Returns whether the current call context is on a EIP7702 authority
/// (i.e. externally owned account).
function _onEIP7702Authority() internal view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
extcodecopy(address(), 0x00, 0x00, 0x20)
// Note: Checking that it starts with hex"ef01" is the most general and futureproof.
// 7702 bytecode is `abi.encodePacked(hex"ef01", uint8(version), address(delegation))`.
result := eq(0xef01, shr(240, mload(0x00)))
}
}
/// @dev Returns the implementation of this contract.
function _selfImplementation() internal view virtual returns (address) {
return address(uint160(__self));
}
/// @dev Returns whether the current call context is on the implementation itself.
function _onImplementation() internal view virtual returns (bool) {
return __self == uint160(address(this));
}
/// @dev Requires that the current call context is performed via a EIP7702 authority.
function _checkOnlyEIP7702Authority() internal view virtual {
if (!_onEIP7702Authority()) _revertUnauthorizedCallContext();
}
/// @dev Requires that the current call context is performed via a proxy.
function _checkOnlyProxy() internal view virtual {
if (_onImplementation()) _revertUnauthorizedCallContext();
}
/// @dev Requires that the current call context is NOT performed via a proxy.
/// This is the opposite of `checkOnlyProxy`.
function _checkNotDelegated() internal view virtual {
if (!_onImplementation()) _revertUnauthorizedCallContext();
}
/// @dev Requires that the current call context is performed via a EIP7702 authority.
modifier onlyEIP7702Authority() virtual {
_checkOnlyEIP7702Authority();
_;
}
/// @dev Requires that the current call context is performed via a proxy.
modifier onlyProxy() virtual {
_checkOnlyProxy();
_;
}
/// @dev Requires that the current call context is NOT performed via a proxy.
/// This is the opposite of `onlyProxy`.
modifier notDelegated() virtual {
_checkNotDelegated();
_;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function _revertUnauthorizedCallContext() private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`.
revert(0x1c, 0x04)
}
}
}{
"remappings": [
"forge-std/=lib/forge-std/src/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"@chainlink/=node_modules/@chainlink/",
"@eth-optimism/=node_modules/@eth-optimism/",
"ds-test/=lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"eth-gas-reporter/=node_modules/eth-gas-reporter/",
"halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
"hardhat/=node_modules/hardhat/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin-solidity/=node_modules/openzeppelin-solidity/",
"solady/=node_modules/solady/"
],
"optimizer": {
"enabled": true,
"runs": 1000
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "prague",
"viaIR": true
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"DeadlineExpired","type":"error"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"ExecutionFailed","type":"error"},{"inputs":[],"name":"InsufficientValue","type":"error"},{"inputs":[],"name":"InterfaceNotAllowed","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"NonceAlreadyUsed","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"uint256","name":"_currentPrice","type":"uint256"},{"internalType":"uint256","name":"_breakpointPrice","type":"uint256"}],"name":"PriceDependentRequestFailed","type":"error"},{"inputs":[],"name":"SignatureMismatch","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnauthorizedCallContext","type":"error"},{"inputs":[],"name":"UpgradeFailed","type":"error"},{"inputs":[],"name":"ZeroAddressNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"allowedInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct KuruForwarder.CancelPriceDependentRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"cancelPriceDependent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"market","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct KuruForwarder.ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"marginAccount","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct KuruForwarder.MarginAccountRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"executeMarginAccountRequest","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"market","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"isBelowPrice","type":"bool"},{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct KuruForwarder.PriceDependentRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"executePriceDependent","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"executedPriceDependentRequest","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"bytes4[]","name":"_allowedInterfaces","type":"bytes4[]"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4[]","name":"_allowedInterfaces","type":"bytes4[]"}],"name":"removeAllowedInterfaces","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes4[]","name":"_allowedInterfaces","type":"bytes4[]"}],"name":"setAllowedInterfaces","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"market","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct KuruForwarder.ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"verify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct KuruForwarder.CancelPriceDependentRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"verifyCancelPriceDependent","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"marginAccount","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct KuruForwarder.MarginAccountRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"verifyMarginAccountRequest","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"market","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"isBelowPrice","type":"bool"},{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct KuruForwarder.PriceDependentRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"verifyPriceDependent","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]Contract Creation Code
610140604052346100d657306080524660a0525f60c0525f60e0525f61010052306101205263409feecd1954600181166100c95760011c6002600160401b031901610084575b60405161168290816100db823960805181505060a05181505060c05181505060e051815050610100518150506101205181818161097e0152610a120152f35b6002600160411b0363409feecd19556001600160401b0360209081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29080a1610045565b63f92ee8a95f526004601cfd5b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806310012a1514610c9b5780631691267114610c6c5780631a4e971f14610bee5780631fbe2e4f14610bae5780632569296214610b655780632d0335ab14610b2d5780633bcb61ef14610b14578063487ed96e14610aff5780634f1ef286146109d957806352d1902d1461096b57806354d1f13d146109275780635b31c2e6146108b15780636b446f49146108985780636cce95f0146107285780636e0849051461066a578063715018a61461060157806384b0196e146105475780638da5cb5b1461051d578063950b2e5114610504578063df477d5014610204578063e6bc6b56146101e1578063f04e283e14610194578063f2fde38b146101575763fee81cf414610121575f80fd5b346101535760203660031901126101535761013a610e26565b63389a75e1600c525f52602080600c2054604051908152f35b5f80fd5b60203660031901126101535761016b610e26565b610173611469565b8060601b156101875761018590611595565b005b637448fbae5f526004601cfd5b6020366003190112610153576101a8610e26565b6101b0611469565b63389a75e1600c52805f526020600c2090815442116101d4575f6101859255611595565b636f5e88185f526004601cfd5b346101535760206101fa6101f436610e3c565b91611398565b6040519015158152f35b61021961021036610eb0565b9082939261118e565b156104dc576060810135908134106104b45760e08101916001600160e01b031961024284611081565b165f525f60205260ff60405f2054161561048c5761025f82610f29565b60405160609190911b6bffffffffffffffffffffffff19166020820190815260808401356034830152906102a081605481015b03601f198101835282610d0f565b5190205f52600160205260405f20600160ff1982541617905560208201600460406001600160a01b036102d284610f29565b168151928380927fb4de8b700000000000000000000000000000000000000000000000000000000082525afa908115610481575f9161044f575b5060c0840161031a8161114b565b908161043f575b811561041a575b50156103e6575f8086868661036561037661034b6103458a610f29565b95611081565b9361029261035d610100830183611096565b949092610f29565b6040519485936020850198896110c9565b51925af161038261111c565b90156103a4576103a090604051918291602083526020830190610e8c565b0390f35b6103e2906040519182917f15fcd675000000000000000000000000000000000000000000000000000000008352602060048401526024830190610e8c565b0390fd5b836040917feaac67f1000000000000000000000000000000000000000000000000000000005f52600452013560245260445ffd5b610424915061114b565b1580610431575b86610328565b50806040850135101561042b565b9050816040860135111590610321565b90506040813d604011610479575b8161046a60409383610d0f565b8101031261015357518561030c565b3d915061045d565b6040513d5f823e3d90fd5b7fd02b530d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f11011294000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f73a8ee18000000000000000000000000000000000000000000000000000000005f5260045ffd5b346101535760206101fa61051736610e3c565b91611286565b34610153575f366003190112610153576020638b78c6d819546001600160a01b0360405191168152f35b34610153575f366003190112610153576105a26105b06105656115d9565b6040929192519384937f0f00000000000000000000000000000000000000000000000000000000000000855260e0602086015260e0850190610e8c565b908382036040850152610e8c565b4660608301523060808301525f60a083015281810360c083015260206060519182815201906080905f5b8181106105e8575050500390f35b82518452859450602093840193909201916001016105da565b5f36600319011261015357610614611469565b5f638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a37f8000000000000000000000000000000000000000000000000000000000000000638b78c6d81955005b61067f61067636610e3c565b90829392611398565b156104dc576040810135908134106104b45760a081016001600160e01b03196106a782611081565b165f525f60205260ff60405f2054161561048c5760608201359160018301809311610714575f9384936001600160a01b036106e184610f29565b1685526002602052604085205561036561037661070361034560208601610f29565b9361029261035d60c0830183611096565b634e487b7160e01b5f52601160045260245ffd5b3461015357604036600319011261015357610741610e26565b60243567ffffffffffffffff811161015357610761903690600401610d45565b9063409feecd199081548060038455610867575b506001600160a01b0316801561083f57638b78c6d819546108325780638b78c6d819555f7f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a35f5b82518110156107fa57806001600160e01b03196107de60019386610f01565b51165f525f60205260405f208260ff19825416179055016107bf565b508061080257005b6002905560016020527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602080a1005b630dc149f05f526004601cfd5b7f8579befe000000000000000000000000000000000000000000000000000000005f5260045ffd5b9091600182811c14303b101561088b576001600160a01b039160ff1b1b9190610775565b63f92ee8a95f526004601cfd5b346101535760206101fa6108ab36610eb0565b9161118e565b346101535760203660031901126101535760043567ffffffffffffffff8111610153576108e2903690600401610d45565b6108ea611469565b5f5b815181101561018557806001600160e01b031961090b60019385610f01565b51165f525f60205260405f208260ff19825416179055016108ec565b5f3660031901126101535763389a75e1600c52335f525f6020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c925f80a2005b34610153575f36600319011261015357307f0000000000000000000000000000000000000000000000000000000000000000036109cc5760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b639f03a0265f526004601cfd5b6040366003190112610153576109ed610e26565b60243567ffffffffffffffff811161015357610a0d903690600401610dbd565b9091307f0000000000000000000000000000000000000000000000000000000000000000146109cc576001600160a01b0390610a47611469565b163d5f526352d1902d6001527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602060016004601d855afa5103610af157807fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a281817f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55610ad457005b815f926040519485378338925af415610ae957005b3d5f823e3d90fd5b6355299b496001526004601dfd5b61067f610b0b36610e3c565b90829392611286565b346101535760206101fa610b2736610deb565b91610f3d565b34610153576020366003190112610153576001600160a01b03610b4e610e26565b165f526002602052602060405f2054604051908152f35b5f3660031901126101535763389a75e1600c52335f526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d5f80a2005b34610153576020366003190112610153576004356001600160e01b03198116809103610153575f525f602052602060ff60405f2054166040519015158152f35b3461015357610c08610bff36610deb565b90829392610f3d565b156104dc57610292610c4f610c1c83610f29565b6040519283916020808401960135908690916034926bffffffffffffffffffffffff199060601b16825260148201520190565b5190205f52600160205260405f20600160ff198254161790555f80f35b34610153576020366003190112610153576004355f526001602052602060ff60405f2054166040519015158152f35b346101535760203660031901126101535760043567ffffffffffffffff811161015357610ccc903690600401610d45565b610cd4611469565b5f5b815181101561018557806001600160e01b0319610cf560019385610f01565b51165f525f60205260405f2060ff19815416905501610cd6565b90601f8019910116810190811067ffffffffffffffff821117610d3157604052565b634e487b7160e01b5f52604160045260245ffd5b81601f820112156101535780359167ffffffffffffffff8311610d31578260051b9160405193610d786020850186610d0f565b845260208085019382010191821161015357602001915b818310610d9c5750505090565b82356001600160e01b03198116810361015357815260209283019201610d8f565b9181601f840112156101535782359167ffffffffffffffff8311610153576020838186019501011161015357565b9060031982016080811261015357606013610153576004916064359067ffffffffffffffff821161015357610e2291600401610dbd565b9091565b600435906001600160a01b038216820361015357565b9060406003198301126101535760043567ffffffffffffffff81116101535760e0818403600319011261015357600401916024359067ffffffffffffffff821161015357610e2291600401610dbd565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9060406003198301126101535760043567ffffffffffffffff811161015357610120818403600319011261015357600401916024359067ffffffffffffffff821161015357610e2291600401610dbd565b8051821015610f155760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b356001600160a01b03811681036101535790565b9060408201359283421161105957610fc291610fbd610f5b85610f29565b602086013596604051906001600160a01b0360208301937f3ec94f0b764242014d547a29ab20da6056912fc1b658b4ad364cd66f407a80058552166040830152886060830152608082015260808152610fb560a082610d0f565b519020611485565b6114fa565b91610292611002610fd284610f29565b9260405192839160208301958690916034926bffffffffffffffffffffffff199060601b16825260148201520190565b5190205f52600160205260ff60405f205416611031576001600160a01b0361102a8192610f29565b1691161490565b7f1fb09b80000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1ab7da6b000000000000000000000000000000000000000000000000000000005f5260045ffd5b356001600160e01b0319811681036101535790565b903590601e1981360301821215610153570180359067ffffffffffffffff82116101535760200191813603831361015357565b909280926001600160e01b031960189695168352600483013701906bffffffffffffffffffffffff199060601b1660048201520190565b67ffffffffffffffff8111610d3157601f01601f191660200190565b3d15611146573d9061112d82611100565b9161113b6040519384610d0f565b82523d5f602084013e565b606090565b3580151581036101535790565b92919261116482611100565b916111726040519384610d0f565b829481845281830111610153578281602093845f960137010152565b9060a08201359283421161105957610fc291610fbd6111ac85610f29565b6111b860208701610f29565b6080870135976001600160e01b03196111d360c08a0161114b565b6111df60e08b01611081565b906111f86111f16101008d018d611096565b3691611158565b60208151910120936001600160a01b03604051968160208901997f1122e3187221cfd9d40ac63fa267c39b342d8710972523d6cbf343f43c0322f78b5216604089015216606087015260408c0135608087015260608c013560a08701528c60c087015260e08601521515610100850152166101208301526101408201526101408152610fb561016082610d0f565b91906080830135918242116110595761135791610fbd6112a586610f29565b6112b160208801610f29565b6060880135966001600160e01b03196112cc60a08b01611081565b6112dc6111f160c08d018d611096565b60208151910120926001600160a01b03604051958160208801987f199c879f0d8420ea1ff045984f2ac5541f91073952f48791833c79f1bea4534e8a5216604088015216606086015260408c013560808601528a60a086015260c08501521660e08301526101008201526101008152610fb561012082610d0f565b906001600160a01b0361136984610f29565b165f52600260205260405f20541115918261138357505090565b6001600160a01b0391925061102a8291610f29565b91906080830135918242116110595761135791610fbd6113b786610f29565b6113c360208801610f29565b6060880135966001600160e01b03196113de60a08b01611081565b6113ee6111f160c08d018d611096565b60208151910120926001600160a01b03604051958160208801987f4a5f28af10183842f321d76044fae00f1697f0bc9a8eae077fe67857cc5d64048a5216604088015216606086015260408c013560808601528a60a086015260c08501521660e08301526101008201526101008152610fb561012082610d0f565b638b78c6d81954330361147857565b6382b429005f526004601cfd5b60a061148f6115d9565b90602081519101209060208151910120604051917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f835260208301526040820152466060820152306080820152206719010000000000005f52601a52603a526042601820905f603a52565b9092919260405193806040146115555760411461152357505050505b638baa579f5f526004601cfd5b806040809201355f1a60205281375b5f526020600160805f825afa51915f6060526040523d611553575050611516565b565b508060207f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff92013590601b8260ff1c016020523560405216606052611532565b6001600160a01b031680638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3801560ff1b17638b78c6d81955565b60409081516115e88382610d0f565b600d81527f4b757275466f727761726465720000000000000000000000000000000000000060208201529161161f81519182610d0f565b600581527f312e302e3000000000000000000000000000000000000000000000000000000060208201529056fea264697066735822122021914d377a3c44f9ba6399b129149d33b1ae319d823a7985b703b09e46e2b23864736f6c634300081e0033
Deployed Bytecode
0x60806040526004361015610011575f80fd5b5f3560e01c806310012a1514610c9b5780631691267114610c6c5780631a4e971f14610bee5780631fbe2e4f14610bae5780632569296214610b655780632d0335ab14610b2d5780633bcb61ef14610b14578063487ed96e14610aff5780634f1ef286146109d957806352d1902d1461096b57806354d1f13d146109275780635b31c2e6146108b15780636b446f49146108985780636cce95f0146107285780636e0849051461066a578063715018a61461060157806384b0196e146105475780638da5cb5b1461051d578063950b2e5114610504578063df477d5014610204578063e6bc6b56146101e1578063f04e283e14610194578063f2fde38b146101575763fee81cf414610121575f80fd5b346101535760203660031901126101535761013a610e26565b63389a75e1600c525f52602080600c2054604051908152f35b5f80fd5b60203660031901126101535761016b610e26565b610173611469565b8060601b156101875761018590611595565b005b637448fbae5f526004601cfd5b6020366003190112610153576101a8610e26565b6101b0611469565b63389a75e1600c52805f526020600c2090815442116101d4575f6101859255611595565b636f5e88185f526004601cfd5b346101535760206101fa6101f436610e3c565b91611398565b6040519015158152f35b61021961021036610eb0565b9082939261118e565b156104dc576060810135908134106104b45760e08101916001600160e01b031961024284611081565b165f525f60205260ff60405f2054161561048c5761025f82610f29565b60405160609190911b6bffffffffffffffffffffffff19166020820190815260808401356034830152906102a081605481015b03601f198101835282610d0f565b5190205f52600160205260405f20600160ff1982541617905560208201600460406001600160a01b036102d284610f29565b168151928380927fb4de8b700000000000000000000000000000000000000000000000000000000082525afa908115610481575f9161044f575b5060c0840161031a8161114b565b908161043f575b811561041a575b50156103e6575f8086868661036561037661034b6103458a610f29565b95611081565b9361029261035d610100830183611096565b949092610f29565b6040519485936020850198896110c9565b51925af161038261111c565b90156103a4576103a090604051918291602083526020830190610e8c565b0390f35b6103e2906040519182917f15fcd675000000000000000000000000000000000000000000000000000000008352602060048401526024830190610e8c565b0390fd5b836040917feaac67f1000000000000000000000000000000000000000000000000000000005f52600452013560245260445ffd5b610424915061114b565b1580610431575b86610328565b50806040850135101561042b565b9050816040860135111590610321565b90506040813d604011610479575b8161046a60409383610d0f565b8101031261015357518561030c565b3d915061045d565b6040513d5f823e3d90fd5b7fd02b530d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f11011294000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f73a8ee18000000000000000000000000000000000000000000000000000000005f5260045ffd5b346101535760206101fa61051736610e3c565b91611286565b34610153575f366003190112610153576020638b78c6d819546001600160a01b0360405191168152f35b34610153575f366003190112610153576105a26105b06105656115d9565b6040929192519384937f0f00000000000000000000000000000000000000000000000000000000000000855260e0602086015260e0850190610e8c565b908382036040850152610e8c565b4660608301523060808301525f60a083015281810360c083015260206060519182815201906080905f5b8181106105e8575050500390f35b82518452859450602093840193909201916001016105da565b5f36600319011261015357610614611469565b5f638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a37f8000000000000000000000000000000000000000000000000000000000000000638b78c6d81955005b61067f61067636610e3c565b90829392611398565b156104dc576040810135908134106104b45760a081016001600160e01b03196106a782611081565b165f525f60205260ff60405f2054161561048c5760608201359160018301809311610714575f9384936001600160a01b036106e184610f29565b1685526002602052604085205561036561037661070361034560208601610f29565b9361029261035d60c0830183611096565b634e487b7160e01b5f52601160045260245ffd5b3461015357604036600319011261015357610741610e26565b60243567ffffffffffffffff811161015357610761903690600401610d45565b9063409feecd199081548060038455610867575b506001600160a01b0316801561083f57638b78c6d819546108325780638b78c6d819555f7f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a35f5b82518110156107fa57806001600160e01b03196107de60019386610f01565b51165f525f60205260405f208260ff19825416179055016107bf565b508061080257005b6002905560016020527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602080a1005b630dc149f05f526004601cfd5b7f8579befe000000000000000000000000000000000000000000000000000000005f5260045ffd5b9091600182811c14303b101561088b576001600160a01b039160ff1b1b9190610775565b63f92ee8a95f526004601cfd5b346101535760206101fa6108ab36610eb0565b9161118e565b346101535760203660031901126101535760043567ffffffffffffffff8111610153576108e2903690600401610d45565b6108ea611469565b5f5b815181101561018557806001600160e01b031961090b60019385610f01565b51165f525f60205260405f208260ff19825416179055016108ec565b5f3660031901126101535763389a75e1600c52335f525f6020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c925f80a2005b34610153575f36600319011261015357307f000000000000000000000000bf6cc109c6ebca4b28e3e51fd8798294599cfe2a036109cc5760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b639f03a0265f526004601cfd5b6040366003190112610153576109ed610e26565b60243567ffffffffffffffff811161015357610a0d903690600401610dbd565b9091307f000000000000000000000000bf6cc109c6ebca4b28e3e51fd8798294599cfe2a146109cc576001600160a01b0390610a47611469565b163d5f526352d1902d6001527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc602060016004601d855afa5103610af157807fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a281817f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55610ad457005b815f926040519485378338925af415610ae957005b3d5f823e3d90fd5b6355299b496001526004601dfd5b61067f610b0b36610e3c565b90829392611286565b346101535760206101fa610b2736610deb565b91610f3d565b34610153576020366003190112610153576001600160a01b03610b4e610e26565b165f526002602052602060405f2054604051908152f35b5f3660031901126101535763389a75e1600c52335f526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d5f80a2005b34610153576020366003190112610153576004356001600160e01b03198116809103610153575f525f602052602060ff60405f2054166040519015158152f35b3461015357610c08610bff36610deb565b90829392610f3d565b156104dc57610292610c4f610c1c83610f29565b6040519283916020808401960135908690916034926bffffffffffffffffffffffff199060601b16825260148201520190565b5190205f52600160205260405f20600160ff198254161790555f80f35b34610153576020366003190112610153576004355f526001602052602060ff60405f2054166040519015158152f35b346101535760203660031901126101535760043567ffffffffffffffff811161015357610ccc903690600401610d45565b610cd4611469565b5f5b815181101561018557806001600160e01b0319610cf560019385610f01565b51165f525f60205260405f2060ff19815416905501610cd6565b90601f8019910116810190811067ffffffffffffffff821117610d3157604052565b634e487b7160e01b5f52604160045260245ffd5b81601f820112156101535780359167ffffffffffffffff8311610d31578260051b9160405193610d786020850186610d0f565b845260208085019382010191821161015357602001915b818310610d9c5750505090565b82356001600160e01b03198116810361015357815260209283019201610d8f565b9181601f840112156101535782359167ffffffffffffffff8311610153576020838186019501011161015357565b9060031982016080811261015357606013610153576004916064359067ffffffffffffffff821161015357610e2291600401610dbd565b9091565b600435906001600160a01b038216820361015357565b9060406003198301126101535760043567ffffffffffffffff81116101535760e0818403600319011261015357600401916024359067ffffffffffffffff821161015357610e2291600401610dbd565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9060406003198301126101535760043567ffffffffffffffff811161015357610120818403600319011261015357600401916024359067ffffffffffffffff821161015357610e2291600401610dbd565b8051821015610f155760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b356001600160a01b03811681036101535790565b9060408201359283421161105957610fc291610fbd610f5b85610f29565b602086013596604051906001600160a01b0360208301937f3ec94f0b764242014d547a29ab20da6056912fc1b658b4ad364cd66f407a80058552166040830152886060830152608082015260808152610fb560a082610d0f565b519020611485565b6114fa565b91610292611002610fd284610f29565b9260405192839160208301958690916034926bffffffffffffffffffffffff199060601b16825260148201520190565b5190205f52600160205260ff60405f205416611031576001600160a01b0361102a8192610f29565b1691161490565b7f1fb09b80000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1ab7da6b000000000000000000000000000000000000000000000000000000005f5260045ffd5b356001600160e01b0319811681036101535790565b903590601e1981360301821215610153570180359067ffffffffffffffff82116101535760200191813603831361015357565b909280926001600160e01b031960189695168352600483013701906bffffffffffffffffffffffff199060601b1660048201520190565b67ffffffffffffffff8111610d3157601f01601f191660200190565b3d15611146573d9061112d82611100565b9161113b6040519384610d0f565b82523d5f602084013e565b606090565b3580151581036101535790565b92919261116482611100565b916111726040519384610d0f565b829481845281830111610153578281602093845f960137010152565b9060a08201359283421161105957610fc291610fbd6111ac85610f29565b6111b860208701610f29565b6080870135976001600160e01b03196111d360c08a0161114b565b6111df60e08b01611081565b906111f86111f16101008d018d611096565b3691611158565b60208151910120936001600160a01b03604051968160208901997f1122e3187221cfd9d40ac63fa267c39b342d8710972523d6cbf343f43c0322f78b5216604089015216606087015260408c0135608087015260608c013560a08701528c60c087015260e08601521515610100850152166101208301526101408201526101408152610fb561016082610d0f565b91906080830135918242116110595761135791610fbd6112a586610f29565b6112b160208801610f29565b6060880135966001600160e01b03196112cc60a08b01611081565b6112dc6111f160c08d018d611096565b60208151910120926001600160a01b03604051958160208801987f199c879f0d8420ea1ff045984f2ac5541f91073952f48791833c79f1bea4534e8a5216604088015216606086015260408c013560808601528a60a086015260c08501521660e08301526101008201526101008152610fb561012082610d0f565b906001600160a01b0361136984610f29565b165f52600260205260405f20541115918261138357505090565b6001600160a01b0391925061102a8291610f29565b91906080830135918242116110595761135791610fbd6113b786610f29565b6113c360208801610f29565b6060880135966001600160e01b03196113de60a08b01611081565b6113ee6111f160c08d018d611096565b60208151910120926001600160a01b03604051958160208801987f4a5f28af10183842f321d76044fae00f1697f0bc9a8eae077fe67857cc5d64048a5216604088015216606086015260408c013560808601528a60a086015260c08501521660e08301526101008201526101008152610fb561012082610d0f565b638b78c6d81954330361147857565b6382b429005f526004601cfd5b60a061148f6115d9565b90602081519101209060208151910120604051917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f835260208301526040820152466060820152306080820152206719010000000000005f52601a52603a526042601820905f603a52565b9092919260405193806040146115555760411461152357505050505b638baa579f5f526004601cfd5b806040809201355f1a60205281375b5f526020600160805f825afa51915f6060526040523d611553575050611516565b565b508060207f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff92013590601b8260ff1c016020523560405216606052611532565b6001600160a01b031680638b78c6d819547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3801560ff1b17638b78c6d81955565b60409081516115e88382610d0f565b600d81527f4b757275466f727761726465720000000000000000000000000000000000000060208201529161161f81519182610d0f565b600581527f312e302e3000000000000000000000000000000000000000000000000000000060208201529056fea264697066735822122021914d377a3c44f9ba6399b129149d33b1ae319d823a7985b703b09e46e2b23864736f6c634300081e0033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in MON
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.