Transaction Hash:
Block:
22650833 at Jun-07-2025 06:11:11 AM +UTC
Transaction Fee:
0.000144411911817404 ETH
$0.31
Gas Used:
203,852 Gas / 0.708415477 Gwei
Emitted Events:
| 588 |
TransparentUpgradeableProxy.0xa7e64de5f8345186f3a39d8f0664d7d6b534e35ca818dbfb1465bb12f80562fc( 0xa7e64de5f8345186f3a39d8f0664d7d6b534e35ca818dbfb1465bb12f80562fc, 0x3246d0d27f0580741698b7cc9771690f537bb99c3021a15d84cd249669c716e1, 0x7977cd98d619dac61fff7724188c47dfd58e5f8154fe9e692b552de9587cc268, 0x0000000000000000000000008cc81c5c09394ceaca7a53be5f547ae719d75dfc )
|
| 589 |
TransparentUpgradeableProxy.0x8277cab1f0fa69b34674f64a7d43f242b0bacece6f5b7e8652f1e0d88a9b873b( 0x8277cab1f0fa69b34674f64a7d43f242b0bacece6f5b7e8652f1e0d88a9b873b, 0x0000000000000000000000000000000000000000000000000000000000002105, 0x0000000000000000000000000000000000000000000000000000000000000004, 0x0000000000000000000000000000000000000000000000000000000000000086, 000000000000000000000000000000000000000000000000000000000001e315, 0000000000000000000000006191442101086253a636aecbcc870e4778490aab, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000080, 0000000000000000000000000000000000000000000000000000000000000000 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 8.119935115179590357 Eth | 8.119935116977564997 Eth | 0.00000000179797464 | |
| 0x5e9A8Aa2...6A79C04d1 | |||||
| 0x61914421...778490AaB |
0.885686398131660935 Eth
Nonce: 4808
|
0.885541986219843531 Eth
Nonce: 4809
| 0.000144411911817404 | ||
| 0x8FCFcd0B...43F210dB8 |
Execution Trace
TransparentUpgradeableProxy.66a1eaf3( )
OmniPortal.xsubmit( )-
Null: 0x000...001.a277246b( ) -
Null: 0x000...001.a277246b( ) -
Null: 0x000...001.a277246b( ) TransparentUpgradeableProxy.2d622343( )SolverNetInbox.markFilled( id=3246D0D27F0580741698B7CC9771690F537BB99C3021A15D84CD249669C716E1, fillHash=7977CD98D619DAC61FFF7724188C47DFD58E5F8154FE9E692B552DE9587CC268, creditedTo=0x8cC81c5C09394CEaCa7a53be5f547AE719D75dFC )TransparentUpgradeableProxy.STATICCALL( )-
OmniPortal.DELEGATECALL( )
-
-
xsubmit[OmniPortal (ln:152)]
_minValSet[OmniPortal (ln:163)]verify[OmniPortal (ln:166)]_isValidSig[Quorum (ln:1483)]recover[Quorum (ln:1491)]tryRecover[ECDSA (ln:2271)]tryRecover[ECDSA (ln:2251)]
_throwError[ECDSA (ln:2272)]ECDSAInvalidSignature[ECDSA (ln:2341)]ECDSAInvalidSignatureLength[ECDSA (ln:2343)]ECDSAInvalidSignatureS[ECDSA (ln:2345)]
_isQuorum[Quorum (ln:1485)]
verify[OmniPortal (ln:178)]processMultiProofCalldata[XBlockMerkleProof (ln:1324)]MerkleProofInvalidMultiproof[MerkleProof (ln:2138)]_hashPair[MerkleProof (ln:2156)]_efficientHash[MerkleProof (ln:2175)]_efficientHash[MerkleProof (ln:2175)]
MerkleProofInvalidMultiproof[MerkleProof (ln:2160)]
_msgLeaves[XBlockMerkleProof (ln:1324)]verify[XBlockMerkleProof (ln:1325)]processProof[MerkleProof (ln:2004)]_hashPair[MerkleProof (ln:2021)]_efficientHash[MerkleProof (ln:2175)]_efficientHash[MerkleProof (ln:2175)]
_blockHeaderLeaf[XBlockMerkleProof (ln:1325)]
_exec[OmniPortal (ln:183)]chainId[OmniPortal (ln:212)]XReceipt[OmniPortal (ln:227)]encodeWithSignature[OmniPortal (ln:234)]toBroadcastShard[OmniPortal (ln:246)]toBroadcastShard[OmniPortal (ln:255)]MsgContext[OmniPortal (ln:260)]_syscall[OmniPortal (ln:262)]gasleft[OmniPortal (ln:297)]call[OmniPortal (ln:298)]gasleft[OmniPortal (ln:299)]
_call[OmniPortal (ln:262)]gasleft[OmniPortal (ln:276)]excessivelySafeCall[OmniPortal (ln:279)]gasleft[OmniPortal (ln:280)]
XReceipt[OmniPortal (ln:266)]
File 1 of 4: TransparentUpgradeableProxy
File 2 of 4: TransparentUpgradeableProxy
File 3 of 4: OmniPortal
File 4 of 4: SolverNetInbox
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.20;
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {ProxyAdmin} from "./ProxyAdmin.sol";
/**
* @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
* does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch
* mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
* include them in the ABI so this interface must be used to interact with it.
*/
interface ITransparentUpgradeableProxy is IERC1967 {
function upgradeToAndCall(address, bytes calldata) external payable;
}
/**
* @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
* 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
* the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
* the proxy admin cannot fallback to the target implementation.
*
* These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a
* dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to
* call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and
* allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative
* interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
*
* NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
* inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch
* mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
* fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
* implementation.
*
* NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
* meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
*
* IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
* immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
* overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
* undesirable state where the admin slot is different from the actual admin.
*
* WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the
* compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new
* function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This
* could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
// An immutable address for the admin to avoid unnecessary SLOADs before each call
// at the expense of removing the ability to change the admin once it's set.
// This is acceptable if the admin is always a ProxyAdmin instance or similar contract
// with its own ability to transfer the permissions to another account.
address private immutable _admin;
/**
* @dev The proxy caller is the current admin, and can't fallback to the proxy target.
*/
error ProxyDeniedAdminAccess();
/**
* @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
* backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
* {ERC1967Proxy-constructor}.
*/
constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
_admin = address(new ProxyAdmin(initialOwner));
// Set the storage value and emit an event for ERC-1967 compatibility
ERC1967Utils.changeAdmin(_proxyAdmin());
}
/**
* @dev Returns the admin of this proxy.
*/
function _proxyAdmin() internal virtual returns (address) {
return _admin;
}
/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
*/
function _fallback() internal virtual override {
if (msg.sender == _proxyAdmin()) {
if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
revert ProxyDeniedAdminAccess();
} else {
_dispatchUpgradeToAndCall();
}
} else {
super._fallback();
}
}
/**
* @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
function _dispatchUpgradeToAndCall() private {
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
ERC1967Utils.upgradeToAndCall(newImplementation, data);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.20;
import {IBeacon} from "../beacon/IBeacon.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*/
library ERC1967Utils {
// We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
// This will be fixed in Solidity 0.8.21. At that point we should remove these events.
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.20;
import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "./ERC1967Utils.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
* encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
constructor(address implementation, bytes memory _data) payable {
ERC1967Utils.upgradeToAndCall(implementation, _data);
}
/**
* @dev Returns the current implementation address.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function _implementation() internal view virtual override returns (address) {
return ERC1967Utils.getImplementation();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.20;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol)
pragma solidity ^0.8.20;
import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol";
import {Ownable} from "../../access/Ownable.sol";
/**
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
* explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
*/
contract ProxyAdmin is Ownable {
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)`
* and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev Sets the initial owner who can perform upgrades.
*/
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
* See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
* - If `data` is empty, `msg.value` must be zero.
*/
function upgradeAndCall(
ITransparentUpgradeableProxy proxy,
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.20;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
pragma solidity ^0.8.20;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback
* function and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
File 2 of 4: TransparentUpgradeableProxy
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.20;
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {ProxyAdmin} from "./ProxyAdmin.sol";
/**
* @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
* does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch
* mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
* include them in the ABI so this interface must be used to interact with it.
*/
interface ITransparentUpgradeableProxy is IERC1967 {
function upgradeToAndCall(address, bytes calldata) external payable;
}
/**
* @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
* 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
* the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
* the proxy admin cannot fallback to the target implementation.
*
* These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a
* dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to
* call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and
* allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative
* interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
*
* NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
* inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch
* mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
* fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
* implementation.
*
* NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
* meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
*
* IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
* immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
* overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
* undesirable state where the admin slot is different from the actual admin.
*
* WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the
* compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new
* function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This
* could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
// An immutable address for the admin to avoid unnecessary SLOADs before each call
// at the expense of removing the ability to change the admin once it's set.
// This is acceptable if the admin is always a ProxyAdmin instance or similar contract
// with its own ability to transfer the permissions to another account.
address private immutable _admin;
/**
* @dev The proxy caller is the current admin, and can't fallback to the proxy target.
*/
error ProxyDeniedAdminAccess();
/**
* @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
* backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
* {ERC1967Proxy-constructor}.
*/
constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
_admin = address(new ProxyAdmin(initialOwner));
// Set the storage value and emit an event for ERC-1967 compatibility
ERC1967Utils.changeAdmin(_proxyAdmin());
}
/**
* @dev Returns the admin of this proxy.
*/
function _proxyAdmin() internal virtual returns (address) {
return _admin;
}
/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
*/
function _fallback() internal virtual override {
if (msg.sender == _proxyAdmin()) {
if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
revert ProxyDeniedAdminAccess();
} else {
_dispatchUpgradeToAndCall();
}
} else {
super._fallback();
}
}
/**
* @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
function _dispatchUpgradeToAndCall() private {
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
ERC1967Utils.upgradeToAndCall(newImplementation, data);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.20;
import {IBeacon} from "../beacon/IBeacon.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*/
library ERC1967Utils {
// We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
// This will be fixed in Solidity 0.8.21. At that point we should remove these events.
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.20;
import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "./ERC1967Utils.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
* encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
constructor(address implementation, bytes memory _data) payable {
ERC1967Utils.upgradeToAndCall(implementation, _data);
}
/**
* @dev Returns the current implementation address.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function _implementation() internal view virtual override returns (address) {
return ERC1967Utils.getImplementation();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.20;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol)
pragma solidity ^0.8.20;
import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol";
import {Ownable} from "../../access/Ownable.sol";
/**
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
* explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
*/
contract ProxyAdmin is Ownable {
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)`
* and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev Sets the initial owner who can perform upgrades.
*/
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
* See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
* - If `data` is empty, `msg.value` must be zero.
*/
function upgradeAndCall(
ITransparentUpgradeableProxy proxy,
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.20;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
pragma solidity ^0.8.20;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback
* function and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
File 3 of 4: OmniPortal
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.24;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { ExcessivelySafeCall } from "@nomad-xyz/excessively-safe-call/src/ExcessivelySafeCall.sol";
import { IFeeOracle } from "../interfaces/IFeeOracle.sol";
import { IOmniPortal } from "../interfaces/IOmniPortal.sol";
import { IOmniPortalSys } from "../interfaces/IOmniPortalSys.sol";
import { IOmniPortalAdmin } from "../interfaces/IOmniPortalAdmin.sol";
import { XBlockMerkleProof } from "../libraries/XBlockMerkleProof.sol";
import { XTypes } from "../libraries/XTypes.sol";
import { Quorum } from "../libraries/Quorum.sol";
import { ConfLevel } from "../libraries/ConfLevel.sol";
import { PausableUpgradeable } from "../utils/PausableUpgradeable.sol";
import { OmniPortalConstants } from "./OmniPortalConstants.sol";
import { OmniPortalStorage } from "./OmniPortalStorage.sol";
contract OmniPortal is
IOmniPortal,
IOmniPortalSys,
IOmniPortalAdmin,
OwnableUpgradeable,
PausableUpgradeable,
ReentrancyGuardUpgradeable,
OmniPortalConstants,
OmniPortalStorage
{
using ExcessivelySafeCall for address;
/**
* @notice Modifier that requires an action is not paused. An action is paused if:
* - actionId is paused for all chains
* - actionId is paused for chainId
* - All actions are paused
* Available actions are ActionXCall and ActionXSubmit, defined in OmniPortalConstants.sol.
*/
modifier whenNotPaused(bytes32 actionId, uint64 chainId_) {
require(!_isPaused(actionId, _chainActionId(actionId, chainId_)), "OmniPortal: paused");
_;
}
/**
* @notice Construct the OmniPortal contract
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initialization init params
* @dev Used to reduce stack depth in initialize()
* @custom:field owner The owner of the contract
* @custom:field feeOracle Address of the fee oracle contract
* @custom:field omniChainId Chain ID of Omni's EVM execution chain
* @custom:field omniCChainId Chain ID of Omni's consensus chain
* @custom:field xmsgMaxGasLimit Maximum gas limit for xmsg
* @custom:field xmsgMinGasLimit Minimum gas limit for xmsg
* @custom:field xmsgMaxDataSize Maximum size of xmsg data in bytes
* @custom:field xreceiptMaxErrorSize Maximum size of xreceipt error in bytes
* @custom:field xsubValsetCutoff Number of validator sets since the latest that validate an XSubmission
* @custom:field cChainXMsgOffset Offset for xmsgs from the consensus chain
* @custom:field cChainXBlockOffset Offset for xblocks from the consensus chain
* @custom:field valSetId Initial validator set id
* @custom:field validators Initial validator set
*/
struct InitParams {
address owner;
address feeOracle;
uint64 omniChainId;
uint64 omniCChainId;
uint64 xmsgMaxGasLimit;
uint64 xmsgMinGasLimit;
uint16 xmsgMaxDataSize;
uint16 xreceiptMaxErrorSize;
uint8 xsubValsetCutoff;
uint64 cChainXMsgOffset;
uint64 cChainXBlockOffset;
uint64 valSetId;
XTypes.Validator[] validators;
}
/**
* @notice Initialize the OmniPortal contract
*/
function initialize(InitParams calldata p) public initializer {
__Ownable_init(p.owner);
_setFeeOracle(p.feeOracle);
_setXMsgMaxGasLimit(p.xmsgMaxGasLimit);
_setXMsgMaxDataSize(p.xmsgMaxDataSize);
_setXMsgMinGasLimit(p.xmsgMinGasLimit);
_setXReceiptMaxErrorSize(p.xreceiptMaxErrorSize);
_setXSubValsetCutoff(p.xsubValsetCutoff);
_addValidatorSet(p.valSetId, p.validators);
omniChainId = p.omniChainId;
omniCChainId = p.omniCChainId;
// omni consensus chain uses Finalised+Broadcast shard
uint64 omniCShard = ConfLevel.toBroadcastShard(ConfLevel.Finalized);
_setInXMsgOffset(p.omniCChainId, omniCShard, p.cChainXMsgOffset);
_setInXBlockOffset(p.omniCChainId, omniCShard, p.cChainXBlockOffset);
}
function chainId() public view returns (uint64) {
return uint64(block.chainid);
}
/**
* @notice Returns the current network (supported chain IDs and shards)
*/
function network() external view returns (XTypes.Chain[] memory) {
return _network;
}
//////////////////////////////////////////////////////////////////////////////
// Outbound xcall functions //
//////////////////////////////////////////////////////////////////////////////
/**
* @notice Call a contract on another chain.
* @param destChainId Destination chain ID
* @param conf Confirmation level
* @param to Address of contract to call on destination chain
* @param data ABI Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function xcall(uint64 destChainId, uint8 conf, address to, bytes calldata data, uint64 gasLimit)
external
payable
whenNotPaused(ActionXCall, destChainId)
{
require(isSupportedDest[destChainId], "OmniPortal: unsupported dest");
require(to != VirtualPortalAddress, "OmniPortal: no portal xcall");
require(gasLimit <= xmsgMaxGasLimit, "OmniPortal: gasLimit too high");
require(gasLimit >= xmsgMinGasLimit, "OmniPortal: gasLimit too low");
require(data.length <= xmsgMaxDataSize, "OmniPortal: data too large");
// conf level will always be last byte of shardId. for now, shardId is just conf level
uint64 shardId = uint64(conf);
require(isSupportedShard[shardId], "OmniPortal: unsupported shard");
uint256 fee = feeFor(destChainId, data, gasLimit);
require(msg.value >= fee, "OmniPortal: insufficient fee");
outXMsgOffset[destChainId][shardId] += 1;
emit XMsg(destChainId, shardId, outXMsgOffset[destChainId][shardId], msg.sender, to, data, gasLimit, fee);
}
/**
* @notice Calculate the fee for calling a contract on another chain
* Fees denominated in wei.
* @param destChainId Destination chain ID
* @param data Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function feeFor(uint64 destChainId, bytes calldata data, uint64 gasLimit) public view returns (uint256) {
return IFeeOracle(feeOracle).feeFor(destChainId, data, gasLimit);
}
//////////////////////////////////////////////////////////////////////////////
// Inbound xcall functions //
//////////////////////////////////////////////////////////////////////////////
/**
* @notice Submit a batch of XMsgs to be executed on this chain
* @param xsub An xchain submission, including an attestation root w/ validator signatures,
* and a block header and message batch, proven against the attestation root.
*/
function xsubmit(XTypes.Submission calldata xsub)
external
whenNotPaused(ActionXSubmit, xsub.blockHeader.sourceChainId)
nonReentrant
{
XTypes.Msg[] calldata xmsgs = xsub.msgs;
XTypes.BlockHeader calldata xheader = xsub.blockHeader;
uint64 valSetId = xsub.validatorSetId;
require(xheader.consensusChainId == omniCChainId, "OmniPortal: wrong cchain ID");
require(xmsgs.length > 0, "OmniPortal: no xmsgs");
require(valSetTotalPower[valSetId] > 0, "OmniPortal: unknown val set");
require(valSetId >= _minValSet(), "OmniPortal: old val set");
// check that the attestationRoot is signed by a quorum of validators in xsub.validatorsSetId
require(
Quorum.verify(
xsub.attestationRoot,
xsub.signatures,
valSet[valSetId],
valSetTotalPower[valSetId],
XSubQuorumNumerator,
XSubQuorumDenominator
),
"OmniPortal: no quorum"
);
// check that blockHeader and xmsgs are included in attestationRoot
require(
XBlockMerkleProof.verify(xsub.attestationRoot, xheader, xmsgs, xsub.proof, xsub.proofFlags),
"OmniPortal: invalid proof"
);
// execute xmsgs
for (uint256 i = 0; i < xmsgs.length; i++) {
_exec(xheader, xmsgs[i]);
}
}
/**
* @notice Returns the current XMsg being executed via this portal.
* - xmsg().sourceChainId Chain ID of the source xcall
* - xmsg().sender msg.sender of the source xcall
* If no XMsg is being executed, all fields will be zero.
* - xmsg().sourceChainId == 0
* - xmsg().sender == address(0)
*/
function xmsg() external view returns (XTypes.MsgContext memory) {
return _xmsg;
}
/**
* @notice Returns true the current transaction is an xcall, false otherwise
*/
function isXCall() external view returns (bool) {
return _xmsg.sourceChainId != 0;
}
/**
* @notice Execute an xmsg.
* @dev Verify if an XMsg is next in its XStream, execute it, increment inXMsgOffset, emit an XReceipt event
*/
function _exec(XTypes.BlockHeader calldata xheader, XTypes.Msg calldata xmsg_) internal {
uint64 sourceChainId = xheader.sourceChainId;
uint64 destChainId = xmsg_.destChainId;
uint64 shardId = xmsg_.shardId;
uint64 offset = xmsg_.offset;
require(destChainId == chainId() || destChainId == BroadcastChainId, "OmniPortal: wrong dest chain");
require(offset == inXMsgOffset[sourceChainId][shardId] + 1, "OmniPortal: wrong offset");
// verify xmsg conf level matches xheader conf level
// allow finalized blocks to for any xmsg, so that finalized blocks may correct "fuzzy" xmsgs
require(
ConfLevel.Finalized == xheader.confLevel || xheader.confLevel == uint8(shardId),
"OmniPortal: wrong conf level"
);
if (inXBlockOffset[sourceChainId][shardId] < xheader.offset) {
inXBlockOffset[sourceChainId][shardId] = xheader.offset;
}
inXMsgOffset[sourceChainId][shardId] += 1;
// do not allow user xcalls to the portal
// only sys xcalls (to _VIRTUAL_PORTAL_ADDRESS) are allowed to be executed on the portal
if (xmsg_.to == address(this)) {
emit XReceipt(
sourceChainId,
shardId,
offset,
0,
msg.sender,
false,
abi.encodeWithSignature("Error(string)", "OmniPortal: no xcall to portal")
);
return;
}
// calls to VirtualPortalAddress are syscalls
bool isSysCall = xmsg_.to == VirtualPortalAddress;
if (isSysCall) {
bytes4 selector = bytes4(xmsg_.data);
// sys calls must broadcast from the consensus chain to a supported handler
require(
xheader.sourceChainId == omniCChainId && xmsg_.sender == CChainSender
&& xmsg_.destChainId == BroadcastChainId
&& xmsg_.shardId == ConfLevel.toBroadcastShard(ConfLevel.Finalized)
&& (selector == this.addValidatorSet.selector || selector == this.setNetwork.selector),
"OmniPortal: invalid syscall"
);
} else {
// only sys calls can be broadcast from the consensus chain
require(
xheader.sourceChainId != omniCChainId && xmsg_.sender != CChainSender
&& xmsg_.destChainId != BroadcastChainId
&& xmsg_.shardId != ConfLevel.toBroadcastShard(ConfLevel.Finalized),
"OmniPortal: invalid xcall"
);
}
// set _xmsg to the one we're executing, allowing external contracts to query the current xmsg via xmsg()
_xmsg = XTypes.MsgContext(sourceChainId, xmsg_.sender);
(bool success, bytes memory result, uint256 gasUsed) =
isSysCall ? _syscall(xmsg_.data) : _call(xmsg_.to, xmsg_.gasLimit, xmsg_.data);
// reset xmsg to zero
delete _xmsg;
bytes memory errorMsg = success ? bytes("") : result;
emit XReceipt(sourceChainId, shardId, offset, gasUsed, msg.sender, success, errorMsg);
}
/**
* @notice Call an external contract.
* @dev Returns the result of the call, the gas used, and whether the call was successful.
* @param to The address of the contract to call.
* @param gasLimit Gas limit of the call.
* @param data Calldata to send to the contract.
*/
function _call(address to, uint256 gasLimit, bytes calldata data) internal returns (bool, bytes memory, uint256) {
uint256 gasLeftBefore = gasleft();
// use excessivelySafeCall for external calls to prevent large return bytes mem copy
(bool success, bytes memory result) =
to.excessivelySafeCall({ _gas: gasLimit, _value: 0, _maxCopy: xreceiptMaxErrorSize, _calldata: data });
uint256 gasLeftAfter = gasleft();
// Ensure relayer sent enough gas for the call
// See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/bd325d56b4c62c9c5c1aff048c37c6bb18ac0290/contracts/metatx/MinimalForwarder.sol#L58-L68
if (gasLeftAfter <= gasLimit / 63) {
// We use invalid opcode to consume all gas and bubble-up the effects, to emulate an "OutOfGas" exception
assembly {
invalid()
}
}
return (success, result, gasLeftBefore - gasLeftAfter);
}
/**
* @notice Call a function on the current contract.
* @dev Reverts on failure. We match _call() return signature for symmetry.
* @param data Calldata to execute on the current contract.
*/
function _syscall(bytes calldata data) internal returns (bool, bytes memory, uint256) {
uint256 gasUsed = gasleft();
(bool success, bytes memory result) = address(this).call(data);
gasUsed = gasUsed - gasleft();
// if not success, revert with same reason
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
return (success, result, gasUsed);
}
/**
* @notice Returns the minimum validator set id that can be used for xsubmissions
*/
function _minValSet() internal view returns (uint64) {
return latestValSetId > xsubValsetCutoff
// plus 1, so the number of accepted valsets == XSubValsetCutoff
? (latestValSetId - xsubValsetCutoff + 1)
: 1;
}
//////////////////////////////////////////////////////////////////////////////
// Syscall functions //
//////////////////////////////////////////////////////////////////////////////
/**
* @notice Add a new validator set.
* @dev Only callable via xcall from Omni's consensus chain
* @param valSetId Validator set id
* @param validators Validator set
*/
function addValidatorSet(uint64 valSetId, XTypes.Validator[] calldata validators) external {
require(msg.sender == address(this), "OmniPortal: only self");
require(_xmsg.sourceChainId == omniCChainId, "OmniPortal: only cchain");
require(_xmsg.sender == CChainSender, "OmniPortal: only cchain sender");
_addValidatorSet(valSetId, validators);
}
/**
* @notice Add a new validator set
* @param valSetId Validator set id
* @param validators Validator set
*/
function _addValidatorSet(uint64 valSetId, XTypes.Validator[] calldata validators) internal {
uint256 numVals = validators.length;
require(numVals > 0, "OmniPortal: no validators");
require(valSetTotalPower[valSetId] == 0, "OmniPortal: duplicate val set");
uint64 totalPower;
XTypes.Validator memory val;
mapping(address => uint64) storage _valSet = valSet[valSetId];
for (uint256 i = 0; i < numVals; i++) {
val = validators[i];
require(val.addr != address(0), "OmniPortal: no zero validator");
require(val.power > 0, "OmniPortal: no zero power");
require(_valSet[val.addr] == 0, "OmniPortal: duplicate validator");
totalPower += val.power;
_valSet[val.addr] = val.power;
}
valSetTotalPower[valSetId] = totalPower;
if (valSetId > latestValSetId) latestValSetId = valSetId;
emit ValidatorSetAdded(valSetId);
}
/**
* @notice Set the network of supported chains & shards
* @dev Only callable via xcall from Omni's consensus chain
* @param network_ The new network
*/
function setNetwork(XTypes.Chain[] calldata network_) external {
require(msg.sender == address(this), "OmniPortal: only self");
require(_xmsg.sourceChainId == omniCChainId, "OmniPortal: only cchain");
require(_xmsg.sender == CChainSender, "OmniPortal: only cchain sender");
_setNetwork(network_);
}
/**
* @notice Set the network of supported chains & shards
* @param network_ The new network
*/
function _setNetwork(XTypes.Chain[] calldata network_) internal {
_clearNetwork();
XTypes.Chain calldata c;
for (uint256 i = 0; i < network_.length; i++) {
c = network_[i];
_network.push(c);
// if not this chain, mark as supported dest
if (c.chainId != chainId()) {
isSupportedDest[c.chainId] = true;
continue;
}
// if this chain, mark shards as supported
for (uint256 j = 0; j < c.shards.length; j++) {
isSupportedShard[c.shards[j]] = true;
}
}
}
/**
* @notice Clear the network of supported chains & shards
*/
function _clearNetwork() private {
XTypes.Chain storage c;
for (uint256 i = 0; i < _network.length; i++) {
c = _network[i];
// if not this chain, mark as unsupported dest
if (c.chainId != chainId()) {
isSupportedDest[c.chainId] = false;
continue;
}
// if this chain, mark shards as unsupported
for (uint256 j = 0; j < c.shards.length; j++) {
isSupportedShard[c.shards[j]] = false;
}
}
delete _network;
}
//////////////////////////////////////////////////////////////////////////////
// Admin functions //
//////////////////////////////////////////////////////////////////////////////
/**
* @notice Set the inbound xmsg offset for a chain and shard
* @param sourceChainId Source chain ID
* @param shardId Shard ID
* @param offset New xmsg offset
*/
function setInXMsgOffset(uint64 sourceChainId, uint64 shardId, uint64 offset) external onlyOwner {
_setInXMsgOffset(sourceChainId, shardId, offset);
}
/**
* @notice Set the inbound xblock offset for a chain and shard
* @param sourceChainId Source chain ID
* @param shardId Shard ID
* @param offset New xblock offset
*/
function setInXBlockOffset(uint64 sourceChainId, uint64 shardId, uint64 offset) external onlyOwner {
_setInXBlockOffset(sourceChainId, shardId, offset);
}
/**
* @notice Set the fee oracle
*/
function setFeeOracle(address feeOracle_) external onlyOwner {
_setFeeOracle(feeOracle_);
}
/**
* @notice Transfer all collected fees to the give address
* @param to The address to transfer the fees to
*/
function collectFees(address to) external onlyOwner {
uint256 amount = address(this).balance;
// .transfer() is fine, owner should provide an EOA address that will not
// consume more than 2300 gas on transfer, and we are okay .transfer() reverts
payable(to).transfer(amount);
emit FeesCollected(to, amount);
}
/**
* @notice Set the minimum gas limit for xmsg
*/
function setXMsgMinGasLimit(uint64 gasLimit) external onlyOwner {
_setXMsgMinGasLimit(gasLimit);
}
/**
* @notice Set the maximum gas limit for xmsg
*/
function setXMsgMaxGasLimit(uint64 gasLimit) external onlyOwner {
_setXMsgMaxGasLimit(gasLimit);
}
/**
* @notice Set the maximum error bytes for xreceipt
*/
function setXMsgMaxDataSize(uint16 numBytes) external onlyOwner {
_setXMsgMaxDataSize(numBytes);
}
/**
* @notice Set the maximum error bytes for xreceipt
*/
function setXReceiptMaxErrorSize(uint16 numBytes) external onlyOwner {
_setXReceiptMaxErrorSize(numBytes);
}
/**
* @notice Set the number of validator sets since the latest that can validate an XSubmission
*/
function setXSubValsetCutoff(uint8 xsubValsetCutoff_) external onlyOwner {
_setXSubValsetCutoff(xsubValsetCutoff_);
}
/**
* @notice Pause xcalls and xsubissions from all chains
*/
function pause() external onlyOwner {
_pauseAll();
emit Paused();
}
/**
* @notice Unpause xcalls and xsubissions from all chains
*/
function unpause() external onlyOwner {
_unpauseAll();
emit Unpaused();
}
/**
* @notice Pause xcalls to all chains
*/
function pauseXCall() external onlyOwner {
_pause(ActionXCall);
emit XCallPaused();
}
/**
* @notice Unpause xcalls to all chains
*/
function unpauseXCall() external onlyOwner {
_unpause(ActionXCall);
emit XCallUnpaused();
}
/**
* @notice Pause xcalls to a specific chain
* @param chainId_ Destination chain ID
*/
function pauseXCallTo(uint64 chainId_) external onlyOwner {
_pause(_chainActionId(ActionXCall, chainId_));
emit XCallToPaused(chainId_);
}
/**
* @notice Unpause xcalls to a specific chain
* @param chainId_ Destination chain ID
*/
function unpauseXCallTo(uint64 chainId_) external onlyOwner {
_unpause(_chainActionId(ActionXCall, chainId_));
emit XCallToUnpaused(chainId_);
}
/**
* @notice Pause xsubmissions from all chains
*/
function pauseXSubmit() external onlyOwner {
_pause(ActionXSubmit);
emit XSubmitPaused();
}
/**
* @notice Unpause xsubmissions from all chains
*/
function unpauseXSubmit() external onlyOwner {
_unpause(ActionXSubmit);
emit XSubmitUnpaused();
}
/**
* @notice Pause xsubmissions from a specific chain
* @param chainId_ Source chain ID
*/
function pauseXSubmitFrom(uint64 chainId_) external onlyOwner {
_pause(_chainActionId(ActionXSubmit, chainId_));
emit XSubmitFromPaused(chainId_);
}
/**
* @notice Unpause xsubmissions from a specific chain
* @param chainId_ Source chain ID
*/
function unpauseXSubmitFrom(uint64 chainId_) external onlyOwner {
_unpause(_chainActionId(ActionXSubmit, chainId_));
emit XSubmitFromUnpaused(chainId_);
}
/**
* @notice Return true if actionId for is paused for the given chain
*/
function isPaused(bytes32 actionId, uint64 chainId_) external view returns (bool) {
return _isPaused(actionId, _chainActionId(actionId, chainId_));
}
/**
* @notice Return true if actionId is paused for all chains
*/
function isPaused(bytes32 actionId) external view returns (bool) {
return _isPaused(actionId);
}
/*
* @notice Return true if all actions are paused
*/
function isPaused() external view returns (bool) {
return _isAllPaused();
}
/**
* @notice An action id with a qualifiying chain id, used as pause keys.
*/
function _chainActionId(bytes32 actionId, uint64 chainId_) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(actionId, chainId_));
}
/**
* @notice Set the minimum gas limit for xmsg
*/
function _setXMsgMinGasLimit(uint64 gasLimit) internal {
require(gasLimit > 0, "OmniPortal: no zero min gas");
require(gasLimit < xmsgMaxGasLimit, "OmniPortal: not below max");
xmsgMinGasLimit = gasLimit;
emit XMsgMinGasLimitSet(gasLimit);
}
/**
* @notice Set the maximum gas limit for xmsg
*/
function _setXMsgMaxGasLimit(uint64 gasLimit) internal {
require(gasLimit > xmsgMinGasLimit, "OmniPortal: not above min");
xmsgMaxGasLimit = gasLimit;
emit XMsgMaxGasLimitSet(gasLimit);
}
/**
* @notice Set the maximum data bytes for xmsg
*/
function _setXMsgMaxDataSize(uint16 numBytes) internal {
require(numBytes > 0, "OmniPortal: no zero max size");
xmsgMaxDataSize = numBytes;
emit XMsgMaxDataSizeSet(numBytes);
}
/**
* @notice Set the maximum error bytes for xreceipt
*/
function _setXReceiptMaxErrorSize(uint16 numBytes) internal {
require(numBytes > 0, "OmniPortal: no zero max size");
xreceiptMaxErrorSize = numBytes;
emit XReceiptMaxErrorSizeSet(numBytes);
}
/**
* @notice Set the number of validator sets since the latest that can validate an XSubmission
*/
function _setXSubValsetCutoff(uint8 xsubValsetCutoff_) internal {
require(xsubValsetCutoff_ > 0, "OmniPortal: no zero cutoff");
xsubValsetCutoff = xsubValsetCutoff_;
emit XSubValsetCutoffSet(xsubValsetCutoff_);
}
/**
* @notice Set the fee oracle
*/
function _setFeeOracle(address feeOracle_) internal {
require(feeOracle_ != address(0), "OmniPortal: no zero feeOracle");
feeOracle = feeOracle_;
emit FeeOracleSet(feeOracle_);
}
/**
* @notice Set the inbound xmsg offset for a chain and shard
*/
function _setInXMsgOffset(uint64 sourceChainId, uint64 shardId, uint64 offset) internal {
inXMsgOffset[sourceChainId][shardId] = offset;
emit InXMsgOffsetSet(sourceChainId, shardId, offset);
}
/**
* @notice Set the inbound xblock offset for a chain and shard
*/
function _setInXBlockOffset(uint64 sourceChainId, uint64 shardId, uint64 offset) internal {
inXBlockOffset[sourceChainId][shardId] = offset;
emit InXBlockOffsetSet(sourceChainId, shardId, offset);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
OwnableStorage storage $ = _getOwnableStorage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
struct ReentrancyGuardStorage {
uint256 _status;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
assembly {
$.slot := ReentrancyGuardStorageLocation
}
}
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
$._status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// On the first call to nonReentrant, _status will be NOT_ENTERED
if ($._status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
$._status = ENTERED;
}
function _nonReentrantAfter() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
$._status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
return $._status == ENTERED;
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.7.6;
library ExcessivelySafeCall {
uint256 constant LOW_28_MASK =
0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/// @notice Use when you _really_ really _really_ don't trust the called
/// contract. This prevents the called contract from causing reversion of
/// the caller in as many ways as we can.
/// @dev The main difference between this and a solidity low-level call is
/// that we limit the number of bytes that the callee can cause to be
/// copied to caller memory. This prevents stupid things like malicious
/// contracts returning 10,000,000 bytes causing a local OOG when copying
/// to memory.
/// @param _target The address to call
/// @param _gas The amount of gas to forward to the remote contract
/// @param _value The value in wei to send to the remote contract
/// @param _maxCopy The maximum number of bytes of returndata to copy
/// to memory.
/// @param _calldata The data to send to the remote contract
/// @return success and returndata, as `.call()`. Returndata is capped to
/// `_maxCopy` bytes.
function excessivelySafeCall(
address _target,
uint256 _gas,
uint256 _value,
uint16 _maxCopy,
bytes memory _calldata
) internal returns (bool, bytes memory) {
// set up for assembly call
uint256 _toCopy;
bool _success;
bytes memory _returnData = new bytes(_maxCopy);
// dispatch message to recipient
// by assembly calling "handle" function
// we call via assembly to avoid memcopying a very large returndata
// returned by a malicious contract
assembly {
_success := call(
_gas, // gas
_target, // recipient
_value, // ether value
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]
returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/// @notice Use when you _really_ really _really_ don't trust the called
/// contract. This prevents the called contract from causing reversion of
/// the caller in as many ways as we can.
/// @dev The main difference between this and a solidity low-level call is
/// that we limit the number of bytes that the callee can cause to be
/// copied to caller memory. This prevents stupid things like malicious
/// contracts returning 10,000,000 bytes causing a local OOG when copying
/// to memory.
/// @param _target The address to call
/// @param _gas The amount of gas to forward to the remote contract
/// @param _maxCopy The maximum number of bytes of returndata to copy
/// to memory.
/// @param _calldata The data to send to the remote contract
/// @return success and returndata, as `.call()`. Returndata is capped to
/// `_maxCopy` bytes.
function excessivelySafeStaticCall(
address _target,
uint256 _gas,
uint16 _maxCopy,
bytes memory _calldata
) internal view returns (bool, bytes memory) {
// set up for assembly call
uint256 _toCopy;
bool _success;
bytes memory _returnData = new bytes(_maxCopy);
// dispatch message to recipient
// by assembly calling "handle" function
// we call via assembly to avoid memcopying a very large returndata
// returned by a malicious contract
assembly {
_success := staticcall(
_gas, // gas
_target, // recipient
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]
returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/**
* @notice Swaps function selectors in encoded contract calls
* @dev Allows reuse of encoded calldata for functions with identical
* argument types but different names. It simply swaps out the first 4 bytes
* for the new selector. This function modifies memory in place, and should
* only be used with caution.
* @param _newSelector The new 4-byte selector
* @param _buf The encoded contract args
*/
function swapSelector(bytes4 _newSelector, bytes memory _buf)
internal
pure
{
require(_buf.length >= 4);
uint256 _mask = LOW_28_MASK;
assembly {
// load the first word of
let _word := mload(add(_buf, 0x20))
// mask out the top 4 bytes
// /x
_word := and(_word, _mask)
_word := or(_newSelector, _word)
mstore(add(_buf, 0x20), _word)
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title IFeeOracle
* @notice Defines the interface expected of a fee oracle by the OmniPortal
*/
interface IFeeOracle {
/**
* @notice Calculate the fee for calling a contract on another chain
* @dev Fees denominated in wei
* @param destChainId Destination chain ID
* @param data Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function feeFor(uint64 destChainId, bytes calldata data, uint64 gasLimit) external view returns (uint256);
/**
* @notice Returns the version of the fee oracle
*/
function version() external view returns (uint64);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
import { XTypes } from "../libraries/XTypes.sol";
/**
* @title IOmniPortal
* @notice The OmniPortal is the on-chain interface to Omni's cross-chain
* messaging protocol. It is used to initiate and execute cross-chain calls.
*/
interface IOmniPortal {
/**
* @notice Emitted when an xcall is made to a contract on another chain
* @param destChainId Destination chain ID
* @param offset Offset this XMsg in the source -> dest XStream
* @param sender msg.sender of the source xcall
* @param to Address of the contract to call on the destination chain
* @param data Encoded function calldata
* @param gasLimit Gas limit for execution on destination chain
* @param fees Fees paid for the xcall
*/
event XMsg(
uint64 indexed destChainId,
uint64 indexed shardId,
uint64 indexed offset,
address sender,
address to,
bytes data,
uint64 gasLimit,
uint256 fees
);
/**
* @notice Emitted when an XMsg is executed on its destination chain
* @param sourceChainId Source chain ID
* @param shardId Shard ID of the XStream (last byte is the confirmation level)
* @param offset Offset the XMsg in the source -> dest XStream
* @param gasUsed Gas used in execution of the XMsg
* @param relayer Address of the relayer who submitted the XMsg
* @param success Whether the execution succeeded
* @param err Result of XMsg execution, if success == false. Limited to
* xreceiptMaxErrorBytes(). Empty if success == true.
*/
event XReceipt(
uint64 indexed sourceChainId,
uint64 indexed shardId,
uint64 indexed offset,
uint256 gasUsed,
address relayer,
bool success,
bytes err
);
/**
* @notice Maximum allowed xmsg gas limit
*/
function xmsgMaxGasLimit() external view returns (uint64);
/**
* @notice Minimum allowed xmsg gas limit
*/
function xmsgMinGasLimit() external view returns (uint64);
/**
* @notice Maximum number of bytes allowed in xmsg data
*/
function xmsgMaxDataSize() external view returns (uint16);
/**
* @notice Maxium number of bytes allowed in xreceipt result
*/
function xreceiptMaxErrorSize() external view returns (uint16);
/**
* @notice Returns the fee oracle address
*/
function feeOracle() external view returns (address);
/**
* @notice Returns the chain ID of the chain to which this portal is deployed
*/
function chainId() external view returns (uint64);
/**
* @notice Returns the chain ID of Omni's EVM execution chain
*/
function omniChainId() external view returns (uint64);
/**
* @notice Returns the offset of the last outbound XMsg sent to destChainId in shardId
*/
function outXMsgOffset(uint64 destChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the offset of the last inbound XMsg received from srcChainId in shardId
*/
function inXMsgOffset(uint64 srcChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the offset of the last inbound XBlock received from srcChainId in shardId
*/
function inXBlockOffset(uint64 srcChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the current XMsg being executed via this portal.
* - xmsg().sourceChainId Chain ID of the source xcall
* - xmsg().sender msg.sender of the source xcall
* If no XMsg is being executed, all fields will be zero.
* - xmsg().sourceChainId == 0
* - xmsg().sender == address(0)
*/
function xmsg() external view returns (XTypes.MsgContext memory);
/**
* @notice Returns true the current transaction is an xcall, false otherwise
*/
function isXCall() external view returns (bool);
/**
* @notice Returns the shard ID is supported by this portal
*/
function isSupportedShard(uint64 shardId) external view returns (bool);
/**
* @notice Returns the destination chain ID is supported by this portal
*/
function isSupportedDest(uint64 destChainId) external view returns (bool);
/**
* @notice Calculate the fee for calling a contract on another chain
* Fees denominated in wei.
* @param destChainId Destination chain ID
* @param data Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function feeFor(uint64 destChainId, bytes calldata data, uint64 gasLimit) external view returns (uint256);
/**
* @notice Call a contract on another chain.
* @param destChainId Destination chain ID
* @param conf Confirmation level;
* @param to Address of contract to call on destination chain
* @param data ABI Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function xcall(uint64 destChainId, uint8 conf, address to, bytes calldata data, uint64 gasLimit) external payable;
/**
* @notice Submit a batch of XMsgs to be executed on this chain
* @param xsub An xchain submisison, including an attestation root w/ validator signatures,
* and a block header and message batch, proven against the attestation root.
*/
function xsubmit(XTypes.Submission calldata xsub) external;
/**
* @notice Returns the current network (supported chain IDs and shards)
*/
function network() external view returns (XTypes.Chain[] memory);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
import { XTypes } from "../libraries/XTypes.sol";
/**
* @title IOmniPortalSys
* @notice Defines syscall functions internal to Omni's cross-chain messaging protocol
*/
interface IOmniPortalSys {
/**
* @notice Emitted when a new validator set is added
* @param setId Validator set ID
*/
event ValidatorSetAdded(uint64 indexed setId);
/**
* @notice Add a new validator set.
* @dev Only callable via xcall from Omni's consensus chain
* @param valSetId Validator set id
* @param validators Validator set
*/
function addValidatorSet(uint64 valSetId, XTypes.Validator[] calldata validators) external;
/**
* @notice Set the network of supported chains & shards
* @dev Only callable via xcall from Omni's consensus chain
* @param network_ The new network
*/
function setNetwork(XTypes.Chain[] calldata network_) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title IOmniPortalAdmin
* @notice Defines the OmniPortal admin interface
*/
interface IOmniPortalAdmin {
/**
* @notice Emitted when the fee oracle is updated.
* @param oracle The new fee oracle address
*/
event FeeOracleSet(address oracle);
/**
* @notice Emited when fees are collected
* @param to The address the fees are collected to
* @param amount The amount of fees collected
*/
event FeesCollected(address indexed to, uint256 amount);
/**
* @notice Emitted when xmsgMinGasLimit is updated
* @param gasLimit The new xmsgMinGasLimit
*/
event XMsgMinGasLimitSet(uint64 gasLimit);
/**
* @notice Emitted when xmsgMaxGasLimit is updated
* @param gasLimit The new xmsgMaxGasLimit
*/
event XMsgMaxGasLimitSet(uint64 gasLimit);
/**
* @notice Emitted when xmsgMaxDataSize is updated
* @param size The new max size
*/
event XMsgMaxDataSizeSet(uint16 size);
/**
* @notice Emitted when xreceiptMaxErrorSize is updated
* @param size The new max size
*/
event XReceiptMaxErrorSizeSet(uint16 size);
/**
* @notice Emitted when the xsubValsetCutoff is updated
* @param cutoff The new cutoff
*/
event XSubValsetCutoffSet(uint8 cutoff);
/**
* @notice Emitted the portal is paused, all xcalls and xsubmissions
*/
event Paused();
/**
* @notice Emitted the portal is unpaused, all xcalls and xsubmissions
*/
event Unpaused();
/**
* @notice Emitted when all xcalls are paused
*/
event XCallPaused();
/**
* @notice Emitted when inbound xmsg offset is updated
*/
event InXMsgOffsetSet(uint64 indexed srcChainId, uint64 indexed shardId, uint64 offset);
/**
* @notice Emitted when all inbound xblock offset is updated
*/
event InXBlockOffsetSet(uint64 indexed srcChainId, uint64 indexed shardId, uint64 offset);
/**
* @notice Emitted when all xcalls are unpaused
*/
event XCallUnpaused();
/**
* @notice Emitted when all xsubmissions are paused
*/
event XSubmitPaused();
/**
* @notice Emitted when all xsubmissions are unpaused
*/
event XSubmitUnpaused();
/**
* @notice Emitted when xcalls to a specific chain are paused
* @param chainId The destination chain
*/
event XCallToPaused(uint64 indexed chainId);
/**
* @notice Emitted when xcalls to a specific chain are unpaused
* @param chainId The destination chain
*/
event XCallToUnpaused(uint64 indexed chainId);
/**
* @notice Emitted when xsubmissions from a specific chain are paused
* @param chainId The source chain
*/
event XSubmitFromPaused(uint64 indexed chainId);
/**
* @notice Emitted when xsubmissions from a specific chain are unpaused
* @param chainId The source chain
*/
event XSubmitFromUnpaused(uint64 indexed chainId);
/**
* @notice Set the inbound xmsg offset for a chain and shard
* @param sourceChainId Source chain ID
* @param shardId Shard ID
* @param offset New xmsg offset
*/
function setInXMsgOffset(uint64 sourceChainId, uint64 shardId, uint64 offset) external;
/**
* @notice Set the inbound xblock offset for a chain and shard
* @param sourceChainId Source chain ID
* @param shardId Shard ID
* @param offset New xblock offset
*/
function setInXBlockOffset(uint64 sourceChainId, uint64 shardId, uint64 offset) external;
/**
* @notice Set the fee oracle
*/
function setFeeOracle(address feeOracle) external;
/**
* @notice Transfer all collected fees to the give address
* @param to The address to transfer the fees to
*/
function collectFees(address to) external;
/**
* @notice Set the minimum gas limit for xmsg
*/
function setXMsgMinGasLimit(uint64 gasLimit) external;
/**
* @notice Set the maximum gas limit for xmsg
*/
function setXMsgMaxGasLimit(uint64 gasLimit) external;
/**
* @notice Set the maximum data bytes for xmsg
*/
function setXMsgMaxDataSize(uint16 numBytes) external;
/**
* @notice Set the maximum error bytes for xreceipt
*/
function setXReceiptMaxErrorSize(uint16 numBytes) external;
/**
* @notice Pause xcalls
*/
function pause() external;
/**
* @notice Unpause xcalls
*/
function unpause() external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.24;
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { XTypes } from "./XTypes.sol";
/**
* @title XBlockMerkleProof
* @dev Library for verifying XBlock merkle proofs
*/
library XBlockMerkleProof {
/// @dev Domain separation tag for XBlockHeaders, prepended to leaves before hashing and signing.
uint8 internal constant DST_XBLOCK_HEADER = 1;
/// @dev Domain separation tag for XMsgs, prepended to leaves before hashing and signing.
uint8 internal constant DST_XMSG = 2;
/**
* @notice Verifies that the provided xmsgs & multi proof produce an xmsg merkle root that, when
* combined with the xblock header, produces the provided root.
* @param root The root of the nested xblock merkle tree (xblock header + xmsg merkle root).
* @param blockHeader Xblock header.
* @param msgs Xmsgs to verify.
* @param msgProof Xmsg merkle proof.
* @param msgProofFlags Xmsg merkle proof flags.
* @return True if the msgs, msg proof & block header are valid, against the provided root.
*/
function verify(
bytes32 root,
XTypes.BlockHeader calldata blockHeader,
XTypes.Msg[] calldata msgs,
bytes32[] calldata msgProof,
bool[] calldata msgProofFlags
) internal pure returns (bool) {
bytes32[] memory rootProof = new bytes32[](1);
rootProof[0] = MerkleProof.processMultiProofCalldata(msgProof, msgProofFlags, _msgLeaves(msgs));
return MerkleProof.verify(rootProof, root, _blockHeaderLeaf(blockHeader));
}
/// @dev Convert xmsgs to leaf hashes
function _msgLeaves(XTypes.Msg[] calldata msgs) private pure returns (bytes32[] memory) {
bytes32[] memory leaves = new bytes32[](msgs.length);
for (uint256 i = 0; i < msgs.length; i++) {
leaves[i] = _leafHash(DST_XMSG, abi.encode(msgs[i]));
}
return leaves;
}
/// @dev Convert xblock header to leaf hash
function _blockHeaderLeaf(XTypes.BlockHeader calldata blockHeader) private pure returns (bytes32) {
return _leafHash(DST_XBLOCK_HEADER, abi.encode(blockHeader));
}
/// @dev Double hash leaves, as recommended by OpenZeppelin, to prevent second preimage attacks
/// Leaves must be double hashed in tree / proof construction
/// Callers must specify the domain separation tag of the leaf, which will be hashed in
function _leafHash(uint8 dst, bytes memory leaf) private pure returns (bytes32) {
return keccak256(bytes.concat(keccak256(abi.encodePacked(dst, leaf))));
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title XTypes
* @dev Defines xchain types, core to Omni's xchain messaging protocol. These
* types mirror those defined in omni/lib/xchain/types.go.
*/
library XTypes {
/**
* @notice A cross chain message - the product of an xcall. This matches the XMsg type used
* throughout Omni's cross-chain messaging protocol. Msg is used to construct and verify
* XSubmission merkle trees / proofs.
* @custom:field destChainId Chain ID of the destination chain
* @custom:field shardId Shard ID of the XStream (last byte is the confirmation level)
* @custom:field offset Monotonically incremented offset of Msg in source -> dest Stream
* @custom:field sender msg.sender of xcall on source chain
* @custom:field to Target address to call on destination chain
* @custom:field data Data to provide to call on destination chain
* @custom:field gasLimit Gas limit to use for call execution on destination chain
*/
struct Msg {
uint64 destChainId;
uint64 shardId;
uint64 offset;
address sender;
address to;
bytes data;
uint64 gasLimit;
}
/**
* @notice Msg context exposed during its execution to consuming xapps.
* @custom:field sourceChainId Chain ID of the source chain
* @custom:field sender msg.sender of xcall on source chain
*/
struct MsgContext {
uint64 sourceChainId;
address sender;
}
/**
* @notice BlockHeader of an XBlock.
* @custom:field sourceChainId Chain ID of the source chain
* @custom:field consensusChainId Chain ID of the Omni consensus chain
* @custom:field confLevel Confirmation level of the cross chain block
* @custom:field offset Offset of the cross chain block
* @custom:field sourceBlockHeight Height of the source chain block
* @custom:field sourceBlockHash Hash of the source chain block
*/
struct BlockHeader {
uint64 sourceChainId;
uint64 consensusChainId;
uint8 confLevel;
uint64 offset;
uint64 sourceBlockHeight;
bytes32 sourceBlockHash;
}
/**
* @notice The required parameters to submit xmsgs to an OmniPortal. Constructed by the relayer
* by watching Omni's consensus chain, and source chain blocks.
* @custom:field attestationRoot Merkle root of xchain block (XBlockRoot), attested to and signed by validators
* @custom:field validatorSetId Unique identifier of the validator set that attested to this root
* @custom:field blockHeader Block header, identifies xchain block
* @custom:field msgs Messages to execute
* @custom:field proof Multi proof of block header and messages, proven against attestationRoot
* @custom:field proofFlags Multi proof flags
* @custom:field signatures Array of validator signatures of the attestationRoot, and their public keys
*/
struct Submission {
bytes32 attestationRoot;
uint64 validatorSetId;
BlockHeader blockHeader;
Msg[] msgs;
bytes32[] proof;
bool[] proofFlags;
SigTuple[] signatures;
}
/**
* @notice A tuple of a validator's ethereum address and signature over some digest.
* @custom:field validatorAddr Validator ethereum address
* @custom:field signature Validator signature over some digest; Ethereum 65 bytes [R || S || V] format.
*/
struct SigTuple {
address validatorAddr;
bytes signature;
}
/**
* @notice An Omni validator, specified by their etheruem address and voting power.
* @custom:field addr Validator ethereum address
* @custom:field power Validator voting power
*/
struct Validator {
address addr;
uint64 power;
}
/**
* @notice A chain in the "omni network" specified by its chain ID and supported shards.
* @custom:field chainId Chain ID
* @custom:field shards Supported shards
*/
struct Chain {
uint64 chainId;
uint64[] shards;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.24;
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { XTypes } from "./XTypes.sol";
/**
* @title Quorum
* @dev Defines quorum verification logic.
*/
library Quorum {
/**
* @notice Verifies that the given percentage of the total power has signed the given digest.
* @param digest Signed hash
* @param sigs Signatures to verify, must be sorted by validator address
* @param validators Maps validator addresses to their voting power
* @param totalPower Total voting power
* @param qNumerator Numerator of the quorum threshold. Ex: 2/3 -> 2
* @param qDenominator Denominator of the quorum threshold. Ex: 2/3 -> 3
*/
function verify(
bytes32 digest,
XTypes.SigTuple[] calldata sigs,
mapping(address => uint64) storage validators,
uint64 totalPower,
uint8 qNumerator,
uint8 qDenominator
) internal view returns (bool) {
uint64 votedPower;
XTypes.SigTuple calldata sig;
for (uint256 i = 0; i < sigs.length; i++) {
sig = sigs[i];
if (i > 0) {
XTypes.SigTuple calldata prev = sigs[i - 1];
require(sig.validatorAddr > prev.validatorAddr, "Quorum: sigs not deduped/sorted");
}
require(_isValidSig(sig, digest), "Quorum: invalid signature");
votedPower += validators[sig.validatorAddr];
if (_isQuorum(votedPower, totalPower, qNumerator, qDenominator)) return true;
}
return false;
}
/// @dev True if SigTuple.sig is a valid ECDSA signature over the given digest for SigTuple.addr, else false.
function _isValidSig(XTypes.SigTuple calldata sig, bytes32 digest) internal pure returns (bool) {
return ECDSA.recover(digest, sig.signature) == sig.validatorAddr;
}
/// @dev True if votedPower exceeds the quorum threshold of numerator/denominator, else false.
function _isQuorum(uint64 votedPower, uint64 totalPower, uint8 numerator, uint8 denominator)
private
pure
returns (bool)
{
return votedPower * uint256(denominator) > totalPower * uint256(numerator);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title ConfLevel
* @notice XMsg confirmation levels. Matches ConfLevels in lib/xchain/types.go
* @dev We prefer explicit constants over Enums, because we want uint8 values to start at 1, not 0, as they do in
* lib/xchain/types.go, such that 0 can represent "unset". Note only latest and finalized levels are supported
* on-chain.
*/
library ConfLevel {
/**
* @notice XMsg confirmation level "latest", last byte of xmsg.shardId.
*/
uint8 internal constant Latest = 1;
/**
* @notice XMsg confirmation level "finalized", last byte of xmsg.shardId.
*/
uint8 internal constant Finalized = 4;
/**
* @notice Returns true if the given level is valid.
*/
function isValid(uint8 level) internal pure returns (bool) {
return level == Latest || level == Finalized;
}
/**
* @notice Returns broadcast shard version of the given level.
*/
function toBroadcastShard(uint8 level) internal pure returns (uint64) {
return uint64(level) | 0x0100;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.24;
/**
* @title PausableUpgradeable
* @notice Contract module which provides a way to pause certain functions by key.
* @dev We use a map of bytes32 key to bools, rather than uint256 bitmap, to allow keys to be generated dynamically.
* This allows for flexible pausing, but at higher gas cost.
*/
contract PausableUpgradeable {
/// @notice Emitted when a key is paused.
event Paused(bytes32 indexed key);
/// @notice Emitted when a key is unpaused.
event Unpaused(bytes32 indexed key);
/// @custom:storage-location erc7201:omni.storage.Pauseable
struct PauseableStorage {
mapping(bytes32 => bool) _paused;
}
// keccak256(abi.encode(uint256(keccak256("omni.storage.Pauseable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageSlot = 0xff37105740f03695c8f3597f3aff2b92fbe1c80abea3c28731ecff2efd693400;
function _getPauseableStorage() internal pure returns (PauseableStorage storage $) {
assembly {
$.slot := PausableStorageSlot
}
}
/**
* @dev Special key for pausing all keys.
*/
bytes32 public constant KeyPauseAll = keccak256("PAUSE_ALL");
/**
* @notice Pause by key.
*/
function _pause(bytes32 key) internal {
PauseableStorage storage $ = _getPauseableStorage();
require(!$._paused[key], "Pausable: paused");
$._paused[key] = true;
emit Paused(key);
}
/**
* @notice Unpause by key.
*/
function _unpause(bytes32 key) internal {
PauseableStorage storage $ = _getPauseableStorage();
require($._paused[key], "Pausable: not paused");
$._paused[key] = false;
emit Unpaused(key);
}
/**
* @notice Returns true if `key` is paused, or all keys are paused.
*/
function _isPaused(bytes32 key) internal view returns (bool) {
PauseableStorage storage $ = _getPauseableStorage();
return $._paused[KeyPauseAll] || $._paused[key];
}
/**
* @notice Returns true if either `key1` or `key2` is paused, or all keys are paused.
*/
function _isPaused(bytes32 key1, bytes32 key2) internal view returns (bool) {
PauseableStorage storage $ = _getPauseableStorage();
return $._paused[KeyPauseAll] || $._paused[key1] || $._paused[key2];
}
/**
* @notice Returns true if all keys are paused.
*/
function _isAllPaused() internal view returns (bool) {
PauseableStorage storage $ = _getPauseableStorage();
return $._paused[KeyPauseAll];
}
/**
* @notice Pause all keys.
*/
function _pauseAll() internal {
_pause(KeyPauseAll);
}
/**
* @notice Unpause all keys.
*/
function _unpauseAll() internal {
_unpause(KeyPauseAll);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title OmniPortalConstants
* @notice Constants used by the OmniPortal contract
*/
contract OmniPortalConstants {
/**
* @notice Numerator of the fraction of total validator power required to accept an XSubmission. Ex 2/3 -> 2
*/
uint8 public constant XSubQuorumNumerator = 2;
/**
* @notice Denominator of the fraction of total validator power required to accept an XSubmission. Ex 2/3 -> 3
*/
uint8 public constant XSubQuorumDenominator = 3;
/**
* @notice Action ID for xsubmissions, used as Pauseable key
*/
bytes32 public constant ActionXSubmit = keccak256("xsubmit");
/**
* @notice Action ID for xcalls, used as Pauseable key
*/
bytes32 public constant ActionXCall = keccak256("xcall");
/**
* @dev xmsg.destChainId for "broadcast" xcalls, intended for all portals
*/
uint64 internal constant BroadcastChainId = 0;
/**
* @dev xmsg.sender for xmsgs from Omni's consensus chain
*/
address internal constant CChainSender = address(0);
/**
* @dev xmsg.to for xcalls to be executed on the portal itself
*/
address internal constant VirtualPortalAddress = address(0);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.24;
import { IOmniPortal } from "../interfaces/IOmniPortal.sol";
import { IOmniPortalAdmin } from "../interfaces/IOmniPortalAdmin.sol";
import { XTypes } from "../libraries/XTypes.sol";
/**
* @title OmniPortalStorage
* @notice Storage layout for OmniPortal
*/
abstract contract OmniPortalStorage is IOmniPortal, IOmniPortalAdmin {
/**
* @notice Number of validator sets since the latest that can be used to validate an XSubmission
*/
uint8 public xsubValsetCutoff;
/**
* @notice Maxium number of bytes allowed in xreceipt result
*/
uint16 public xreceiptMaxErrorSize;
/**
* @notice Maximum number of bytes allowed in xmsg data
*/
uint16 public xmsgMaxDataSize;
/**
* @notice Maximum allowed xmsg gas limit
*/
uint64 public xmsgMaxGasLimit;
/**
* @notice Minimum allowed xmsg gas limit
*/
uint64 public xmsgMinGasLimit;
/**
* @notice ID of the latest validator set relayed to this portal from the consensus chain.
*/
uint64 public latestValSetId;
/**
* @notice Chain ID of Omni's EVM execution chain
*/
uint64 public omniChainId;
/**
* @notice Virtual Chain ID of Omni's consensus chain
*/
uint64 public omniCChainId;
/**
* @notice The address of the fee oracle contract
*/
address public feeOracle;
/**
* @notice A list of supported chains & shards.
*/
XTypes.Chain[] internal _network;
/**
* @notice Maps shard ID to true, if the shard is supported.
*/
mapping(uint64 => bool) public isSupportedShard;
/**
* @notice Maps chain ID to true, if the chain is supported.
*/
mapping(uint64 => bool) public isSupportedDest;
/**
* @notice Offset of the last outbound XMsg that was sent to destChainId in shardId
* Maps destChainId -> shardId -> offset.
*/
mapping(uint64 => mapping(uint64 => uint64)) public outXMsgOffset;
/**
* @notice Offset of the last inbound XMsg that was sent from sourceChainId in shardId
* Maps sourceChainId -> shardId -> offset.
*/
mapping(uint64 => mapping(uint64 => uint64)) public inXMsgOffset;
/**
* @notice The xblock offset of the last inbound XMsg that was received from sourceChainId in shardId
* Maps sourceChainId -> shardId -> xblockOffset.
*/
mapping(uint64 => mapping(uint64 => uint64)) public inXBlockOffset;
/**
* @notice Maps validator set id -> total power
*/
mapping(uint64 => uint64) public valSetTotalPower;
/**
* @notice Maps validator set id -> validator address -> power
*/
mapping(uint64 => mapping(address => uint64)) public valSet;
/**
* @notice The current XMsg being executed, exposed via xmsg() getter
* @dev Internal state + public getter preferred over public state with default getter
* so that we can use the XMsg struct type in the interface.
*/
XTypes.MsgContext internal _xmsg;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.20;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
if (leavesLen + proofLen != totalHashes + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
if (leavesLen + proofLen != totalHashes + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Sorts the pair (a, b) and hashes the result.
*/
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError, bytes32) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
File 4 of 4: SolverNetInbox
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.24;
import { OwnableRoles } from "solady/src/auth/OwnableRoles.sol";
import { ReentrancyGuard } from "solady/src/utils/ReentrancyGuard.sol";
import { Initializable } from "solady/src/utils/Initializable.sol";
import { DeployedAt } from "./util/DeployedAt.sol";
import { XAppBase } from "./lib/XAppBase.sol";
import { MailboxClient } from "./ext/MailboxClient.sol";
import { IERC7683 } from "./erc7683/IERC7683.sol";
import { ISolverNetInbox } from "./interfaces/ISolverNetInbox.sol";
import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol";
import { SolverNet } from "./lib/SolverNet.sol";
import { AddrUtils } from "./lib/AddrUtils.sol";
/**
* @title SolverNetInbox
* @notice Entrypoint and alt-mempool for user solve orders.
*/
contract SolverNetInbox is
OwnableRoles,
ReentrancyGuard,
Initializable,
DeployedAt,
XAppBase,
MailboxClient,
ISolverNetInbox
{
using SafeTransferLib for address;
using AddrUtils for address;
using AddrUtils for bytes32;
/**
* @notice Maximum number of calls and expenses in an order.
*/
uint8 internal constant MAX_ARRAY_SIZE = 32;
/**
* @notice Buffer for closing orders after fill deadline to give Omni Core relayer time to act.
*/
uint256 internal constant CLOSE_BUFFER = 6 hours;
/**
* @notice Role for solvers.
* @dev _ROLE_0 evaluates to '1'.
*/
uint256 internal constant SOLVER = _ROLE_0;
/**
* @notice Typehash for the OrderData struct.
*/
bytes32 internal constant ORDERDATA_TYPEHASH = keccak256(
"OrderData(address owner,uint64 destChainId,Deposit deposit,Call[] calls,TokenExpense[] expenses)Deposit(address token,uint96 amount)Call(address target,bytes4 selector,uint256 value,bytes params)TokenExpense(address spender,address token,uint96 amount)"
);
/**
* @notice Action ID for xsubmissions, used as Pauseable key in OmniPortal
*/
bytes32 internal constant ACTION_XSUBMIT = keccak256("xsubmit");
/**
* @notice Key for pausing the `open` function.
*/
bytes32 internal constant OPEN = keccak256("OPEN");
/**
* @notice Key for pausing the `close` function.
*/
bytes32 internal constant CLOSE = keccak256("CLOSE");
uint8 internal constant NONE_PAUSED = 0;
uint8 internal constant OPEN_PAUSED = 1;
uint8 internal constant CLOSE_PAUSED = 2;
uint8 internal constant ALL_PAUSED = 3;
/**
* @dev Incremental order offset for source inbox orders.
*/
uint248 internal _offset;
/**
* @notice Pause state.
* @dev 0 = no pause, 1 = open paused, 2 = close paused, 3 = all paused.
*/
uint8 public pauseState;
/**
* @notice Addresses of the outbox contracts.
*/
mapping(uint64 chainId => address outbox) internal _outboxes;
/**
* @notice Map order ID to header parameters.
* @dev (owner, destChainId, fillDeadline)
*/
mapping(bytes32 id => SolverNet.Header) internal _orderHeader;
/**
* @notice Map order ID to deposit parameters.
* @dev (token, amount)
*/
mapping(bytes32 id => SolverNet.Deposit) internal _orderDeposit;
/**
* @notice Map order ID to call parameters.
* @dev (target, selector, value, params)
*/
mapping(bytes32 id => SolverNet.Call[]) internal _orderCalls;
/**
* @notice Map order ID to expense parameters.
* @dev (spender, token, amount)
*/
mapping(bytes32 id => SolverNet.TokenExpense[]) internal _orderExpenses;
/**
* @notice Map order ID to order parameters.
*/
mapping(bytes32 id => OrderState) internal _orderState;
/**
* @notice Map order ID to order offset.
*/
mapping(bytes32 id => uint248) internal _orderOffset;
/**
* @notice Map user to nonce.
*/
mapping(address user => uint256 nonce) internal _userNonce;
/**
* @notice Modifier to ensure contract functions are not paused.
*/
modifier whenNotPaused(bytes32 pauseKey) {
uint8 _pauseState = pauseState;
if (_pauseState != NONE_PAUSED) {
if (_pauseState == OPEN_PAUSED && pauseKey == OPEN) revert IsPaused();
if (_pauseState == CLOSE_PAUSED && pauseKey == CLOSE) revert IsPaused();
if (_pauseState == ALL_PAUSED) revert AllPaused();
}
_;
}
/**
* @notice Constructor sets the OmniPortal, and Hyperlane Mailbox contract addresses.
* @param omni_ Address of the OmniPortal.
* @param mailbox_ Address of the Hyperlane Mailbox.
*/
constructor(address omni_, address mailbox_) XAppBase(omni_) MailboxClient(mailbox_) {
_disableInitializers();
}
/**
* @notice Initialize the contract's owner and solver.
* @dev Used instead of constructor as we want to use the transparent upgradeable proxy pattern.
* `reinitializer(2)` is set so fresh deployments lock out `initializeV2`, which is only needed to set state on existing deployments.
* @param owner_ Address of the owner.
* @param solver_ Address of the solver.
*/
function initialize(address owner_, address solver_) external reinitializer(2) {
_initializeOwner(owner_);
_grantRoles(solver_, SOLVER);
}
/**
* @notice Set the outbox address for the local chain.
* @dev Necessary as a local assignment in _outboxes is required. Only needed on existing deployments.
* @param outbox Address of the outbox.
*/
function initializeV2(address outbox) external reinitializer(2) {
_outboxes[uint64(block.chainid)] = outbox;
emit OutboxSet(uint64(block.chainid), outbox);
}
/**
* @notice Returns the outbox address for the given chain ID.
* @param chainId ID of the chain.
* @return outbox Outbox address.
*/
function getOutbox(uint64 chainId) external view returns (address) {
return _outboxes[chainId];
}
/**
* @notice Pause the `open` function, preventing new orders from being opened.
* @dev Cannot override ALL_PAUSED state.
* @param pause True to pause, false to unpause.
*/
function pauseOpen(bool pause) external onlyOwnerOrRoles(SOLVER) {
_setPauseState(OPEN, pause);
}
/**
* @notice Pause the `close` function, preventing orders from being closed by users.
* @dev `close` should only be paused if the Omni Core relayer is not available.
* @dev Cannot override ALL_PAUSED state.
* @param pause True to pause, false to unpause.
*/
function pauseClose(bool pause) external onlyOwnerOrRoles(SOLVER) {
_setPauseState(CLOSE, pause);
}
/**
* @notice Pause open and close functions.
* @dev Can override OPEN_PAUSED or CLOSE_PAUSED states.
* @param pause True to pause, false to unpause.
*/
function pauseAll(bool pause) external onlyOwnerOrRoles(SOLVER) {
pause ? pauseState = ALL_PAUSED : pauseState = NONE_PAUSED;
emit Paused(OPEN, pause, pauseState);
emit Paused(CLOSE, pause, pauseState);
}
/**
* @notice Set the outbox addresses for the given chain IDs.
* @param chainIds IDs of the chains.
* @param outboxes Addresses of the outboxes.
*/
function setOutboxes(uint64[] calldata chainIds, address[] calldata outboxes) external onlyOwner {
if (chainIds.length != outboxes.length) revert InvalidArrayLength();
for (uint256 i; i < chainIds.length; ++i) {
_outboxes[chainIds[i]] = outboxes[i];
emit OutboxSet(chainIds[i], outboxes[i]);
}
}
/**
* @notice Returns the order, its state, and offset with the given ID.
* @param id ID of the order.
*/
function getOrder(bytes32 id)
external
view
returns (ResolvedCrossChainOrder memory resolved, OrderState memory state, uint248 offset)
{
SolverNet.Order memory orderData = _getOrder(id);
return (_resolve(orderData, id), _orderState[id], _orderOffset[id]);
}
/**
* @notice Returns the order ID for the given user and nonce.
* @param user Address of the user.
* @param nonce Nonce of the order.
*/
function getOrderId(address user, uint256 nonce) external view returns (bytes32) {
return _getOrderId(user, nonce);
}
/**
* @notice Returns the next order ID for the given user.
* @param user Address of the user.
*/
function getNextOrderId(address user) external view returns (bytes32) {
return _getOrderId(user, _userNonce[user]);
}
/**
* @notice Returns the nonce for the given user.
* @param user Address of the user.
*/
function getUserNonce(address user) external view returns (uint256) {
return _userNonce[user];
}
/**
* @notice Returns the order offset of the latest order opened at this inbox.
*/
function getLatestOrderOffset() external view returns (uint248) {
return _offset;
}
/**
* @dev Validate the onchain order.
* @param order OnchainCrossChainOrder to validate.
*/
function validate(OnchainCrossChainOrder calldata order) external view returns (bool) {
_validate(order);
return true;
}
/**
* @notice Resolve the onchain order with validation.
* @param order OnchainCrossChainOrder to resolve.
*/
function resolve(OnchainCrossChainOrder calldata order) public view returns (ResolvedCrossChainOrder memory) {
SolverNet.Order memory orderData = _validate(order);
address user = orderData.header.owner;
return _resolve(orderData, _getOrderId(user, _userNonce[user]));
}
/**
* @notice Open an order to execute a call on another chain, backed by deposits.
* @dev Token deposits are transferred from msg.sender to this inbox.
* @param order OnchainCrossChainOrder to open.
*/
function open(OnchainCrossChainOrder calldata order) external payable whenNotPaused(OPEN) nonReentrant {
SolverNet.Order memory orderData = _validate(order);
_onchainDeposit(orderData.deposit);
ResolvedCrossChainOrder memory resolved = _openOrder(orderData);
emit FillOriginData(
resolved.orderId, abi.decode(resolved.fillInstructions[0].originData, (SolverNet.FillOriginData))
);
emit Open(resolved.orderId, resolved);
}
/**
* @notice Reject an open order and refund deposits.
* @dev Only a whitelisted solver can reject.
* @param id ID of the order.
* @param reason Reason code for rejection.
*/
function reject(bytes32 id, uint8 reason) external onlyRoles(SOLVER) nonReentrant {
OrderState memory state = _orderState[id];
if (reason == 0) revert InvalidReason();
if (state.status != Status.Pending) revert OrderNotPending();
_upsertOrder(id, Status.Rejected, reason, msg.sender);
_transferDeposit(id, _orderHeader[id].owner);
_purgeState(id, Status.Rejected);
emit Rejected(id, msg.sender, reason);
}
/**
* @notice Close order and refund deposits after fill deadline has elapsed.
* @dev Only order initiator can close.
* @param id ID of the order.
*/
function close(bytes32 id) external whenNotPaused(CLOSE) nonReentrant {
OrderState memory state = _orderState[id];
SolverNet.Header memory header = _orderHeader[id];
uint256 buffer = header.destChainId == block.chainid ? 0 : CLOSE_BUFFER;
if (state.status != Status.Pending) revert OrderNotPending();
if (header.owner != msg.sender) revert Unauthorized();
if (header.fillDeadline + buffer >= block.timestamp) revert OrderStillValid();
_upsertOrder(id, Status.Closed, 0, msg.sender);
_transferDeposit(id, header.owner);
_purgeState(id, Status.Closed);
emit Closed(id);
}
/**
* @notice Fill an order via Omni Core.
* @dev Only callable by the outbox.
* @param id ID of the order.
* @param fillHash Hash of fill instructions origin data.
* @param creditedTo Address deposits are credited to, provided by the filler.
*/
function markFilled(bytes32 id, bytes32 fillHash, address creditedTo) external xrecv {
uint64 origin = xmsg.sourceChainId == 0 ? uint64(block.chainid) : xmsg.sourceChainId;
address sender = xmsg.sender == address(0) ? msg.sender : xmsg.sender;
_markFilled(id, fillHash, creditedTo, origin, sender);
}
/**
* @notice Fill an order via Hyperlane.
* @dev Only callable by the outbox.
* @param origin The origin domain
* @param sender The sender address
* @param message The message
*/
function handle(uint32 origin, bytes32 sender, bytes calldata message) external payable override onlyMailbox {
(bytes32 id, bytes32 fillHash, address creditedTo) = abi.decode(message, (bytes32, bytes32, address));
_markFilled(id, fillHash, creditedTo, origin, sender.toAddress());
}
/**
* @notice Claim deposits for a filled order.
* @param id ID of the order.
* @param to Address to send deposits to.
*/
function claim(bytes32 id, address to) external nonReentrant {
OrderState memory state = _orderState[id];
if (state.status != Status.Filled) revert OrderNotFilled();
if (state.updatedBy != msg.sender) revert Unauthorized();
_upsertOrder(id, Status.Claimed, 0, msg.sender);
_transferDeposit(id, to);
_purgeState(id, Status.Claimed);
emit Claimed(id, msg.sender, to);
}
/**
* @dev Return the order for the given ID.
* @param id ID of the order.
*/
function _getOrder(bytes32 id) internal view returns (SolverNet.Order memory) {
return SolverNet.Order({
header: _orderHeader[id],
calls: _orderCalls[id],
deposit: _orderDeposit[id],
expenses: _orderExpenses[id]
});
}
/**
* @dev Parse and return order data, validate correctness.
* @param order OnchainCrossChainOrder to parse
*/
function _validate(OnchainCrossChainOrder calldata order) internal view returns (SolverNet.Order memory) {
// Validate OnchainCrossChainOrder
if (order.fillDeadline <= block.timestamp) revert InvalidFillDeadline();
if (order.orderDataType != ORDERDATA_TYPEHASH) revert InvalidOrderTypehash();
if (order.orderData.length == 0) revert InvalidOrderData();
SolverNet.OrderData memory orderData = abi.decode(order.orderData, (SolverNet.OrderData));
// Validate SolverNet.OrderData.Header fields
if (orderData.owner == address(0)) orderData.owner = msg.sender;
if (orderData.destChainId == 0) revert InvalidChainId();
SolverNet.Header memory header = SolverNet.Header({
owner: orderData.owner,
destChainId: orderData.destChainId,
fillDeadline: order.fillDeadline
});
// Validate SolverNet.OrderData.Call
SolverNet.Call[] memory calls = orderData.calls;
if (calls.length == 0) revert InvalidMissingCalls();
if (calls.length > MAX_ARRAY_SIZE) revert InvalidArrayLength();
// Validate SolverNet.OrderData.Expenses
SolverNet.TokenExpense[] memory expenses = orderData.expenses;
if (expenses.length > MAX_ARRAY_SIZE) revert InvalidArrayLength();
for (uint256 i; i < expenses.length; ++i) {
if (expenses[i].token == address(0)) revert InvalidExpenseToken();
if (expenses[i].amount == 0) revert InvalidExpenseAmount();
}
return SolverNet.Order({ header: header, calls: calls, deposit: orderData.deposit, expenses: expenses });
}
/**
* @dev Derive the maxSpent Output for the order.
* @param orderData Order data to derive from.
*/
function _deriveMaxSpent(SolverNet.Order memory orderData) internal pure returns (IERC7683.Output[] memory) {
SolverNet.Header memory header = orderData.header;
SolverNet.Call[] memory calls = orderData.calls;
SolverNet.TokenExpense[] memory expenses = orderData.expenses;
uint256 totalNativeValue;
for (uint256 i; i < calls.length; ++i) {
if (calls[i].value > 0) totalNativeValue += calls[i].value;
}
// maxSpent recipient field is unused in SolverNet, was previously set to outbox as a placeholder
IERC7683.Output[] memory maxSpent =
new IERC7683.Output[](totalNativeValue > 0 ? expenses.length + 1 : expenses.length);
for (uint256 i; i < expenses.length; ++i) {
maxSpent[i] = IERC7683.Output({
token: expenses[i].token.toBytes32(),
amount: expenses[i].amount,
recipient: bytes32(0),
chainId: header.destChainId
});
}
if (totalNativeValue > 0) {
maxSpent[expenses.length] = IERC7683.Output({
token: bytes32(0),
amount: totalNativeValue,
recipient: bytes32(0),
chainId: header.destChainId
});
}
return maxSpent;
}
/**
* @dev Derive the minReceived Output for the order.
* @param orderData Order data to derive from.
*/
function _deriveMinReceived(SolverNet.Order memory orderData) internal view returns (IERC7683.Output[] memory) {
SolverNet.Deposit memory deposit = orderData.deposit;
IERC7683.Output[] memory minReceived = new IERC7683.Output[](deposit.amount > 0 ? 1 : 0);
if (deposit.amount > 0) {
minReceived[0] = IERC7683.Output({
token: deposit.token.toBytes32(),
amount: deposit.amount,
recipient: bytes32(0),
chainId: block.chainid
});
}
return minReceived;
}
/**
* @dev Derive the fillInstructions for the order.
* @param orderData Order data to derive from.
*/
function _deriveFillInstructions(SolverNet.Order memory orderData)
internal
view
returns (IERC7683.FillInstruction[] memory)
{
SolverNet.Header memory header = orderData.header;
SolverNet.Call[] memory calls = orderData.calls;
SolverNet.TokenExpense[] memory expenses = orderData.expenses;
IERC7683.FillInstruction[] memory fillInstructions = new IERC7683.FillInstruction[](1);
fillInstructions[0] = IERC7683.FillInstruction({
destinationChainId: header.destChainId,
destinationSettler: _outboxes[header.destChainId].toBytes32(),
originData: abi.encode(
SolverNet.FillOriginData({
srcChainId: uint64(block.chainid),
destChainId: header.destChainId,
fillDeadline: header.fillDeadline,
calls: calls,
expenses: expenses
})
)
});
return fillInstructions;
}
/**
* @dev Resolve the order without validation.
* @param orderData Order data to resolve.
*/
function _resolve(SolverNet.Order memory orderData, bytes32 id)
internal
view
returns (ResolvedCrossChainOrder memory)
{
SolverNet.Header memory header = orderData.header;
IERC7683.Output[] memory maxSpent = _deriveMaxSpent(orderData);
IERC7683.Output[] memory minReceived = _deriveMinReceived(orderData);
IERC7683.FillInstruction[] memory fillInstructions = _deriveFillInstructions(orderData);
return ResolvedCrossChainOrder({
user: header.owner,
originChainId: block.chainid,
openDeadline: 0,
fillDeadline: header.fillDeadline,
orderId: id,
maxSpent: maxSpent,
minReceived: minReceived,
fillInstructions: fillInstructions
});
}
/**
* @notice Validate and intake an ERC20 or native deposit.
* @param deposit Deposit to process.
*/
function _onchainDeposit(SolverNet.Deposit memory deposit) internal {
if (deposit.token == address(0)) {
if (msg.value != deposit.amount) revert InvalidNativeDeposit();
} else {
uint256 balance = deposit.token.balanceOf(address(this));
deposit.token.safeTransferFrom(msg.sender, address(this), deposit.amount);
// If we received less tokens than expected (max transfer value override or fee on transfer), revert
if (deposit.token.balanceOf(address(this)) < balance + deposit.amount) revert InvalidERC20Deposit();
}
}
/**
* @dev Opens a new order by initializing its state.
* @param orderData Order data to open.
*/
function _openOrder(SolverNet.Order memory orderData) internal returns (ResolvedCrossChainOrder memory resolved) {
address user = orderData.header.owner;
bytes32 id = _getOrderId(user, _userNonce[user]++);
resolved = _resolve(orderData, id);
_orderHeader[id] = orderData.header;
_orderDeposit[id] = orderData.deposit;
_orderOffset[id] = _incrementOffset();
for (uint256 i; i < orderData.calls.length; ++i) {
_orderCalls[id].push(orderData.calls[i]);
}
for (uint256 i; i < orderData.expenses.length; ++i) {
_orderExpenses[id].push(orderData.expenses[i]);
}
_upsertOrder(id, Status.Pending, 0, msg.sender);
return resolved;
}
/**
* @dev Mark an order as filled.
* @param id ID of the order.
* @param fillHash Hash of fill instructions origin data.
* @param creditedTo Address deposits are credited to, provided by the filler.
* @param origin Origin chain ID.
* @param sender Remote sender address.
*/
function _markFilled(bytes32 id, bytes32 fillHash, address creditedTo, uint64 origin, address sender) internal {
SolverNet.Header memory header = _orderHeader[id];
OrderState memory state = _orderState[id];
if (state.status != Status.Pending) revert OrderNotPending();
if (origin != header.destChainId) revert WrongSourceChain();
if (sender != _outboxes[origin]) revert Unauthorized();
// Ensure reported fill hash matches origin data
// We are temporarily supporting both so no in-flight order settlements are rejected during the transition
(bytes32 fillhash, SolverNet.FillOriginData memory fillOriginData) = _deprecatedFillHash(id);
if (fillHash != fillhash) {
if (fillHash != _fillHash(id, fillOriginData)) {
revert WrongFillHash();
}
}
_upsertOrder(id, Status.Filled, 0, creditedTo);
_purgeState(id, Status.Filled);
emit Filled(id, fillHash, creditedTo);
}
/**
* @dev Transfer deposit to recipient. Used for both refunds and claims.
* @param id ID of the order.
* @param to Address to send deposits to.
*/
function _transferDeposit(bytes32 id, address to) internal {
SolverNet.Deposit memory deposit = _orderDeposit[id];
if (deposit.amount > 0) {
if (deposit.token == address(0)) to.safeTransferETH(deposit.amount);
else deposit.token.safeTransfer(to, deposit.amount);
}
}
/**
* @dev Update or insert order state by id.
* @param id ID of the order.
* @param status Status to upsert.
* @param rejectReason Reason code for rejecting the order, if rejected.
* @param updatedBy Address updating the order.
*/
function _upsertOrder(bytes32 id, Status status, uint8 rejectReason, address updatedBy) internal {
uint8 _rejectReason = _orderState[id].rejectReason;
_orderState[id] = OrderState({
status: status,
rejectReason: rejectReason > 0 ? rejectReason : _rejectReason,
timestamp: uint32(block.timestamp),
updatedBy: updatedBy
});
}
/**
* @dev Purge order state after it is no longer needed.
* @param id ID of the order.
* @param status Status of the order.
*/
function _purgeState(bytes32 id, Status status) internal {
if (status == Status.Pending) return;
if (status != Status.Filled) delete _orderDeposit[id];
if (status != Status.Claimed) {
delete _orderHeader[id];
delete _orderCalls[id];
delete _orderExpenses[id];
}
}
/**
* @dev Derive order ID from user and nonce.
* @param user Address of the user.
* @param nonce Nonce of the order.
*/
function _getOrderId(address user, uint256 nonce) internal view returns (bytes32) {
return keccak256(abi.encode(user, nonce, block.chainid));
}
/**
* @dev Increment and return the next order offset.
*/
function _incrementOffset() internal returns (uint248) {
return ++_offset;
}
/**
* @dev Returns old fill hash. Used to discern fulfillment.
* @param orderId ID of the order.
*/
function _deprecatedFillHash(bytes32 orderId) internal view returns (bytes32, SolverNet.FillOriginData memory) {
SolverNet.Header memory header = _orderHeader[orderId];
SolverNet.Call[] memory calls = _orderCalls[orderId];
SolverNet.TokenExpense[] memory expenses = _orderExpenses[orderId];
SolverNet.FillOriginData memory fillOriginData = SolverNet.FillOriginData({
srcChainId: uint64(block.chainid),
destChainId: header.destChainId,
fillDeadline: header.fillDeadline,
calls: calls,
expenses: expenses
});
return (keccak256(abi.encode(orderId, abi.encode(fillOriginData))), fillOriginData);
}
/**
* @dev Return fill hash without unnecessary double encoding.
* We will transition to this hash method, where this will eventually incorporate state logic from _deprecatedFillHash.
* @param orderId ID of the order.
* @param fillOriginData Fill origin data to hash. (prevents having to derive it again)
*/
function _fillHash(bytes32 orderId, SolverNet.FillOriginData memory fillOriginData)
internal
pure
returns (bytes32)
{
return keccak256(abi.encode(orderId, fillOriginData));
}
/**
* @notice Pause the `open` or `close` function
* @dev Cannot override ALL_PAUSED state
* @param key OPEN or CLOSE pause key
* @param pause True to pause, false to unpause
*/
function _setPauseState(bytes32 key, bool pause) internal {
uint8 _pauseState = pauseState;
if (_pauseState == ALL_PAUSED) revert AllPaused();
uint8 targetState = key == OPEN ? OPEN_PAUSED : CLOSE_PAUSED;
if (pause ? _pauseState == targetState : _pauseState != targetState) revert IsPaused();
pauseState = pause ? targetState : NONE_PAUSED;
emit Paused(key, pause, pauseState);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Ownable} from "./Ownable.sol";
/// @notice Simple single owner and multiroles authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/OwnableRoles.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 OwnableRoles is Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The `user`'s roles is updated to `roles`.
/// Each bit of `roles` represents whether the role is set.
event RolesUpdated(address indexed user, uint256 indexed roles);
/// @dev `keccak256(bytes("RolesUpdated(address,uint256)"))`.
uint256 private constant _ROLES_UPDATED_EVENT_SIGNATURE =
0x715ad5ce61fc9595c7b415289d59cf203f23a94fa06f04af7e489a0a76e1fe26;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The role slot of `user` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _ROLE_SLOT_SEED))
/// let roleSlot := keccak256(0x00, 0x20)
/// ```
/// This automatically ignores the upper bits of the `user` in case
/// they are not clean, as well as keep the `keccak256` under 32-bytes.
///
/// Note: This is equivalent to `uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))`.
uint256 private constant _ROLE_SLOT_SEED = 0x8b78c6d8;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Overwrite the roles directly without authorization guard.
function _setRoles(address user, uint256 roles) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Store the new value.
sstore(keccak256(0x0c, 0x20), roles)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), roles)
}
}
/// @dev Updates the roles directly without authorization guard.
/// If `on` is true, each set bit of `roles` will be turned on,
/// otherwise, each set bit of `roles` will be turned off.
function _updateRoles(address user, uint256 roles, bool on) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
let roleSlot := keccak256(0x0c, 0x20)
// Load the current value.
let current := sload(roleSlot)
// Compute the updated roles if `on` is true.
let updated := or(current, roles)
// Compute the updated roles if `on` is false.
// Use `and` to compute the intersection of `current` and `roles`,
// `xor` it with `current` to flip the bits in the intersection.
if iszero(on) { updated := xor(current, and(current, roles)) }
// Then, store the new value.
sstore(roleSlot, updated)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), updated)
}
}
/// @dev Grants the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn on.
function _grantRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, true);
}
/// @dev Removes the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn off.
function _removeRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, false);
}
/// @dev Throws if the sender does not have any of the `roles`.
function _checkRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Throws if the sender is not the owner,
/// and does not have any of the `roles`.
/// Checks for ownership first, then lazily checks for roles.
function _checkOwnerOrRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Throws if the sender does not have any of the `roles`,
/// and is not the owner.
/// Checks for roles first, then lazily checks for ownership.
function _checkRolesOrOwner(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _rolesFromOrdinals(uint8[] memory ordinals) internal pure returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
for { let i := shl(5, mload(ordinals)) } i { i := sub(i, 0x20) } {
// We don't need to mask the values of `ordinals`, as Solidity
// cleans dirty upper bits when storing variables into memory.
roles := or(shl(mload(add(ordinals, i)), 1), roles)
}
}
}
/// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ordinalsFromRoles(uint256 roles) internal pure returns (uint8[] memory ordinals) {
/// @solidity memory-safe-assembly
assembly {
// Grab the pointer to the free memory.
ordinals := mload(0x40)
let ptr := add(ordinals, 0x20)
let o := 0
// The absence of lookup tables, De Bruijn, etc., here is intentional for
// smaller bytecode, as this function is not meant to be called on-chain.
for { let t := roles } 1 {} {
mstore(ptr, o)
// `shr` 5 is equivalent to multiplying by 0x20.
// Push back into the ordinals array if the bit is set.
ptr := add(ptr, shl(5, and(t, 1)))
o := add(o, 1)
t := shr(o, roles)
if iszero(t) { break }
}
// Store the length of `ordinals`.
mstore(ordinals, shr(5, sub(ptr, add(ordinals, 0x20))))
// Allocate the memory.
mstore(0x40, ptr)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to grant `user` `roles`.
/// If the `user` already has a role, then it will be an no-op for the role.
function grantRoles(address user, uint256 roles) public payable virtual onlyOwner {
_grantRoles(user, roles);
}
/// @dev Allows the owner to remove `user` `roles`.
/// If the `user` does not have a role, then it will be an no-op for the role.
function revokeRoles(address user, uint256 roles) public payable virtual onlyOwner {
_removeRoles(user, roles);
}
/// @dev Allow the caller to remove their own roles.
/// If the caller does not have a role, then it will be an no-op for the role.
function renounceRoles(uint256 roles) public payable virtual {
_removeRoles(msg.sender, roles);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the roles of `user`.
function rolesOf(address user) public view virtual returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Load the stored value.
roles := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns whether `user` has any of `roles`.
function hasAnyRole(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles != 0;
}
/// @dev Returns whether `user` has all of `roles`.
function hasAllRoles(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles == roles;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by an account with `roles`.
modifier onlyRoles(uint256 roles) virtual {
_checkRoles(roles);
_;
}
/// @dev Marks a function as only callable by the owner or by an account
/// with `roles`. Checks for ownership first, then lazily checks for roles.
modifier onlyOwnerOrRoles(uint256 roles) virtual {
_checkOwnerOrRoles(roles);
_;
}
/// @dev Marks a function as only callable by an account with `roles`
/// or the owner. Checks for roles first, then lazily checks for ownership.
modifier onlyRolesOrOwner(uint256 roles) virtual {
_checkRolesOrOwner(roles);
_;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ROLE CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// IYKYK
uint256 internal constant _ROLE_0 = 1 << 0;
uint256 internal constant _ROLE_1 = 1 << 1;
uint256 internal constant _ROLE_2 = 1 << 2;
uint256 internal constant _ROLE_3 = 1 << 3;
uint256 internal constant _ROLE_4 = 1 << 4;
uint256 internal constant _ROLE_5 = 1 << 5;
uint256 internal constant _ROLE_6 = 1 << 6;
uint256 internal constant _ROLE_7 = 1 << 7;
uint256 internal constant _ROLE_8 = 1 << 8;
uint256 internal constant _ROLE_9 = 1 << 9;
uint256 internal constant _ROLE_10 = 1 << 10;
uint256 internal constant _ROLE_11 = 1 << 11;
uint256 internal constant _ROLE_12 = 1 << 12;
uint256 internal constant _ROLE_13 = 1 << 13;
uint256 internal constant _ROLE_14 = 1 << 14;
uint256 internal constant _ROLE_15 = 1 << 15;
uint256 internal constant _ROLE_16 = 1 << 16;
uint256 internal constant _ROLE_17 = 1 << 17;
uint256 internal constant _ROLE_18 = 1 << 18;
uint256 internal constant _ROLE_19 = 1 << 19;
uint256 internal constant _ROLE_20 = 1 << 20;
uint256 internal constant _ROLE_21 = 1 << 21;
uint256 internal constant _ROLE_22 = 1 << 22;
uint256 internal constant _ROLE_23 = 1 << 23;
uint256 internal constant _ROLE_24 = 1 << 24;
uint256 internal constant _ROLE_25 = 1 << 25;
uint256 internal constant _ROLE_26 = 1 << 26;
uint256 internal constant _ROLE_27 = 1 << 27;
uint256 internal constant _ROLE_28 = 1 << 28;
uint256 internal constant _ROLE_29 = 1 << 29;
uint256 internal constant _ROLE_30 = 1 << 30;
uint256 internal constant _ROLE_31 = 1 << 31;
uint256 internal constant _ROLE_32 = 1 << 32;
uint256 internal constant _ROLE_33 = 1 << 33;
uint256 internal constant _ROLE_34 = 1 << 34;
uint256 internal constant _ROLE_35 = 1 << 35;
uint256 internal constant _ROLE_36 = 1 << 36;
uint256 internal constant _ROLE_37 = 1 << 37;
uint256 internal constant _ROLE_38 = 1 << 38;
uint256 internal constant _ROLE_39 = 1 << 39;
uint256 internal constant _ROLE_40 = 1 << 40;
uint256 internal constant _ROLE_41 = 1 << 41;
uint256 internal constant _ROLE_42 = 1 << 42;
uint256 internal constant _ROLE_43 = 1 << 43;
uint256 internal constant _ROLE_44 = 1 << 44;
uint256 internal constant _ROLE_45 = 1 << 45;
uint256 internal constant _ROLE_46 = 1 << 46;
uint256 internal constant _ROLE_47 = 1 << 47;
uint256 internal constant _ROLE_48 = 1 << 48;
uint256 internal constant _ROLE_49 = 1 << 49;
uint256 internal constant _ROLE_50 = 1 << 50;
uint256 internal constant _ROLE_51 = 1 << 51;
uint256 internal constant _ROLE_52 = 1 << 52;
uint256 internal constant _ROLE_53 = 1 << 53;
uint256 internal constant _ROLE_54 = 1 << 54;
uint256 internal constant _ROLE_55 = 1 << 55;
uint256 internal constant _ROLE_56 = 1 << 56;
uint256 internal constant _ROLE_57 = 1 << 57;
uint256 internal constant _ROLE_58 = 1 << 58;
uint256 internal constant _ROLE_59 = 1 << 59;
uint256 internal constant _ROLE_60 = 1 << 60;
uint256 internal constant _ROLE_61 = 1 << 61;
uint256 internal constant _ROLE_62 = 1 << 62;
uint256 internal constant _ROLE_63 = 1 << 63;
uint256 internal constant _ROLE_64 = 1 << 64;
uint256 internal constant _ROLE_65 = 1 << 65;
uint256 internal constant _ROLE_66 = 1 << 66;
uint256 internal constant _ROLE_67 = 1 << 67;
uint256 internal constant _ROLE_68 = 1 << 68;
uint256 internal constant _ROLE_69 = 1 << 69;
uint256 internal constant _ROLE_70 = 1 << 70;
uint256 internal constant _ROLE_71 = 1 << 71;
uint256 internal constant _ROLE_72 = 1 << 72;
uint256 internal constant _ROLE_73 = 1 << 73;
uint256 internal constant _ROLE_74 = 1 << 74;
uint256 internal constant _ROLE_75 = 1 << 75;
uint256 internal constant _ROLE_76 = 1 << 76;
uint256 internal constant _ROLE_77 = 1 << 77;
uint256 internal constant _ROLE_78 = 1 << 78;
uint256 internal constant _ROLE_79 = 1 << 79;
uint256 internal constant _ROLE_80 = 1 << 80;
uint256 internal constant _ROLE_81 = 1 << 81;
uint256 internal constant _ROLE_82 = 1 << 82;
uint256 internal constant _ROLE_83 = 1 << 83;
uint256 internal constant _ROLE_84 = 1 << 84;
uint256 internal constant _ROLE_85 = 1 << 85;
uint256 internal constant _ROLE_86 = 1 << 86;
uint256 internal constant _ROLE_87 = 1 << 87;
uint256 internal constant _ROLE_88 = 1 << 88;
uint256 internal constant _ROLE_89 = 1 << 89;
uint256 internal constant _ROLE_90 = 1 << 90;
uint256 internal constant _ROLE_91 = 1 << 91;
uint256 internal constant _ROLE_92 = 1 << 92;
uint256 internal constant _ROLE_93 = 1 << 93;
uint256 internal constant _ROLE_94 = 1 << 94;
uint256 internal constant _ROLE_95 = 1 << 95;
uint256 internal constant _ROLE_96 = 1 << 96;
uint256 internal constant _ROLE_97 = 1 << 97;
uint256 internal constant _ROLE_98 = 1 << 98;
uint256 internal constant _ROLE_99 = 1 << 99;
uint256 internal constant _ROLE_100 = 1 << 100;
uint256 internal constant _ROLE_101 = 1 << 101;
uint256 internal constant _ROLE_102 = 1 << 102;
uint256 internal constant _ROLE_103 = 1 << 103;
uint256 internal constant _ROLE_104 = 1 << 104;
uint256 internal constant _ROLE_105 = 1 << 105;
uint256 internal constant _ROLE_106 = 1 << 106;
uint256 internal constant _ROLE_107 = 1 << 107;
uint256 internal constant _ROLE_108 = 1 << 108;
uint256 internal constant _ROLE_109 = 1 << 109;
uint256 internal constant _ROLE_110 = 1 << 110;
uint256 internal constant _ROLE_111 = 1 << 111;
uint256 internal constant _ROLE_112 = 1 << 112;
uint256 internal constant _ROLE_113 = 1 << 113;
uint256 internal constant _ROLE_114 = 1 << 114;
uint256 internal constant _ROLE_115 = 1 << 115;
uint256 internal constant _ROLE_116 = 1 << 116;
uint256 internal constant _ROLE_117 = 1 << 117;
uint256 internal constant _ROLE_118 = 1 << 118;
uint256 internal constant _ROLE_119 = 1 << 119;
uint256 internal constant _ROLE_120 = 1 << 120;
uint256 internal constant _ROLE_121 = 1 << 121;
uint256 internal constant _ROLE_122 = 1 << 122;
uint256 internal constant _ROLE_123 = 1 << 123;
uint256 internal constant _ROLE_124 = 1 << 124;
uint256 internal constant _ROLE_125 = 1 << 125;
uint256 internal constant _ROLE_126 = 1 << 126;
uint256 internal constant _ROLE_127 = 1 << 127;
uint256 internal constant _ROLE_128 = 1 << 128;
uint256 internal constant _ROLE_129 = 1 << 129;
uint256 internal constant _ROLE_130 = 1 << 130;
uint256 internal constant _ROLE_131 = 1 << 131;
uint256 internal constant _ROLE_132 = 1 << 132;
uint256 internal constant _ROLE_133 = 1 << 133;
uint256 internal constant _ROLE_134 = 1 << 134;
uint256 internal constant _ROLE_135 = 1 << 135;
uint256 internal constant _ROLE_136 = 1 << 136;
uint256 internal constant _ROLE_137 = 1 << 137;
uint256 internal constant _ROLE_138 = 1 << 138;
uint256 internal constant _ROLE_139 = 1 << 139;
uint256 internal constant _ROLE_140 = 1 << 140;
uint256 internal constant _ROLE_141 = 1 << 141;
uint256 internal constant _ROLE_142 = 1 << 142;
uint256 internal constant _ROLE_143 = 1 << 143;
uint256 internal constant _ROLE_144 = 1 << 144;
uint256 internal constant _ROLE_145 = 1 << 145;
uint256 internal constant _ROLE_146 = 1 << 146;
uint256 internal constant _ROLE_147 = 1 << 147;
uint256 internal constant _ROLE_148 = 1 << 148;
uint256 internal constant _ROLE_149 = 1 << 149;
uint256 internal constant _ROLE_150 = 1 << 150;
uint256 internal constant _ROLE_151 = 1 << 151;
uint256 internal constant _ROLE_152 = 1 << 152;
uint256 internal constant _ROLE_153 = 1 << 153;
uint256 internal constant _ROLE_154 = 1 << 154;
uint256 internal constant _ROLE_155 = 1 << 155;
uint256 internal constant _ROLE_156 = 1 << 156;
uint256 internal constant _ROLE_157 = 1 << 157;
uint256 internal constant _ROLE_158 = 1 << 158;
uint256 internal constant _ROLE_159 = 1 << 159;
uint256 internal constant _ROLE_160 = 1 << 160;
uint256 internal constant _ROLE_161 = 1 << 161;
uint256 internal constant _ROLE_162 = 1 << 162;
uint256 internal constant _ROLE_163 = 1 << 163;
uint256 internal constant _ROLE_164 = 1 << 164;
uint256 internal constant _ROLE_165 = 1 << 165;
uint256 internal constant _ROLE_166 = 1 << 166;
uint256 internal constant _ROLE_167 = 1 << 167;
uint256 internal constant _ROLE_168 = 1 << 168;
uint256 internal constant _ROLE_169 = 1 << 169;
uint256 internal constant _ROLE_170 = 1 << 170;
uint256 internal constant _ROLE_171 = 1 << 171;
uint256 internal constant _ROLE_172 = 1 << 172;
uint256 internal constant _ROLE_173 = 1 << 173;
uint256 internal constant _ROLE_174 = 1 << 174;
uint256 internal constant _ROLE_175 = 1 << 175;
uint256 internal constant _ROLE_176 = 1 << 176;
uint256 internal constant _ROLE_177 = 1 << 177;
uint256 internal constant _ROLE_178 = 1 << 178;
uint256 internal constant _ROLE_179 = 1 << 179;
uint256 internal constant _ROLE_180 = 1 << 180;
uint256 internal constant _ROLE_181 = 1 << 181;
uint256 internal constant _ROLE_182 = 1 << 182;
uint256 internal constant _ROLE_183 = 1 << 183;
uint256 internal constant _ROLE_184 = 1 << 184;
uint256 internal constant _ROLE_185 = 1 << 185;
uint256 internal constant _ROLE_186 = 1 << 186;
uint256 internal constant _ROLE_187 = 1 << 187;
uint256 internal constant _ROLE_188 = 1 << 188;
uint256 internal constant _ROLE_189 = 1 << 189;
uint256 internal constant _ROLE_190 = 1 << 190;
uint256 internal constant _ROLE_191 = 1 << 191;
uint256 internal constant _ROLE_192 = 1 << 192;
uint256 internal constant _ROLE_193 = 1 << 193;
uint256 internal constant _ROLE_194 = 1 << 194;
uint256 internal constant _ROLE_195 = 1 << 195;
uint256 internal constant _ROLE_196 = 1 << 196;
uint256 internal constant _ROLE_197 = 1 << 197;
uint256 internal constant _ROLE_198 = 1 << 198;
uint256 internal constant _ROLE_199 = 1 << 199;
uint256 internal constant _ROLE_200 = 1 << 200;
uint256 internal constant _ROLE_201 = 1 << 201;
uint256 internal constant _ROLE_202 = 1 << 202;
uint256 internal constant _ROLE_203 = 1 << 203;
uint256 internal constant _ROLE_204 = 1 << 204;
uint256 internal constant _ROLE_205 = 1 << 205;
uint256 internal constant _ROLE_206 = 1 << 206;
uint256 internal constant _ROLE_207 = 1 << 207;
uint256 internal constant _ROLE_208 = 1 << 208;
uint256 internal constant _ROLE_209 = 1 << 209;
uint256 internal constant _ROLE_210 = 1 << 210;
uint256 internal constant _ROLE_211 = 1 << 211;
uint256 internal constant _ROLE_212 = 1 << 212;
uint256 internal constant _ROLE_213 = 1 << 213;
uint256 internal constant _ROLE_214 = 1 << 214;
uint256 internal constant _ROLE_215 = 1 << 215;
uint256 internal constant _ROLE_216 = 1 << 216;
uint256 internal constant _ROLE_217 = 1 << 217;
uint256 internal constant _ROLE_218 = 1 << 218;
uint256 internal constant _ROLE_219 = 1 << 219;
uint256 internal constant _ROLE_220 = 1 << 220;
uint256 internal constant _ROLE_221 = 1 << 221;
uint256 internal constant _ROLE_222 = 1 << 222;
uint256 internal constant _ROLE_223 = 1 << 223;
uint256 internal constant _ROLE_224 = 1 << 224;
uint256 internal constant _ROLE_225 = 1 << 225;
uint256 internal constant _ROLE_226 = 1 << 226;
uint256 internal constant _ROLE_227 = 1 << 227;
uint256 internal constant _ROLE_228 = 1 << 228;
uint256 internal constant _ROLE_229 = 1 << 229;
uint256 internal constant _ROLE_230 = 1 << 230;
uint256 internal constant _ROLE_231 = 1 << 231;
uint256 internal constant _ROLE_232 = 1 << 232;
uint256 internal constant _ROLE_233 = 1 << 233;
uint256 internal constant _ROLE_234 = 1 << 234;
uint256 internal constant _ROLE_235 = 1 << 235;
uint256 internal constant _ROLE_236 = 1 << 236;
uint256 internal constant _ROLE_237 = 1 << 237;
uint256 internal constant _ROLE_238 = 1 << 238;
uint256 internal constant _ROLE_239 = 1 << 239;
uint256 internal constant _ROLE_240 = 1 << 240;
uint256 internal constant _ROLE_241 = 1 << 241;
uint256 internal constant _ROLE_242 = 1 << 242;
uint256 internal constant _ROLE_243 = 1 << 243;
uint256 internal constant _ROLE_244 = 1 << 244;
uint256 internal constant _ROLE_245 = 1 << 245;
uint256 internal constant _ROLE_246 = 1 << 246;
uint256 internal constant _ROLE_247 = 1 << 247;
uint256 internal constant _ROLE_248 = 1 << 248;
uint256 internal constant _ROLE_249 = 1 << 249;
uint256 internal constant _ROLE_250 = 1 << 250;
uint256 internal constant _ROLE_251 = 1 << 251;
uint256 internal constant _ROLE_252 = 1 << 252;
uint256 internal constant _ROLE_253 = 1 << 253;
uint256 internal constant _ROLE_254 = 1 << 254;
uint256 internal constant _ROLE_255 = 1 << 255;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Reentrancy guard mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Unauthorized reentrant call.
error Reentrancy();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to: `uint72(bytes9(keccak256("_REENTRANCY_GUARD_SLOT")))`.
/// 9 bytes is large enough to avoid collisions with lower slots,
/// but not too large to result in excessive bytecode bloat.
uint256 private constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* REENTRANCY GUARD */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Guards a function from reentrancy.
modifier nonReentrant() virtual {
/// @solidity memory-safe-assembly
assembly {
if eq(sload(_REENTRANCY_GUARD_SLOT), address()) {
mstore(0x00, 0xab143c06) // `Reentrancy()`.
revert(0x1c, 0x04)
}
sstore(_REENTRANCY_GUARD_SLOT, address())
}
_;
/// @solidity memory-safe-assembly
assembly {
sstore(_REENTRANCY_GUARD_SLOT, codesize())
}
}
/// @dev Guards a view function from read-only reentrancy.
modifier nonReadReentrant() virtual {
/// @solidity memory-safe-assembly
assembly {
if eq(sload(_REENTRANCY_GUARD_SLOT), address()) {
mstore(0x00, 0xab143c06) // `Reentrancy()`.
revert(0x1c, 0x04)
}
}
_;
}
}
// 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: GPL-3.0-only
pragma solidity ^0.8.24;
import { IArbSys } from "../ext/IArbSys.sol";
/**
* @title DeployedAt
* @notice Provides the block number at which the contract was deployed.
*/
abstract contract DeployedAt {
/**
* @notice Block number at which the contract was deployed.
*/
uint256 public immutable deployedAt;
/**
* @notice Arbitrum's ArbSys precompile (0x0000000000000000000000000000000000000064)
* @dev Used to get Arbitrum block number.
*/
address internal constant ARB_SYS = 0x0000000000000000000000000000000000000064;
constructor() {
// Must get Arbitrum block number from ArbSys precompile, block.number returns L1 block number on Arbitrum.
if (_isContract(ARB_SYS)) {
try IArbSys(ARB_SYS).arbBlockNumber() returns (uint256 arbBlockNumber) {
deployedAt = arbBlockNumber;
} catch {
deployedAt = block.number;
}
} else {
deployedAt = block.number;
}
}
/**
* @dev Returns true if the address is a contract.
* @param addr Address to check.
*/
function _isContract(address addr) internal view returns (bool) {
uint32 size;
assembly {
size := extcodesize(addr)
}
return (size > 0);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
import { IOmniPortal } from "core/src/interfaces/IOmniPortal.sol";
import { XTypes } from "core/src/libraries/XTypes.sol";
import { ConfLevel } from "core/src/libraries/ConfLevel.sol";
/**
* @title XAppBase
* @notice Shared XApp storage and logic.
*/
abstract contract XAppBase {
/**
* @notice Deprecated `_omni` variable in favor of an immutable equivalent.
* @dev This variable is used to avoid a storage slot collision.
*/
IOmniPortal private _deprecatedOmni;
/**
* @notice The OmniPortal contract
*/
IOmniPortal public immutable omni;
/**
* @notice Deprecated `_defaultConfLevel` variable as we do not use the xcall method that retrieves it.
* @dev This variable is used to avoid a storage slot collision.
*/
uint8 private _deprecatedDefaultConfLevel;
/**
* @notice Transient storage for the current xmsg
*/
XTypes.MsgContext internal xmsg;
/**
* @notice Read current xmsg into storage before execution, delete it afterwards
* @dev If `omni` is not set, xmsg is not modified.
*/
modifier xrecv() {
if (address(omni) != address(0)) {
xmsg = omni.xmsg();
_;
delete xmsg;
} else {
_;
}
}
constructor(address omni_) {
omni = IOmniPortal(omni_);
}
/**
* @notice Returns the fee for calling a contract on another chain, with the specified gas limit
*/
function feeFor(uint64 destChainId, bytes memory data, uint64 gasLimit) internal view returns (uint256) {
return omni.feeFor(destChainId, data, gasLimit);
}
/**
* @notice Call a contract on another chain. (Only Finalized ConfLevel)
* @dev This function pays the xcall fee to the OmniPortal from the
* contract's balance. If you would prefer charge callers for the
* fee, you must check msg.value.
* Ex:
* uint256 fee = xcall(...)
* require(msg.value >= fee)
*
* @param destChainId Destination chain ID
* @param to Address of contract to call on destination chain
* @param data ABI Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function xcall(uint64 destChainId, address to, bytes memory data, uint64 gasLimit) internal returns (uint256) {
uint256 fee = omni.feeFor(destChainId, data, gasLimit);
require(address(this).balance >= fee, "XApp: insufficient funds");
omni.xcall{ value: fee }(destChainId, ConfLevel.Finalized, to, data, gasLimit);
return fee;
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.24;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import { IMailbox } from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
import { IInterchainSecurityModule } from "@hyperlane-xyz/core/contracts/interfaces/IInterchainSecurityModule.sol";
import { Message } from "@hyperlane-xyz/core/contracts/libs/Message.sol";
import { AddrUtils } from "../lib/AddrUtils.sol";
import { PackageVersioned } from "@hyperlane-xyz/core/contracts/PackageVersioned.sol";
import { StandardHookMetadata } from "@hyperlane-xyz/core/contracts/hooks/libs/StandardHookMetadata.sol";
// ============ External Imports ============
import { OwnableRoles } from "solady/src/auth/OwnableRoles.sol";
import { Initializable } from "solady/src/utils/Initializable.sol";
abstract contract MailboxClient is OwnableRoles, Initializable, PackageVersioned {
using Message for bytes;
using AddrUtils for address;
// ============ Events ============
event HookSet(address _hook);
event IsmSet(address _ism);
// ============ Immutable Storage ============
IMailbox public immutable mailbox;
IInterchainSecurityModule public immutable interchainSecurityModule;
uint32 public immutable localDomain;
/**
* @notice Only accept messages from a Hyperlane Mailbox contract
*/
modifier onlyMailbox() {
if (msg.sender != address(mailbox)) revert Unauthorized();
_;
}
constructor(address _mailbox) {
mailbox = IMailbox(_mailbox);
if (_mailbox != address(0)) {
interchainSecurityModule = mailbox.defaultIsm();
localDomain = mailbox.localDomain();
}
}
// ============ Internal functions ============
/**
* @notice Dispatch a message to a destination domain.
* @param _destinationDomain The destination domain.
* @param _target The target address.
* @param _value The value to send with the message.
* @param _messageBody The message body.
* @param _gasLimit The gas limit.
*/
function _dispatch(
uint32 _destinationDomain,
address _target,
uint256 _value,
bytes memory _messageBody,
uint256 _gasLimit,
address _refundAddress
) internal returns (bytes32) {
bytes memory _hookMetadata = StandardHookMetadata.formatMetadata(0, _gasLimit, _refundAddress, "");
return mailbox.dispatch{ value: _value }(_destinationDomain, _target.toBytes32(), _messageBody, _hookMetadata);
}
/**
* @notice Quote the dispatch fee for a message to a destination domain.
* @param _destinationDomain The destination domain.
* @param _target The target address.
* @param _messageBody The message body.
* @param _gasLimit The gas limit.
*/
function _quoteDispatch(
uint32 _destinationDomain,
address _target,
bytes memory _messageBody,
uint256 _gasLimit,
address _refundAddress
) internal view returns (uint256) {
bytes memory _hookMetadata = StandardHookMetadata.formatMetadata(0, _gasLimit, _refundAddress, "");
return mailbox.quoteDispatch(_destinationDomain, _target.toBytes32(), _messageBody, _hookMetadata);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
/// @title IERC7683
/// @notice Standard type definitions for ERC-7683 compliant settlement contracts
/// @dev See https://github.com/ethereum/ERCs/blob/master/ERCS/erc-7683.md
interface IERC7683 {
/// @notice Signals that an order has been opened
/// @param orderId a unique order identifier within this settlement system
/// @param resolvedOrder resolved order that would be returned by resolve if called instead of Open
event Open(bytes32 indexed orderId, ResolvedCrossChainOrder resolvedOrder);
/// @title GaslessCrossChainOrder CrossChainOrder type
/// @notice Standard order struct to be signed by users, disseminated to fillers, and submitted to origin settler contracts
struct GaslessCrossChainOrder {
/// @dev The contract address that the order is meant to be settled by.
/// Fillers send this order to this contract address on the origin chain
address originSettler;
/// @dev The address of the user who is initiating the swap,
/// whose input tokens will be taken and escrowed
address user;
/// @dev Nonce to be used as replay protection for the order
uint256 nonce;
/// @dev The chainId of the origin chain
uint256 originChainId;
/// @dev The timestamp by which the order must be opened
uint32 openDeadline;
/// @dev The timestamp by which the order must be filled on the destination chain
uint32 fillDeadline;
/// @dev Type identifier for the order data. This is an EIP-712 typehash.
bytes32 orderDataType;
/// @dev Arbitrary implementation-specific data
/// Can be used to define tokens, amounts, destination chains, fees, settlement parameters,
/// or any other order-type specific information
bytes orderData;
}
/// @title OnchainCrossChainOrder CrossChainOrder type
/// @notice Standard order struct for user-opened orders, where the user is the msg.sender.
struct OnchainCrossChainOrder {
/// @dev The timestamp by which the order must be filled on the destination chain
uint32 fillDeadline;
/// @dev Type identifier for the order data. This is an EIP-712 typehash.
bytes32 orderDataType;
/// @dev Arbitrary implementation-specific data
/// Can be used to define tokens, amounts, destination chains, fees, settlement parameters,
/// or any other order-type specific information
bytes orderData;
}
/// @title ResolvedCrossChainOrder type
/// @notice An implementation-generic representation of an order intended for filler consumption
/// @dev Defines all requirements for filling an order by unbundling the implementation-specific orderData.
/// @dev Intended to improve integration generalization by allowing fillers to compute the exact input and output information of any order
struct ResolvedCrossChainOrder {
/// @dev The address of the user who is initiating the transfer
address user;
/// @dev The chainId of the origin chain
uint256 originChainId;
/// @dev The timestamp by which the order must be opened
uint32 openDeadline;
/// @dev The timestamp by which the order must be filled on the destination chain(s)
uint32 fillDeadline;
/// @dev The unique identifier for this order within this settlement system
bytes32 orderId;
/// @dev The max outputs that the filler will send. It's possible the actual amount depends on the state of the destination
/// chain (destination dutch auction, for instance), so these outputs should be considered a cap on filler liabilities.
Output[] maxSpent;
/// @dev The minimum outputs that must to be given to the filler as part of order settlement. Similar to maxSpent, it's possible
/// that special order types may not be able to guarantee the exact amount at open time, so this should be considered
/// a floor on filler receipts.
Output[] minReceived;
/// @dev Each instruction in this array is parameterizes a single leg of the fill. This provides the filler with the information
/// necessary to perform the fill on the destination(s).
FillInstruction[] fillInstructions;
}
/// @notice Tokens that must be receive for a valid order fulfillment
struct Output {
/// @dev The address of the ERC20 token on the destination chain
/// @dev address(0) used as a sentinel for the native token
bytes32 token;
/// @dev The amount of the token to be sent
uint256 amount;
/// @dev The address to receive the output tokens
bytes32 recipient;
/// @dev The destination chain for this output
uint256 chainId;
}
/// @title FillInstruction type
/// @notice Instructions to parameterize each leg of the fill
/// @dev Provides all the origin-generated information required to produce a valid fill leg
struct FillInstruction {
/// @dev The contract address that the order is meant to be settled by
uint64 destinationChainId;
/// @dev The contract address that the order is meant to be filled on
bytes32 destinationSettler;
/// @dev The data generated on the origin chain needed by the destinationSettler to process the fill
bytes originData;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
import { IOriginSettler } from "../erc7683/IOriginSettler.sol";
import { IMessageRecipient } from "@hyperlane-xyz/core/contracts/interfaces/IMessageRecipient.sol";
import { SolverNet } from "../lib/SolverNet.sol";
interface ISolverNetInbox is IOriginSettler, IMessageRecipient {
// Validation errors
error InvalidOrderTypehash();
error InvalidOrderData();
error InvalidChainId();
error InvalidFillDeadline();
error InvalidMissingCalls();
error InvalidExpenseToken();
error InvalidExpenseAmount();
error InvalidArrayLength();
// Open order errors
error InvalidERC20Deposit();
error InvalidNativeDeposit();
// Reject order errors
error InvalidReason();
// Order status errors
error OrderNotPending();
error OrderStillValid();
// Order fill errors
error WrongSourceChain();
error WrongFillHash();
// Order claim errors
error OrderNotFilled();
// Pause errors
error IsPaused();
error AllPaused();
/**
* @notice Emitted when an outbox is set.
* @param chainId ID of the chain.
* @param outbox Address of the outbox.
*/
event OutboxSet(uint64 indexed chainId, address indexed outbox);
/**
* @notice Emitted when a pause state is set.
* @param key Pause key.
* @param pause True if paused, false if unpaused.
* @param pauseState Current pause state.
*/
event Paused(bytes32 indexed key, bool indexed pause, uint8 indexed pauseState);
/**
* @notice Emitted when an order is opened.
* @dev This event emits the FillOriginData typed `originData`, rather than ABI-encoded as seen in `IERC7683.Open`.
* @param id ID of the order.
* @param fillOriginData Order fill originData.
*/
event FillOriginData(bytes32 indexed id, SolverNet.FillOriginData fillOriginData);
/**
* @notice Emitted when an order is rejected.
* @param id ID of the order.
* @param by Address of the solver who rejected the order.
* @param reason Reason code for rejecting the order.
*/
event Rejected(bytes32 indexed id, address indexed by, uint8 indexed reason);
/**
* @notice Emitted when an order is cancelled.
* @param id ID of the order.
*/
event Closed(bytes32 indexed id);
/**
* @notice Emitted when an order is filled.
* @param id ID of the order.
* @param fillHash Hash of the fill instructions origin data.
* @param creditedTo Address of the recipient credited the funds by the solver.
*/
event Filled(bytes32 indexed id, bytes32 indexed fillHash, address indexed creditedTo);
/**
* @notice Emitted when an order is claimed.
* @param id ID of the order.
* @param by The solver address that claimed the order.
* @param to The recipient of claimed deposits.
*/
event Claimed(bytes32 indexed id, address indexed by, address indexed to);
/**
* @notice Status of an order.
*/
enum Status {
Invalid,
Pending,
Rejected,
Closed,
Filled,
Claimed
}
/**
* @notice State of an order.
* @param status Latest order status.
* @param rejectReason Reason code for rejecting the order, if rejected.
* @param timestamp Timestamp of the status update.
* @param updatedBy Address for who last updated the order.
*/
struct OrderState {
Status status;
uint8 rejectReason;
uint32 timestamp;
address updatedBy;
}
/**
* @notice Set the outbox addresses for the given chain IDs.
* @param chainIds IDs of the chains.
* @param outboxes Addresses of the outboxes.
*/
function setOutboxes(uint64[] calldata chainIds, address[] calldata outboxes) external;
/**
* @notice Returns the outbox address for the given chain ID.
* @param chainId ID of the chain.
* @return outbox Outbox address.
*/
function getOutbox(uint64 chainId) external view returns (address);
/**
* @notice Returns the order, its state, and offset with the given ID.
* @param id ID of the order.
*/
function getOrder(bytes32 id)
external
view
returns (ResolvedCrossChainOrder memory order, OrderState memory state, uint248 offset);
/**
* @notice Returns the order ID for the given user and nonce.
* @param user Address of the user.
* @param nonce Nonce of the order.
*/
function getOrderId(address user, uint256 nonce) external view returns (bytes32);
/**
* @notice Returns the next order ID for the given user.
* @param user Address of the user.
*/
function getNextOrderId(address user) external view returns (bytes32);
/**
* @notice Returns the nonce for the given user.
* @param user Address of the user.
*/
function getUserNonce(address user) external view returns (uint256);
/**
* @notice Returns the order offset of the latest order opened at this inbox.
*/
function getLatestOrderOffset() external view returns (uint248);
/**
* @dev Validate the onchain order.
* @param order OnchainCrossChainOrder to validate.
*/
function validate(OnchainCrossChainOrder calldata order) external view returns (bool);
/**
* @notice Reject an open order and refund deposits.
* @dev Only a whitelisted solver can reject.
* @param id ID of the order.
* @param reason Reason code for rejection.
*/
function reject(bytes32 id, uint8 reason) external;
/**
* @notice Close order and refund deposits after fill deadline has elapsed.
* @dev Only order initiator can close.
* @param id ID of the order.
*/
function close(bytes32 id) external;
/**
* @notice Fill an order via Omni Core.
* @dev Only callable by the outbox.
* @param id ID of the order.
* @param fillHash Hash of fill instructions origin data.
* @param creditedTo Address deposits are credited to, provided by the filler.
*/
function markFilled(bytes32 id, bytes32 fillHash, address creditedTo) external;
/**
* @notice Claim deposits for a filled order.
* @param id ID of the order.
* @param to Address to send deposits to.
*/
function claim(bytes32 id, address to) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The ERC20 `totalSupply` query has failed.
error TotalSupplyQueryFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/// @dev The Permit2 approve operation has failed.
error Permit2ApproveFailed();
/// @dev The Permit2 lockdown operation has failed.
error Permit2LockdownFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
// Check the `extcodesize` again just in case the token selfdestructs lol.
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Performs a `token.balanceOf(account)` check.
/// `implemented` denotes whether the `token` does not implement `balanceOf`.
/// `amount` is zero if the `token` does not implement `balanceOf`.
function checkBalanceOf(address token, address account)
internal
view
returns (bool implemented, uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
implemented :=
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
amount := mul(mload(0x20), implemented)
}
}
/// @dev Returns the total supply of the `token`.
/// Reverts if the token does not exist or does not implement `totalSupply()`.
function totalSupply(address token) internal view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x18160ddd) // `totalSupply()`.
if iszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20))
) {
mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`.
revert(0x1c, 0x04)
}
result := mload(0x00)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(
and(
call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
)
) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(
add(m, 0x94),
lt(iszero(amount), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
)
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `amount != 0` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero( // Revert if token does not have code, or if the call fails.
mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Approves `spender` to spend `amount` of `token` for `address(this)`.
function permit2Approve(address token, address spender, uint160 amount, uint48 expiration)
internal
{
/// @solidity memory-safe-assembly
assembly {
let addressMask := shr(96, not(0))
let m := mload(0x40)
mstore(m, 0x87517c45) // `approve(address,address,uint160,uint48)`.
mstore(add(m, 0x20), and(addressMask, token))
mstore(add(m, 0x40), and(addressMask, spender))
mstore(add(m, 0x60), and(addressMask, amount))
mstore(add(m, 0x80), and(0xffffffffffff, expiration))
if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
mstore(0x00, 0x324f14ae) // `Permit2ApproveFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Revokes an approval for `token` and `spender` for `address(this)`.
function permit2Lockdown(address token, address spender) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0xcc53287f) // `Permit2.lockdown`.
mstore(add(m, 0x20), 0x20) // Offset of the `approvals`.
mstore(add(m, 0x40), 1) // `approvals.length`.
mstore(add(m, 0x60), shr(96, shl(96, token)))
mstore(add(m, 0x80), shr(96, shl(96, spender)))
if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
mstore(0x00, 0x96b3de23) // `Permit2LockdownFailed()`.
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
library SolverNet {
/**
* @notice OrderData is SolverNet's ERC7683 order data encoding, used when opening an order.
* It describes the calls to execute, the expenses required, and the deposit backing the order.
*
* Note that expenses only includes ERC20 expenses, native expenses are inferred from the calls.
* This reduces the order data size, saving gas.
*
* Order expenses (both token expenses and required call values) should be exact. If expenses are underestimated,
* the order will be rejected. If expenses are overestimated, the excess will be refunded to the solver.
*
* Order deposits must cover expenses + solver fees. If they do not, the order will be rejected. To
* determine required deposits, use Omni's solver /quote API, or Omni's SolverNet SDK. (TODO link to docs)
*
* @custom:field owner The address that can close the order. Defaults to msg.sender if zero.
* @custom:field destChainId The chain on which to execute calls.
* @custom:field deposit The deposit paid by the user on the source chain when opening the order.
* @custom:field calls The calls to be executed on the destination chain.
* @custom:field expenses The token expenses required to fill the order, paid by the solver.
*/
struct OrderData {
address owner;
uint64 destChainId;
Deposit deposit;
Call[] calls;
TokenExpense[] expenses;
}
/**
* @notice OmniOrderData is the same struct as OrderData, but named properly so we are fully compliant with EIP-712.
* @dev This is currently only used in SolverNetInboxV2.
*/
struct OmniOrderData {
address owner;
uint64 destChainId;
Deposit deposit;
Call[] calls;
TokenExpense[] expenses;
}
/**
* @notice Order is a convenience struct that fully describes an order.
* @dev It is not written to storage, but rather built on view.
*/
struct Order {
Header header;
Deposit deposit;
Call[] calls;
TokenExpense[] expenses;
}
/**
* @notice Header is an "order header", containing order data not related to deposit, calls, or expenses.
* @dev This struct is used to pack loose order data fields into a single slot.
*/
struct Header {
address owner;
uint64 destChainId;
uint32 fillDeadline;
}
/**
* @notice Deposit describes a deposit (native or ERC20) paid by the user on the source chain when opening an order.
* @custom:field token The address of the token, address(0) if native.
* @custom:field amount The amount of the token deposited.
*/
struct Deposit {
address token;
uint96 amount;
}
/**
* @notice Call describes a call to execute.
* If the call is a native transfer, `target` is the recipient address, and `selector` / `params` are empty.
* @dev Full call data is built from `selector` and `params`: abi.encodePacked(call.selector, call.params).
* @custom:field target The address to execute the call against
* @custom:field selector The function selector to call
* @custom:field value The amount of native token to send with the call
* @custom:field params The call parameters
*/
struct Call {
address target;
bytes4 selector;
uint256 value;
bytes params;
}
/**
* @notice TokenExpense describes an ERC20 expense to be paid by the solver on destination chain when filling an
* order. Native expenses are inferred from the calls, and are not included in the order data.
* @custom:field spender The address that will do token.transferFrom(...) on fill. Required to set allowance.
* @custom:field token The address of the token on the destination chain.
* @custom:field amount The amount of the token to be spent.
*/
struct TokenExpense {
address spender;
address token;
uint96 amount;
}
/**
* @notice FillOriginData is the SolverNet's ERC7683 fill instruction data encoding, used when filling an order.
* @dev The hash(orderId, fillOriginData) is used to prove that a solver properly filled an order.
* @custom:field srcChainId The chain on which the order was opened.
* @custom:field destChainId The chain on which to execute calls.
* @custom:field fillDeadline The deadline by which the order must be filled.
* @custom:field calls The calls to execute on the destination chain.
* @custom:field expenses The token expenses required to fill the order, paid by the solver.
*/
struct FillOriginData {
uint64 srcChainId;
uint64 destChainId;
uint32 fillDeadline;
Call[] calls;
TokenExpense[] expenses;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
library AddrUtils {
/**
* @dev Convert address to bytes32.
* @param a Address to convert.
*/
function toBytes32(address a) internal pure returns (bytes32) {
return bytes32(uint256(uint160(a)));
}
/**
* @dev Convert bytes32 to address.
* @param b Bytes32 to convert.
*/
function toAddress(bytes32 b) internal pure returns (address) {
return address(uint160(uint256(b)));
}
}
// 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: GPL-3.0-only
pragma solidity ^0.8.24;
/**
* @title IArbSys
* @notice Interface for Arbitrum's ArbSys precompile (0x0000000000000000000000000000000000000064)
* @custom:source https://github.com/OffchainLabs/nitro-contracts/blob/main/src/precompiles/ArbSys.sol
*/
interface IArbSys {
/**
* @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0)
* @return block number as int
*/
function arbBlockNumber() external view returns (uint256);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
import { XTypes } from "../libraries/XTypes.sol";
/**
* @title IOmniPortal
* @notice The OmniPortal is the on-chain interface to Omni's cross-chain
* messaging protocol. It is used to initiate and execute cross-chain calls.
*/
interface IOmniPortal {
/**
* @notice Emitted when an xcall is made to a contract on another chain
* @param destChainId Destination chain ID
* @param offset Offset this XMsg in the source -> dest XStream
* @param sender msg.sender of the source xcall
* @param to Address of the contract to call on the destination chain
* @param data Encoded function calldata
* @param gasLimit Gas limit for execution on destination chain
* @param fees Fees paid for the xcall
*/
event XMsg(
uint64 indexed destChainId,
uint64 indexed shardId,
uint64 indexed offset,
address sender,
address to,
bytes data,
uint64 gasLimit,
uint256 fees
);
/**
* @notice Emitted when an XMsg is executed on its destination chain
* @param sourceChainId Source chain ID
* @param shardId Shard ID of the XStream (last byte is the confirmation level)
* @param offset Offset the XMsg in the source -> dest XStream
* @param gasUsed Gas used in execution of the XMsg
* @param relayer Address of the relayer who submitted the XMsg
* @param success Whether the execution succeeded
* @param err Result of XMsg execution, if success == false. Limited to
* xreceiptMaxErrorBytes(). Empty if success == true.
*/
event XReceipt(
uint64 indexed sourceChainId,
uint64 indexed shardId,
uint64 indexed offset,
uint256 gasUsed,
address relayer,
bool success,
bytes err
);
/**
* @notice Maximum allowed xmsg gas limit
*/
function xmsgMaxGasLimit() external view returns (uint64);
/**
* @notice Minimum allowed xmsg gas limit
*/
function xmsgMinGasLimit() external view returns (uint64);
/**
* @notice Maximum number of bytes allowed in xmsg data
*/
function xmsgMaxDataSize() external view returns (uint16);
/**
* @notice Maximum number of bytes allowed in xreceipt result
*/
function xreceiptMaxErrorSize() external view returns (uint16);
/**
* @notice Returns the fee oracle address
*/
function feeOracle() external view returns (address);
/**
* @notice Returns the chain ID of the chain to which this portal is deployed
*/
function chainId() external view returns (uint64);
/**
* @notice Returns the chain ID of Omni's EVM execution chain
*/
function omniChainId() external view returns (uint64);
/**
* @notice Returns the offset of the last outbound XMsg sent to destChainId in shardId
*/
function outXMsgOffset(uint64 destChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the offset of the last inbound XMsg received from srcChainId in shardId
*/
function inXMsgOffset(uint64 srcChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the offset of the last inbound XBlock received from srcChainId in shardId
*/
function inXBlockOffset(uint64 srcChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the current XMsg being executed via this portal.
* - xmsg().sourceChainId Chain ID of the source xcall
* - xmsg().sender msg.sender of the source xcall
* If no XMsg is being executed, all fields will be zero.
* - xmsg().sourceChainId == 0
* - xmsg().sender == address(0)
*/
function xmsg() external view returns (XTypes.MsgContext memory);
/**
* @notice Returns true the current transaction is an xcall, false otherwise
*/
function isXCall() external view returns (bool);
/**
* @notice Returns the shard ID is supported by this portal
*/
function isSupportedShard(uint64 shardId) external view returns (bool);
/**
* @notice Returns the destination chain ID is supported by this portal
*/
function isSupportedDest(uint64 destChainId) external view returns (bool);
/**
* @notice Calculate the fee for calling a contract on another chain
* Fees denominated in wei.
* @param destChainId Destination chain ID
* @param data Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function feeFor(uint64 destChainId, bytes calldata data, uint64 gasLimit) external view returns (uint256);
/**
* @notice Call a contract on another chain.
* @param destChainId Destination chain ID
* @param conf Confirmation level;
* @param to Address of contract to call on destination chain
* @param data ABI Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function xcall(uint64 destChainId, uint8 conf, address to, bytes calldata data, uint64 gasLimit) external payable;
/**
* @notice Submit a batch of XMsgs to be executed on this chain
* @param xsub An xchain submission, including an attestation root w/ validator signatures,
* and a block header and message batch, proven against the attestation root.
*/
function xsubmit(XTypes.Submission calldata xsub) external;
/**
* @notice Returns the current network (supported chain IDs and shards)
*/
function network() external view returns (XTypes.Chain[] memory);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title XTypes
* @dev Defines xchain types, core to Omni's xchain messaging protocol. These
* types mirror those defined in omni/lib/xchain/types.go.
*/
library XTypes {
/**
* @notice A cross chain message - the product of an xcall. This matches the XMsg type used
* throughout Omni's cross-chain messaging protocol. Msg is used to construct and verify
* XSubmission merkle trees / proofs.
* @custom:field destChainId Chain ID of the destination chain
* @custom:field shardId Shard ID of the XStream (last byte is the confirmation level)
* @custom:field offset Monotonically incremented offset of Msg in source -> dest Stream
* @custom:field sender msg.sender of xcall on source chain
* @custom:field to Target address to call on destination chain
* @custom:field data Data to provide to call on destination chain
* @custom:field gasLimit Gas limit to use for call execution on destination chain
*/
struct Msg {
uint64 destChainId;
uint64 shardId;
uint64 offset;
address sender;
address to;
bytes data;
uint64 gasLimit;
}
/**
* @notice Msg context exposed during its execution to consuming xapps.
* @custom:field sourceChainId Chain ID of the source chain
* @custom:field sender msg.sender of xcall on source chain
*/
struct MsgContext {
uint64 sourceChainId;
address sender;
}
/**
* @notice BlockHeader of an XBlock.
* @custom:field sourceChainId Chain ID of the source chain
* @custom:field consensusChainId Chain ID of the Omni consensus chain
* @custom:field confLevel Confirmation level of the cross chain block
* @custom:field offset Offset of the cross chain block
* @custom:field sourceBlockHeight Height of the source chain block
* @custom:field sourceBlockHash Hash of the source chain block
*/
struct BlockHeader {
uint64 sourceChainId;
uint64 consensusChainId;
uint8 confLevel;
uint64 offset;
uint64 sourceBlockHeight;
bytes32 sourceBlockHash;
}
/**
* @notice The required parameters to submit xmsgs to an OmniPortal. Constructed by the relayer
* by watching Omni's consensus chain, and source chain blocks.
* @custom:field attestationRoot Merkle root of xchain block (XBlockRoot), attested to and signed by validators
* @custom:field validatorSetId Unique identifier of the validator set that attested to this root
* @custom:field blockHeader Block header, identifies xchain block
* @custom:field msgs Messages to execute
* @custom:field proof Multi proof of block header and messages, proven against attestationRoot
* @custom:field proofFlags Multi proof flags
* @custom:field signatures Array of validator signatures of the attestationRoot, and their public keys
*/
struct Submission {
bytes32 attestationRoot;
uint64 validatorSetId;
BlockHeader blockHeader;
Msg[] msgs;
bytes32[] proof;
bool[] proofFlags;
SigTuple[] signatures;
}
/**
* @notice A tuple of a validator's ethereum address and signature over some digest.
* @custom:field validatorAddr Validator ethereum address
* @custom:field signature Validator signature over some digest; Ethereum 65 bytes [R || S || V] format.
*/
struct SigTuple {
address validatorAddr;
bytes signature;
}
/**
* @notice An Omni validator, specified by their ethereum address and voting power.
* @custom:field addr Validator ethereum address
* @custom:field power Validator voting power
*/
struct Validator {
address addr;
uint64 power;
}
/**
* @notice A chain in the "omni network" specified by its chain ID and supported shards.
* @custom:field chainId Chain ID
* @custom:field shards Supported shards
*/
struct Chain {
uint64 chainId;
uint64[] shards;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title ConfLevel
* @notice XMsg confirmation levels. Matches ConfLevels in lib/xchain/types.go
* @dev We prefer explicit constants over Enums, because we want uint8 values to start at 1, not 0, as they do in
* lib/xchain/types.go, such that 0 can represent "unset". Note only latest and finalized levels are supported
* on-chain.
*/
library ConfLevel {
/**
* @notice XMsg confirmation level "latest", last byte of xmsg.shardId.
*/
uint8 internal constant Latest = 1;
/**
* @notice XMsg confirmation level "finalized", last byte of xmsg.shardId.
*/
uint8 internal constant Finalized = 4;
/**
* @notice Returns true if the given level is valid.
*/
function isValid(uint8 level) internal pure returns (bool) {
return level == Latest || level == Finalized;
}
/**
* @notice Returns broadcast shard version of the given level.
*/
function toBroadcastShard(uint8 level) internal pure returns (uint64) {
return uint64(level) | 0x0100;
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {IInterchainSecurityModule} from "./IInterchainSecurityModule.sol";
import {IPostDispatchHook} from "./hooks/IPostDispatchHook.sol";
interface IMailbox {
// ============ Events ============
/**
* @notice Emitted when a new message is dispatched via Hyperlane
* @param sender The address that dispatched the message
* @param destination The destination domain of the message
* @param recipient The message recipient address on `destination`
* @param message Raw bytes of message
*/
event Dispatch(
address indexed sender,
uint32 indexed destination,
bytes32 indexed recipient,
bytes message
);
/**
* @notice Emitted when a new message is dispatched via Hyperlane
* @param messageId The unique message identifier
*/
event DispatchId(bytes32 indexed messageId);
/**
* @notice Emitted when a Hyperlane message is processed
* @param messageId The unique message identifier
*/
event ProcessId(bytes32 indexed messageId);
/**
* @notice Emitted when a Hyperlane message is delivered
* @param origin The origin domain of the message
* @param sender The message sender address on `origin`
* @param recipient The address that handled the message
*/
event Process(
uint32 indexed origin,
bytes32 indexed sender,
address indexed recipient
);
function localDomain() external view returns (uint32);
function delivered(bytes32 messageId) external view returns (bool);
function defaultIsm() external view returns (IInterchainSecurityModule);
function defaultHook() external view returns (IPostDispatchHook);
function requiredHook() external view returns (IPostDispatchHook);
function latestDispatchedId() external view returns (bytes32);
function dispatch(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes calldata messageBody
) external payable returns (bytes32 messageId);
function quoteDispatch(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes calldata messageBody
) external view returns (uint256 fee);
function dispatch(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes calldata body,
bytes calldata defaultHookMetadata
) external payable returns (bytes32 messageId);
function quoteDispatch(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes calldata messageBody,
bytes calldata defaultHookMetadata
) external view returns (uint256 fee);
function dispatch(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes calldata body,
bytes calldata customHookMetadata,
IPostDispatchHook customHook
) external payable returns (bytes32 messageId);
function quoteDispatch(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes calldata messageBody,
bytes calldata customHookMetadata,
IPostDispatchHook customHook
) external view returns (uint256 fee);
function process(
bytes calldata metadata,
bytes calldata message
) external payable;
function recipientIsm(
address recipient
) external view returns (IInterchainSecurityModule module);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
interface IInterchainSecurityModule {
enum Types {
UNUSED,
ROUTING,
AGGREGATION,
LEGACY_MULTISIG,
MERKLE_ROOT_MULTISIG,
MESSAGE_ID_MULTISIG,
NULL, // used with relayer carrying no metadata
CCIP_READ,
ARB_L2_TO_L1,
WEIGHTED_MERKLE_ROOT_MULTISIG,
WEIGHTED_MESSAGE_ID_MULTISIG,
OP_L2_TO_L1
}
/**
* @notice Returns an enum that represents the type of security model
* encoded by this ISM.
* @dev Relayers infer how to fetch and format metadata.
*/
function moduleType() external view returns (uint8);
/**
* @notice Defines a security model responsible for verifying interchain
* messages based on the provided metadata.
* @param _metadata Off-chain metadata provided by a relayer, specific to
* the security model encoded by the module (e.g. validator signatures)
* @param _message Hyperlane encoded interchain message
* @return True if the message was verified
*/
function verify(
bytes calldata _metadata,
bytes calldata _message
) external returns (bool);
}
interface ISpecifiesInterchainSecurityModule {
function interchainSecurityModule()
external
view
returns (IInterchainSecurityModule);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {TypeCasts} from "./TypeCasts.sol";
/**
* @title Hyperlane Message Library
* @notice Library for formatted messages used by Mailbox
**/
library Message {
using TypeCasts for bytes32;
uint256 private constant VERSION_OFFSET = 0;
uint256 private constant NONCE_OFFSET = 1;
uint256 private constant ORIGIN_OFFSET = 5;
uint256 private constant SENDER_OFFSET = 9;
uint256 private constant DESTINATION_OFFSET = 41;
uint256 private constant RECIPIENT_OFFSET = 45;
uint256 private constant BODY_OFFSET = 77;
/**
* @notice Returns formatted (packed) Hyperlane message with provided fields
* @dev This function should only be used in memory message construction.
* @param _version The version of the origin and destination Mailboxes
* @param _nonce A nonce to uniquely identify the message on its origin chain
* @param _originDomain Domain of origin chain
* @param _sender Address of sender as bytes32
* @param _destinationDomain Domain of destination chain
* @param _recipient Address of recipient on destination chain as bytes32
* @param _messageBody Raw bytes of message body
* @return Formatted message
*/
function formatMessage(
uint8 _version,
uint32 _nonce,
uint32 _originDomain,
bytes32 _sender,
uint32 _destinationDomain,
bytes32 _recipient,
bytes calldata _messageBody
) internal pure returns (bytes memory) {
return
abi.encodePacked(
_version,
_nonce,
_originDomain,
_sender,
_destinationDomain,
_recipient,
_messageBody
);
}
/**
* @notice Returns the message ID.
* @param _message ABI encoded Hyperlane message.
* @return ID of `_message`
*/
function id(bytes memory _message) internal pure returns (bytes32) {
return keccak256(_message);
}
/**
* @notice Returns the message version.
* @param _message ABI encoded Hyperlane message.
* @return Version of `_message`
*/
function version(bytes calldata _message) internal pure returns (uint8) {
return uint8(bytes1(_message[VERSION_OFFSET:NONCE_OFFSET]));
}
/**
* @notice Returns the message nonce.
* @param _message ABI encoded Hyperlane message.
* @return Nonce of `_message`
*/
function nonce(bytes calldata _message) internal pure returns (uint32) {
return uint32(bytes4(_message[NONCE_OFFSET:ORIGIN_OFFSET]));
}
/**
* @notice Returns the message origin domain.
* @param _message ABI encoded Hyperlane message.
* @return Origin domain of `_message`
*/
function origin(bytes calldata _message) internal pure returns (uint32) {
return uint32(bytes4(_message[ORIGIN_OFFSET:SENDER_OFFSET]));
}
/**
* @notice Returns the message sender as bytes32.
* @param _message ABI encoded Hyperlane message.
* @return Sender of `_message` as bytes32
*/
function sender(bytes calldata _message) internal pure returns (bytes32) {
return bytes32(_message[SENDER_OFFSET:DESTINATION_OFFSET]);
}
/**
* @notice Returns the message sender as address.
* @param _message ABI encoded Hyperlane message.
* @return Sender of `_message` as address
*/
function senderAddress(
bytes calldata _message
) internal pure returns (address) {
return sender(_message).bytes32ToAddress();
}
/**
* @notice Returns the message destination domain.
* @param _message ABI encoded Hyperlane message.
* @return Destination domain of `_message`
*/
function destination(
bytes calldata _message
) internal pure returns (uint32) {
return uint32(bytes4(_message[DESTINATION_OFFSET:RECIPIENT_OFFSET]));
}
/**
* @notice Returns the message recipient as bytes32.
* @param _message ABI encoded Hyperlane message.
* @return Recipient of `_message` as bytes32
*/
function recipient(
bytes calldata _message
) internal pure returns (bytes32) {
return bytes32(_message[RECIPIENT_OFFSET:BODY_OFFSET]);
}
/**
* @notice Returns the message recipient as address.
* @param _message ABI encoded Hyperlane message.
* @return Recipient of `_message` as address
*/
function recipientAddress(
bytes calldata _message
) internal pure returns (address) {
return recipient(_message).bytes32ToAddress();
}
/**
* @notice Returns the message body.
* @param _message ABI encoded Hyperlane message.
* @return Body of `_message`
*/
function body(
bytes calldata _message
) internal pure returns (bytes calldata) {
return bytes(_message[BODY_OFFSET:]);
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
/**
* @title PackageVersioned
* @notice Package version getter for contracts
**/
abstract contract PackageVersioned {
// GENERATED CODE - DO NOT EDIT
string public constant PACKAGE_VERSION = "7.0.0";
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
/**
* Format of metadata:
*
* [0:2] variant
* [2:34] msg.value
* [34:66] Gas limit for message (IGP)
* [66:86] Refund address for message (IGP)
* [86:] Custom metadata
*/
library StandardHookMetadata {
struct Metadata {
uint16 variant;
uint256 msgValue;
uint256 gasLimit;
address refundAddress;
}
uint8 private constant VARIANT_OFFSET = 0;
uint8 private constant MSG_VALUE_OFFSET = 2;
uint8 private constant GAS_LIMIT_OFFSET = 34;
uint8 private constant REFUND_ADDRESS_OFFSET = 66;
uint256 private constant MIN_METADATA_LENGTH = 86;
uint16 public constant VARIANT = 1;
/**
* @notice Returns the variant of the metadata.
* @param _metadata ABI encoded standard hook metadata.
* @return variant of the metadata as uint8.
*/
function variant(bytes calldata _metadata) internal pure returns (uint16) {
if (_metadata.length < VARIANT_OFFSET + 2) return 0;
return uint16(bytes2(_metadata[VARIANT_OFFSET:VARIANT_OFFSET + 2]));
}
/**
* @notice Returns the specified value for the message.
* @param _metadata ABI encoded standard hook metadata.
* @param _default Default fallback value.
* @return Value for the message as uint256.
*/
function msgValue(
bytes calldata _metadata,
uint256 _default
) internal pure returns (uint256) {
if (_metadata.length < MSG_VALUE_OFFSET + 32) return _default;
return
uint256(bytes32(_metadata[MSG_VALUE_OFFSET:MSG_VALUE_OFFSET + 32]));
}
/**
* @notice Returns the specified gas limit for the message.
* @param _metadata ABI encoded standard hook metadata.
* @param _default Default fallback gas limit.
* @return Gas limit for the message as uint256.
*/
function gasLimit(
bytes calldata _metadata,
uint256 _default
) internal pure returns (uint256) {
if (_metadata.length < GAS_LIMIT_OFFSET + 32) return _default;
return
uint256(bytes32(_metadata[GAS_LIMIT_OFFSET:GAS_LIMIT_OFFSET + 32]));
}
/**
* @notice Returns the specified refund address for the message.
* @param _metadata ABI encoded standard hook metadata.
* @param _default Default fallback refund address.
* @return Refund address for the message as address.
*/
function refundAddress(
bytes calldata _metadata,
address _default
) internal pure returns (address) {
if (_metadata.length < REFUND_ADDRESS_OFFSET + 20) return _default;
return
address(
bytes20(
_metadata[REFUND_ADDRESS_OFFSET:REFUND_ADDRESS_OFFSET + 20]
)
);
}
/**
* @notice Returns any custom metadata.
* @param _metadata ABI encoded standard hook metadata.
* @return Custom metadata.
*/
function getCustomMetadata(
bytes calldata _metadata
) internal pure returns (bytes calldata) {
if (_metadata.length < MIN_METADATA_LENGTH) return _metadata[0:0];
return _metadata[MIN_METADATA_LENGTH:];
}
/**
* @notice Formats the specified gas limit and refund address into standard hook metadata.
* @param _msgValue msg.value for the message.
* @param _gasLimit Gas limit for the message.
* @param _refundAddress Refund address for the message.
* @param _customMetadata Additional metadata to include in the standard hook metadata.
* @return ABI encoded standard hook metadata.
*/
function formatMetadata(
uint256 _msgValue,
uint256 _gasLimit,
address _refundAddress,
bytes memory _customMetadata
) internal pure returns (bytes memory) {
return
abi.encodePacked(
VARIANT,
_msgValue,
_gasLimit,
_refundAddress,
_customMetadata
);
}
/**
* @notice Formats the specified gas limit and refund address into standard hook metadata.
* @param _msgValue msg.value for the message.
* @return ABI encoded standard hook metadata.
*/
function overrideMsgValue(
uint256 _msgValue
) internal view returns (bytes memory) {
return formatMetadata(_msgValue, uint256(0), msg.sender, "");
}
/**
* @notice Formats the specified gas limit and refund address into standard hook metadata.
* @param _gasLimit Gas limit for the message.
* @return ABI encoded standard hook metadata.
*/
function overrideGasLimit(
uint256 _gasLimit
) internal view returns (bytes memory) {
return formatMetadata(uint256(0), _gasLimit, msg.sender, "");
}
/**
* @notice Formats the specified refund address into standard hook metadata.
* @param _refundAddress Refund address for the message.
* @return ABI encoded standard hook metadata.
*/
function overrideRefundAddress(
address _refundAddress
) internal pure returns (bytes memory) {
return formatMetadata(uint256(0), uint256(0), _refundAddress, "");
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
import { IERC7683 } from "./IERC7683.sol";
/// @title IOriginSettler
/// @notice Standard interface for settlement contracts on the origin chain
/// @dev See https://github.com/ethereum/ERCs/blob/master/ERCS/erc-7683.md
interface IOriginSettler is IERC7683 {
/// @notice Opens a cross-chain order
/// @dev To be called by the user
/// @dev This method must emit the Open event
/// @param order The OnchainCrossChainOrder definition
function open(OnchainCrossChainOrder calldata order) external payable;
/// @notice Resolves a specific OnchainCrossChainOrder into a generic ResolvedCrossChainOrder
/// @dev Intended to improve standardized integration of various order types and settlement contracts
/// @param order The OnchainCrossChainOrder definition
/// @return ResolvedCrossChainOrder hydrated order data including the inputs and outputs of the order
function resolve(OnchainCrossChainOrder calldata order) external view returns (ResolvedCrossChainOrder memory);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
interface IMessageRecipient {
function handle(
uint32 _origin,
bytes32 _sender,
bytes calldata _message
) external payable;
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
interface IPostDispatchHook {
enum Types {
UNUSED,
ROUTING,
AGGREGATION,
MERKLE_TREE,
INTERCHAIN_GAS_PAYMASTER,
FALLBACK_ROUTING,
ID_AUTH_ISM,
PAUSABLE,
PROTOCOL_FEE,
LAYER_ZERO_V1,
RATE_LIMITED,
ARB_L2_TO_L1,
OP_L2_TO_L1,
MAILBOX_DEFAULT_HOOK,
AMOUNT_ROUTING
}
/**
* @notice Returns an enum that represents the type of hook
*/
function hookType() external view returns (uint8);
/**
* @notice Returns whether the hook supports metadata
* @param metadata metadata
* @return Whether the hook supports metadata
*/
function supportsMetadata(
bytes calldata metadata
) external view returns (bool);
/**
* @notice Post action after a message is dispatched via the Mailbox
* @param metadata The metadata required for the hook
* @param message The message passed from the Mailbox.dispatch() call
*/
function postDispatch(
bytes calldata metadata,
bytes calldata message
) external payable;
/**
* @notice Compute the payment required by the postDispatch call
* @param metadata The metadata required for the hook
* @param message The message passed from the Mailbox.dispatch() call
* @return Quoted payment for the postDispatch call
*/
function quoteDispatch(
bytes calldata metadata,
bytes calldata message
) external view returns (uint256);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
library TypeCasts {
// alignment preserving cast
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
// alignment preserving cast
function bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
require(
uint256(_buf) <= uint256(type(uint160).max),
"TypeCasts: bytes32ToAddress overflow"
);
return address(uint160(uint256(_buf)));
}
}