Source Code
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
Loading...
Loading
Contract Name:
CurveFacet
Compiler Version
v0.8.25+commit.b61c2a91
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {OpsCurveAMM, Ops} from "./Ops.sol";
import {ICurveFacet, ICoreFacet} from "./interfaces/ICurveFacet.sol";
import {IAccessControlFacet} from "./interfaces/IAccessControlFacet.sol";
import {IUnifiedPoolAdapter} from "../interfaces/IUnifiedPoolAdapter.sol";
import {CoreFacetStorage} from "./libraries/CoreFacetStorage.sol";
import {CurveFacetStorage} from "./libraries/CurveFacetStorage.sol";
import {TypecastLib} from "../utils/TypecastLib.sol";
contract CurveFacet is ICurveFacet, OpsCurveAMM, Ops {
using CoreFacetStorage for CoreFacetStorage.DS;
using CurveFacetStorage for CurveFacetStorage.DS;
/// @notice Role identifier for operator permissions.
/// @dev Accounts with this role can perform operational tasks like setting pool adapters.
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
/// @dev Modifier that checks that the caller has a specific role.
/// @param role The role identifier to check.
modifier onlyRole(bytes32 role) {
require(IAccessControlFacet(address(this)).hasRole(role, msg.sender), "CurveFacet: missing role");
_;
}
/// @inheritdoc ICurveFacet
function setPoolAdapter(
address pool_,
address poolAdapter_
) external onlyRole(OPERATOR_ROLE) {
require(pool_ != address(0), "CurveFacet: zero address");
CurveFacetStorage.ds().poolAdapter[pool_] = poolAdapter_;
emit PoolAdapterSet(pool_, poolAdapter_);
}
/// @inheritdoc ICurveFacet
function executeCurveAMMOp(
bool /*isOpHalfDone*/,
bytes32 op,
bytes32 nextOp,
bytes memory params,
ICoreFacet.MaskedParams memory prevMaskedParams
)
external
payable
onlyDiamond
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
)
{
if (ADD_CODE == op) {
ICurveFacet.AddParams memory p = abi.decode(params, (ICurveFacet.AddParams));
address adapter = _getPoolAdapter(TypecastLib.castToAddress(p.pool));
(p.amountIn, p.from, p.emergencyTo) = checkMaskedParams(p.amountIn, p.from, p.emergencyTo, prevMaskedParams);
p.to = checkTo(p.to, p.emergencyTo, uint64(block.chainid), op, nextOp);
_transferToAdapter(TypecastLib.castToAddress(p.tokenIn), TypecastLib.castToAddress(p.from), adapter, p.amountIn);
maskedParams.amountOut = IUnifiedPoolAdapter(adapter).addLiquidity(
TypecastLib.castToAddress(p.tokenIn),
p.amountIn,
TypecastLib.castToAddress(p.to),
TypecastLib.castToAddress(p.pool),
p.minAmountOut,
p.i,
TypecastLib.castToAddress(p.emergencyTo)
);
maskedParams.to = p.to;
maskedParams.emergencyTo = p.emergencyTo;
result = _checkResult(maskedParams.amountOut);
} else if (REMOVE_CODE == op) {
ICurveFacet.RemoveParams memory p = abi.decode(params, (ICurveFacet.RemoveParams));
address adapter = _getPoolAdapter(TypecastLib.castToAddress(p.pool));
(p.amountIn, p.from, p.emergencyTo) = checkMaskedParams(p.amountIn, p.from, p.emergencyTo, prevMaskedParams);
p.to = checkTo(p.to, p.emergencyTo, uint64(block.chainid), op, nextOp);
_transferToAdapter(TypecastLib.castToAddress(p.tokenIn), TypecastLib.castToAddress(p.from), adapter, p.amountIn);
maskedParams.amountOut = IUnifiedPoolAdapter(adapter).removeLiquidity(
TypecastLib.castToAddress(p.tokenIn),
p.amountIn,
TypecastLib.castToAddress(p.to),
TypecastLib.castToAddress(p.pool),
p.minAmountOut,
p.j,
TypecastLib.castToAddress(p.emergencyTo)
);
maskedParams.to = p.to;
maskedParams.emergencyTo = p.emergencyTo;
result = _checkResult(maskedParams.amountOut);
} else if (SWAP_CODE == op) {
ICurveFacet.SwapParams memory p = abi.decode(params, (ICurveFacet.SwapParams));
address adapter = _getPoolAdapter(TypecastLib.castToAddress(p.pool));
(p.amountIn, p.from, p.emergencyTo) = checkMaskedParams(p.amountIn, p.from, p.emergencyTo, prevMaskedParams);
p.to = checkTo(p.to, p.emergencyTo, uint64(block.chainid), op, nextOp);
_transferToAdapter(TypecastLib.castToAddress(p.tokenIn), TypecastLib.castToAddress(p.from), adapter, p.amountIn);
maskedParams.amountOut = IUnifiedPoolAdapter(adapter).swap(
TypecastLib.castToAddress(p.tokenIn),
p.amountIn,
TypecastLib.castToAddress(p.to),
TypecastLib.castToAddress(p.pool),
p.minAmountOut,
p.i,
p.j,
TypecastLib.castToAddress(p.emergencyTo)
);
maskedParams.to = p.to;
maskedParams.emergencyTo = p.emergencyTo;
result = _checkResult(maskedParams.amountOut);
} else {
result = ICoreFacet.ExecutionResult.Failed;
}
chainIdTo = 0;
updatedParams = bytes("");
}
/// @inheritdoc ICurveFacet
function poolAdapter(address pool) external view returns (address) {
return CurveFacetStorage.ds().poolAdapter[pool];
}
/// @notice Transfers tokens from `from` to `adapter`, depending on ownership.
/// @param tokenIn Address of the token.
/// @param from Address of sender.
/// @param adapter Address of the target adapter.
/// @param amountIn Amount to transfer.
function _transferToAdapter(address tokenIn, address from, address adapter, uint256 amountIn) private {
if (from == address(this)) {
SafeERC20.safeTransfer(IERC20(tokenIn), adapter, amountIn);
} else {
SafeERC20.safeTransferFrom(IERC20(tokenIn), from, adapter, amountIn);
}
}
/// @notice Returns the adapter address for a given liquidity pool.
/// @param pool The liquidity pool address.
/// @return adapter The corresponding pool adapter address.
/// @dev Reverts if adapter is not registered.
function _getPoolAdapter(address pool) private view returns (address adapter) {
adapter = CurveFacetStorage.ds().poolAdapter[pool];
require(adapter != address(0), "CurveFacet: pool adapter not set");
}
/// @notice Returns the execution result based on the output amount.
/// @param amountOut The amount received after operation.
/// @return The corresponding ExecutionResult enum value.
function _checkResult(uint256 amountOut) private view returns (ICoreFacet.ExecutionResult) {
if (amountOut == 0) {
if (CoreFacetStorage.isOriginNetwork()) {
revert("CurveFacet: slippage");
}
return ICoreFacet.ExecutionResult.Interrupted;
}
return ICoreFacet.ExecutionResult.Succeeded;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
pragma solidity ^0.8.0;
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
*/
library Counters {
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
unchecked {
counter._value += 1;
}
}
function decrement(Counter storage counter) internal {
uint256 value = counter._value;
require(value > 0, "Counter: decrement overflow");
unchecked {
counter._value = value - 1;
}
}
function reset(Counter storage counter) internal {
counter._value = 0;
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity ^0.8.17;
interface IAddressBook {
/// @dev returns portal by given chainId
function portal(uint64 chainId) external view returns (address);
/// @dev returns synthesis by given chainId
function synthesis(uint64 chainId) external view returns (address);
/// @dev returns router by given chainId
function router(uint64 chainId) external view returns (address);
/// @dev returns portal by given chainId
function portalV3(uint64 chainId) external view returns (bytes32);
/// @dev returns synthesis by given chainId
function synthesisV3(uint64 chainId) external view returns (bytes32);
/// @dev returns router by given chainId
function routerV3(uint64 chainId) external view returns (bytes32);
/// @dev returns whitelist
function whitelist() external view returns (address);
/// @dev returns treasury
function treasury() external view returns (address);
/// @dev returns gateKeeper
function gateKeeper() external view returns (address);
/// @dev returns receiver
function receiver() external view returns (address);
/// @dev returns wrapped native asset (WETH)
function WETH() external view returns (address);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2023 - all rights reserved
pragma solidity ^0.8.17;
interface IUnifiedPoolAdapter {
function addLiquidity(
address tokenIn,
uint256 amountIn,
address to,
address pool,
uint256 minAmountOut,
uint8 i,
address emergencyTo
) external returns (uint256 amountOut);
function swap(
address tokenIn,
uint256 amountIn,
address to,
address pool,
uint256 minAmountOut,
uint8 i,
uint8 j,
address emergencyTo
) external returns (uint256 amountOut);
function removeLiquidity(
address tokenIn,
uint256 amountIn,
address to,
address pool,
uint256 minAmountOut,
uint8 j,
address emergencyTo
) external returns (uint256 amountOut);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
interface IAccessControlFacet {
/// @notice Emitted when a role's admin role is changed.
/// @param role The role identifier whose admin role was changed.
/// @param previousAdminRole The previous admin role.
/// @param newAdminRole The new admin role.
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/// @notice Emitted when a role is granted to an account.
/// @param role The role identifier being granted.
/// @param account The address receiving the role.
/// @param sender The address that performed the role grant.
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/// @notice Emitted when a role is revoked from an account.
/// @param role The role identifier being revoked.
/// @param account The address losing the role.
/// @param sender The address that performed the role revocation.
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/// @notice Grants a role to an account.
/// @dev Only callable by accounts that have the admin role of the given role.
/// @param role The role identifier to grant.
/// @param account The address to grant the role to.
function grantRole(bytes32 role, address account) external;
/// @notice Revokes a role from an account.
/// @dev Only callable by accounts that have the admin role of the given role.
/// @param role The role identifier to revoke.
/// @param account The address to revoke the role from.
function revokeRole(bytes32 role, address account) external;
/// @notice Allows a caller to renounce a role for themselves.
/// @dev Caller must be the same as the account being modified.
/// @param role The role identifier to renounce.
/// @param account The address renouncing the role (must be msg.sender).
function renounceRole(bytes32 role, address account) external;
/// @notice Checks whether an account has a specific role.
/// @param role The role identifier to check.
/// @param account The address to check for the role.
/// @return True if the account has the role, false otherwise.
function hasRole(bytes32 role, address account) external view returns (bool);
/// @notice Returns the admin role that controls a specific role.
/// @param role The role identifier to query.
/// @return The admin role associated with the given role.
function getRoleAdmin(bytes32 role) external view returns (bytes32);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./ICoreFacet.sol";
interface IBreakFacet {
/// @notice Break operation attempted as the first step of a resumed tail.
/// @dev Used by {resumeBr} to forbid placing `br` at index 0 of the resumed pipeline.
error BrFirstStepForbidden();
/// @notice Previous step output was not routed back into the router.
/// @dev Thrown when `prevMaskedParams.to` is not equal to the router address during `br`,
/// meaning the router does not currently custody the funds to be escrowed.
error BrPrevToNotRouter();
/// @notice Break with this requestId already exists.
/// @dev Signals that a new `br` tried to reuse a `requestId` whose status is not `None`.
error BrAlreadyExists();
/// @notice Nothing to stash for this break.
/// @dev Thrown when the previous step produced `amountOut == 0`, so there is no value
/// to put under break escrow.
error BrNothingToStash();
/// @notice Invalid break state.
/// @dev Used as a generic internal-safety error, for example when `requestId` is zero
/// or other invariant assumptions do not hold.
error BrInternalState();
/// @notice Break metadata not found.
/// @dev Indicates that no record exists in storage for the specified `requestId`.
error BrNotFound();
/// @notice Break is not in the Pending state.
/// @dev Thrown when an action that requires `Pending` (cancel or resume) is attempted
/// on a break with a different status.
error BrNotPending();
/// @notice Wrong chain.
/// @dev Raised when `resumeBr` is called on a chain whose `block.chainid` does not match
/// the chain recorded in the break metadata.
error BrWrongChain();
/// @notice Wrong cursor position.
/// @dev Thrown if the `cPos` argument passed into `resumeBr` does not match the cursor
/// that was persisted at break time.
error BrWrongCPos();
/// @notice Tail mismatch.
/// @dev Used when:
/// - the provided tail length differs from the stored `tailLen`, or
/// - `keccak256(abi.encode(opsTail, paramsTail))` does not match the stored `tailHash`.
error BrWrongTail();
/// @notice Break resume already started.
/// @dev Signaled when `resumeBr` is invoked again for a break where `resumeStarted`
/// is already true.
error BrAlreadyStarted();
/// @notice Caller is not authorized.
/// @dev Thrown when a function restricted to `initiator` or `emergencyTo` is called
/// by a different address.
error BrNotAuthorized();
/// @notice Break funds have already been consumed.
/// @dev Raised when `resumeBr` is called but the stored reserved balance for this break
/// is already zero, meaning the funds were processed earlier.
error BrAlreadyProcessed();
/// @notice Non-zero msg.value where zero was expected.
/// @dev Used by `resumeBr` and other break-related entry points that must never accept
/// direct ETH.
error BrMsgValueNotAllowed();
/// @notice Router is paused.
/// @dev Thrown when break flows are invoked while the router is paused via the pausable
/// facet.
error BrPaused();
/// @notice Insufficient native balance to escrow.
/// @dev Signals that the router’s native balance is lower than the amount that must
/// be placed into break escrow for the current operation.
error BrInsufficientNative();
/// @notice Insufficient ERC20 balance to escrow.
/// @dev Signals that the router’s ERC20 balance for the given asset is lower than the
/// amount that must be placed into break escrow.
error BrInsufficientToken();
/// @notice Break escrow address is not configured.
/// @dev Used when a break operation requires the escrow contract but the stored escrow
/// address is zero.
error BrEscrowNotSet();
/// @notice Caller is not the configured break escrow contract.
/// @dev Enforces that the router-side callback for native returns can only be invoked
/// by the current BreakEscrow instance.
error BrOnlyEscrow();
/// @notice Stage context was not found for this break.
/// @dev Thrown when `executeBreakOp` is invoked but no staged asset and amount
/// were recorded in `BreakFacetStorage` by the preceding step (e.g. Rubic/Bungee/Runner),
/// typically meaning `BREAK` was not placed directly after a staging operation.
error BrMissingStageContext();
/// @notice Staged amount does not match previous step output.
/// @dev Used when the amount recorded in `BreakFacetStorage` differs from
/// `prevMaskedParams.amountOut` inside `executeBreakOp`, indicating a corrupted
/// or misconfigured pipeline for the current break.
error BrInconsistentAmount();
/// @notice Emitted when a new break (`br`) is created and funds are escrowed.
/// @dev Records the full metadata snapshot for a newly created break:
/// - `requestId` uniquely identifies the break,
/// - `initiator` is the address that triggered `br`,
/// - `emergencyTo` is the account that may later cancel or resume,
/// - `chainId` is the chain on which the break must be resumed,
/// - `cPos` is the cursor position where execution halted,
/// - `tailHash` is `keccak256(abi.encode(opsTail, paramsTail))`,
/// - `tailLen` is the number of operations in the saved tail.
/// @param requestId Unique break identifier.
/// @param initiator Address that initiated the break.
/// @param emergencyTo Emergency recipient that may cancel or resume.
/// @param chainId Chain ID where this break must later be resumed.
/// @param cPos Cursor position within the pipeline where execution stopped.
/// @param tailHash Hash of the recorded tail ops and params.
/// @param tailLen Number of operations stored in the tail.
event BreakCreated(
bytes32 indexed requestId,
address indexed initiator,
address indexed emergencyTo,
uint64 chainId,
uint8 cPos,
bytes32 tailHash,
uint16 tailLen
);
/// @notice Emitted when a break is successfully resumed and its funds consumed.
/// @dev Emitted at the end of a successful `resumeBr` flow, after the reserved balances
/// for this break have been cleared and the status set to `Consumed`.
/// @param requestId Break identifier that was resumed and consumed.
event BreakConsumed(bytes32 indexed requestId);
/// @notice Emitted when a pending break is cancelled with `!br`.
/// @dev Emitted after a break is marked `Cancelled` and its reserved funds are released
/// to the configured emergency recipient.
/// @param requestId Break identifier that was cancelled.
/// @param to Recipient of released funds (usually `emergencyTo`).
event BreakCancelled(bytes32 indexed requestId, address indexed to);
/// @notice Configures the BreakEscrow contract used to hold break funds.
/// @dev Must be called by a privileged router role before any `br` / `!br` / `resumeBr`
/// flows are executed. Reverts with {BrEscrowNotSet} if `escrow` is the zero address.
/// @param escrow Address of the BreakEscrow contract that will custody break funds.
function setBreakEscrow(address escrow) external;
/// @notice Creates a new break (`br`) inside the router pipeline and interrupts execution.
/// @dev Intended to be invoked only by the router’s core execution loop via delegatecall.
/// The function:
/// - validates that `prevMaskedParams.to` points back to the router,
/// - derives the amount to be escrowed from `prevMaskedParams.amountOut`,
/// - moves native or ERC20 funds into the configured BreakEscrow,
/// - records break metadata keyed by `requestId`,
/// - returns `ExecutionResult.Interrupted` so the router stops processing further ops.
/// After a successful call the break status becomes `Pending`.
/// @param isResumeStart Reserved flag propagated by the router. Always false for `br`.
/// @param currentOp Operation code for this step. Expected to equal `BREAK_CODE`.
/// @param nextOp Next operation code in the pipeline. Expected to be zero, since `br` is terminal.
/// @param rawParams ABI-encoded break parameters:
/// - `requestId`: unique ID for this break,
/// - `asset`: token being escrowed (zero address = native),
/// - `cPos`: cursor position of the break,
/// - `tailHash`: keccak256 hash of `(opsTail, paramsTail)`,
/// - `tailLen`: number of ops in the tail.
/// @param prevMaskedParams Masked context from the previous step, including `amountOut`,
/// `to`, and `emergencyTo`. Used to:
/// - determine how much value to escrow,
/// - assert that the router currently holds the funds,
/// - persist the authorized `emergencyTo`.
/// @return chainIdTo Always 0. No cross-chain hop is initiated by `br`.
/// @return updatedParams Always empty. The break does not forward param patches.
/// @return newMaskedParams Masked params that will be carried forward unchanged.
/// @return result Always `ExecutionResult.Interrupted`. Signals the pipeline to stop.
function executeBreakOp(
bool isResumeStart,
bytes32 currentOp,
bytes32 nextOp,
bytes calldata rawParams,
ICoreFacet.MaskedParams calldata prevMaskedParams
)
external
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory newMaskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Cancels a pending break (`!br`) and releases the reserved funds to the emergency recipient.
/// @dev Intended to be invoked only by the router’s core execution loop via delegatecall.
/// The function:
/// - requires that the break status is `Pending`,
/// - requires the caller to be either `initiator` or `emergencyTo`,
/// - requests the BreakEscrow to transfer reserved funds to `emergencyTo`,
/// - clears the logical reserved balance in break storage,
/// - marks the break as `Cancelled`,
/// - returns `ExecutionResult.Succeeded`.
/// After a successful cancellation the break cannot be resumed anymore.
/// @param isResumeStart Reserved flag propagated by the router. Unused for `!br`.
/// @param currentOp Operation code. Expected to equal `CANCEL_BREAK_CODE`.
/// @param nextOp Next operation code. Expected to be zero, since `!br` is terminal.
/// @param rawParams ABI-encoded single field:
/// - `requestId`: identifier of the break to cancel.
/// @param prevMaskedParams Masked context from the previous step. Forwarded unchanged.
/// @return chainIdTo Always 0. No cross-chain hop is initiated by `!br`.
/// @return updatedParams Always empty. No param patching is forwarded.
/// @return newMaskedParams Same masked params as `prevMaskedParams`.
/// @return result Always `ExecutionResult.Succeeded`. Indicates success to the router.
function executeCancelBreakOp(
bool isResumeStart,
bytes32 currentOp,
bytes32 nextOp,
bytes calldata rawParams,
ICoreFacet.MaskedParams calldata prevMaskedParams
)
external
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory newMaskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Resumes a pending break by executing the stored tail of operations.
/// @dev Called directly by `initiator` or `emergencyTo` outside of the normal pipeline.
/// The function:
/// - requires `msg.value == 0`,
/// - checks that the router is not paused,
/// - verifies that the break exists and is `Pending`,
/// - validates caller authorization against `initiator` / `emergencyTo`,
/// - enforces that `chainId`, `cPos`, `tailLen` and `tailHash` match the stored metadata,
/// - reads the reserved balance from break storage and reverts if it is already zero,
/// - pulls the reserved funds from BreakEscrow back to the router,
/// - records resume context via `setResumeMask` in the break-core facet,
/// - ensures the first step of the tail is not another `br`,
/// - executes the tail through `runTailFrom`,
/// - after success, clears reserved balances and marks the break as `Consumed`.
/// If a step in the tail triggers a cross-chain hop, the helper facet handles
/// dispatch and local execution stops at that point.
/// @param requestId Unique break identifier to resume.
/// @param cPos Cursor position saved at break time. Must match the stored cursor.
/// @param opsTail Array of operation names (string identifiers) representing the saved
/// tail of the pipeline after the break.
/// @param paramsTail ABI-encoded params for each op in `opsTail`. Must match `opsTail`
/// one-to-one.
/// @param bridgeOptions Encoded bridge configuration stack used if any resumed step
/// performs a cross-chain hop.
function resumeBr(
bytes32 requestId,
uint8 cPos,
string[] calldata opsTail,
bytes[] calldata paramsTail,
bytes calldata bridgeOptions
)
external
payable;
/// @notice Callback used by the BreakEscrow contract to return native funds to the router.
/// @dev Called by BreakEscrow when native funds are withdrawn back to the router during
/// a resume flow. The function:
/// - requires `msg.sender` to be the configured escrow address,
/// - checks that `requestId` is non-zero,
/// - accepts `msg.value` as router balance without mutating break storage.
/// Logical reserved balances remain tracked inside `BreakFacetStorage`.
/// @param requestId Break identifier whose native funds are being returned to the router.
function receiveNativeFromEscrow(bytes32 requestId) external payable;
/// @notice Returns internal metadata for a break.
/// @dev Provides a read-only view into the stored break record:
/// - `initiator`: address that called `br`,
/// - `emergencyTo`: address allowed to cancel or resume,
/// - `chainId`: chain where the break is anchored,
/// - `cPos`: cursor position where execution paused,
/// - `tailHash`: keccak256(abi.encode(opsTail, paramsTail)) captured at break time,
/// - `tailLen`: number of ops in the recorded tail,
/// - `status`: encoded status enum (None / Pending / Consumed / Cancelled),
/// - `resumeStarted`: whether `resumeBr` has already been invoked.
/// The underlying asset address is stored in the internal struct but is not part
/// of this return set.
/// @param requestId Break identifier to inspect.
/// @return initiator Address that initiated the break.
/// @return emergencyTo Authorized emergency recipient for this break.
/// @return chainId Chain ID where the break must be resumed.
/// @return cPos Saved cursor position for resume.
/// @return tailHash Hash of the recorded tail.
/// @return tailLen Tail length recorded during `br`.
/// @return status Encoded status enum value for this break.
/// @return resumeStarted True if `resumeBr` has already been started.
function getBreakMeta(
bytes32 requestId
)
external
view
returns (
address initiator,
address emergencyTo,
uint64 chainId,
uint8 cPos,
bytes32 tailHash,
uint16 tailLen,
uint8 status,
bool resumeStarted
);
/// @notice Returns the reserved ERC20 balance for a given break and token.
/// @dev Reads the logical reserved amount tracked in break storage for (`requestId`, `token`).
/// The physical custody of these funds is held by BreakEscrow until a resume or cancel
/// operation is executed.
/// @param requestId Break identifier.
/// @param token ERC20 token address.
/// @return balance Amount of `token` currently reserved for this break.
function getTokenBalance(bytes32 requestId, address token) external view returns (uint256 balance);
/// @notice Returns the reserved native balance for a given break.
/// @dev Reads the logical reserved native amount tracked in break storage for `requestId`.
/// The actual ETH is held in BreakEscrow until resume or cancel flows are executed.
/// @param requestId Break identifier.
/// @return balance Amount of native currency currently reserved for this break.
function getNativeBalance(bytes32 requestId) external view returns (uint256 balance);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./ICoreFacet.sol";
interface IBungeeFacet {
/// @notice ERC20 approval data for a Bungee "manual route" call built off-chain.
/// @dev
/// If `approvalTokenAddress` is non-zero, the facet will:
/// - compute how much of that token it is about to send into the downstream call,
/// - make sure allowance for `allowanceTarget` is at least `minimumApprovalAmount`
/// (or that computed amount, whichever is larger),
/// - then later reset that allowance to zero.
struct ManualApprovalData {
/// @notice Address of the ERC20 that needs approval.
/// @dev Zero address means "no ERC20 approval needed / expect native-only".
address approvalTokenAddress;
/// @notice Spender to approve.
/// @dev Must be allowlisted via {setBungeeSpender}.
address allowanceTarget;
/// @notice Minimum allowance that must exist before calling the downstream target.
/// @dev The facet will approve `max(minimumApprovalAmount, totalInputAmountOfToken)`.
uint256 minimumApprovalAmount;
}
/// @notice Off-chain constructed call description for a manual Bungee route.
/// @dev
/// The idea of "manual" is: integrator / backend calls Bungee (Socket) API,
/// gets a ready-to-execute tx (target, data, value), and passes it here.
/// The facet will:
/// - forward the summed native input + `value` + `nativeValue` from params,
/// - optionally set ERC20 allowance using `approvalData`,
/// - perform the external call,
/// - zero the allowance again.
struct ManualBuildTx {
/// @notice Target contract that actually executes the Bungee route.
/// @dev Must be allowlisted via {setBungeeTarget}.
address payable txTarget;
/// @notice Calldata to send to `txTarget`.
bytes txData;
/// @notice Extra ETH to attach specifically to this call,
/// @dev in addition to any ETH aggregated from user inputs and `nativeValue`.
uint256 value;
/// @notice Optional ERC20 approval info.
/// @dev If `approvalTokenAddress` is zero, no ERC20 approval path is executed.
ManualApprovalData approvalData;
}
/// @notice Parameters for starting a manual Bungee operation.
/// @dev
/// High-level flow:
/// - We collect inputs (native and/or ERC20s) from `from`.
/// - We may approve an allowlisted spender.
/// - We call `buildTx.txTarget` with `buildTx.txData` and the computed ETH value.
/// - We reset approvals back to zero.
/// - We measure how much of `stageAsset` ended up staged on the router after that call.
///
/// `tokens` / `amounts`:
/// - `tokens[i]` is the input token encoded as bytes32; `address(0)` (cast from bytes32) means native token.
/// - `amounts[i]` is how much of that token to use.
/// - If `amounts[i] == type(uint256).max`, we substitute `prevMaskedParams.amountOut` for that entry.
///
/// `from`:
/// - bytes32-encoded address we should pull ERC20 funds from (using safeTransferFrom).
/// - If `from == 0`, we fallback to `prevMaskedParams.to`. (Meaning: use output of previous pipeline step.)
/// - If `from != 0`, then on the origin call it MUST equal `msg.sender`.
///
/// `emergencyTo`:
/// - bytes32-encoded address for emergency withdrawal fallback.
/// - If `emergencyTo == 0`, we fallback to `prevMaskedParams.emergencyTo`.
/// - If `emergencyTo != 0`, then on the origin call it MUST equal `msg.sender`.
///
/// `nativeValue`:
/// - Extra ETH we must include in the downstream call in addition to any ETH amounts coming from `tokens[i]`.
///
/// Staging (`stageAsset`, `minStageAmount`) and BREAK:
/// - `stageAsset` is the token (as bytes32 address; `0` = native ETH) we EXPECT to remain sitting on the router
/// after Bungee completes.
/// - We snapshot router balance of that asset before and after the Bungee call.
/// For ETH, we adjust the "before" snapshot by subtracting exactly the ETH we are about to forward
/// so that forwarded call value is not counted as staged output.
/// - The delta is "stagedDelta".
/// - If the next op is BREAK (`nextOp == BreakOps.BREAK_CODE`), we REQUIRE `stagedDelta >= minStageAmount`.
/// Then we surface that staged amount to the BREAK via `maskedParams`.
/// - If the next op is terminal (`nextOp == 0`), we do not enforce staging and return zeroed amountOut/to.
struct ManualStartParams {
/// @notice Input asset addresses encoded as bytes32 (`0` = native token).
bytes32[] tokens;
/// @notice Amounts corresponding to `tokens`.
/// @dev `type(uint256).max` means "use prevMaskedParams.amountOut" for that entry.
uint256[] amounts;
/// @notice bytes32-encoded source of funds.
/// @dev If zero, defaults to `prevMaskedParams.to`. Otherwise must match `msg.sender` on origin.
bytes32 from;
/// @notice Extra ETH to add on top of summed native inputs.
/// @dev Used for fees / integrator-required msg.value top-ups.
uint256 nativeValue;
/// @notice bytes32-encoded emergency recipient.
/// @dev If zero, defaults to `prevMaskedParams.emergencyTo`. Otherwise must match `msg.sender` on origin.
bytes32 emergencyTo;
/// @notice Asset we expect to remain staged on the router if we BREAK next.
/// @dev bytes32-encoded token address. `0` means native ETH.
bytes32 stageAsset;
/// @notice Minimal acceptable amount of `stageAsset` that must be staged on the router post-call.
/// @dev Only enforced if `nextOp == BreakOps.BREAK_CODE`.
uint256 minStageAmount;
/// @notice Off-chain prebuilt call definition for the Bungee step.
ManualBuildTx buildTx;
}
/// @notice ERC20 approval data for an "auto route" Bungee call.
/// @dev
/// "Auto" = we already know the target, calldata, and required approval on-chain
/// (for example, a pre-integrated Socket/Bungee router).
///
/// If `tokenAddress` is non-zero, the facet:
/// - computes total ERC20 amount it's about to send,
/// - grants allowance to `spenderAddress` for `max(amount, totalInputAmount)`,
/// - executes the call,
/// - resets allowance back to zero.
struct AutoApprovalData {
/// @notice ERC20 that must be approved for spending.
/// @dev Zero means "no ERC20 approval path / expect native-only".
address tokenAddress;
/// @notice Spender that will pull the tokens.
/// @dev Must be allowlisted via {setBungeeSpender}.
address spenderAddress;
/// @notice Minimum allowance that must be guaranteed before the call.
/// @dev The facet will approve `max(amount, totalInputAmount)`.
uint256 amount;
}
/// @notice On-chain call specification for an auto Bungee route.
/// @dev
/// The facet will call `to` directly with `data`, attach the required ETH,
/// handle approvals according to `approval`, and then revoke approvals.
struct AutoTxData {
/// @notice Destination contract to invoke.
/// @dev Must be allowlisted via {setBungeeTarget}.
address payable to;
/// @notice Calldata for the Bungee route call.
bytes data;
/// @notice Extra ETH to attach to the call in addition to aggregated native inputs.
uint256 value;
/// @notice Optional ERC20 approval config.
AutoApprovalData approval;
}
/// @notice Parameters for starting an auto Bungee operation.
/// @dev
/// Semantics match {ManualStartParams} but instead of `ManualBuildTx` we have fixed `autoTx`.
///
/// `tokens`, `amounts`, `from`, `emergencyTo`, `nativeValue`:
/// - behave exactly like in {ManualStartParams}.
///
/// Staging / BREAK semantics are also identical:
/// - We snapshot balance of `stageAsset` before and after the `autoTx` call (with ETH "before" adjusted
/// by subtracting the ETH we're about to forward).
/// - Compute stagedDelta.
/// - If `nextOp == BreakOps.BREAK_CODE`, require `stagedDelta >= minStageAmount`
/// and surface it to BREAK in `maskedParams`.
struct AutoStartParams {
/// @notice Input asset addresses (bytes32, `0` = native).
bytes32[] tokens;
/// @notice Input amounts matching `tokens`.
/// @dev `type(uint256).max` means "use prevMaskedParams.amountOut" for that index".
uint256[] amounts;
/// @notice bytes32-encoded address from which ERC20 funds should be sourced.
/// @dev Zero means "use prevMaskedParams.to". Otherwise must equal `msg.sender` on origin.
bytes32 from;
/// @notice Extra ETH to include on top of summed native inputs.
uint256 nativeValue;
/// @notice bytes32-encoded emergency recipient for fallback withdrawal.
/// @dev Zero means "use prevMaskedParams.emergencyTo". Otherwise must equal `msg.sender` on origin.
bytes32 emergencyTo;
/// @notice Asset whose balance increase on the router we treat as staged output if we BREAK next.
/// @dev bytes32-encoded token address, or zero for native ETH.
bytes32 stageAsset;
/// @notice Minimal acceptable staged amount of `stageAsset` after the call.
/// @dev Only enforced if `nextOp == BreakOps.BREAK_CODE`.
uint256 minStageAmount;
/// @notice On-chain call data for the auto route.
AutoTxData autoTx;
}
/// @notice One refund transfer instruction to be executed by {executeBungeeRefundOp}.
/// @dev
/// `token` encoding:
/// - `token == bytes32(0)` means native ETH.
/// - otherwise `token` is a bytes32-encoded ERC20 token address.
///
/// `unwrapWETH`:
/// - If `unwrapWETH == true`, then:
/// - `token` MUST be WETH (as configured in Core storage),
/// - the facet unwraps WETH into native ETH and sends ETH to the recipient.
/// - If `token == 0` (native ETH), `unwrapWETH` MUST be false.
struct RefundItem {
/// @notice Asset identifier (bytes32-encoded token address; `0` = native ETH).
bytes32 token;
/// @notice Amount of the asset to refund.
uint256 amount;
/// @notice Whether to unwrap WETH into native ETH before sending.
bool unwrapWETH;
}
/// @notice Parameters for a single refund payout executed by {executeBungeeRefundOp}.
/// @dev
/// This op is intended for backend-triggered refunds on the origin chain, paid from the router’s
/// existing balance (funds must already be on the router at the time of execution).
///
/// Constraints expected by the implementation:
/// - `to` MUST decode to a non-zero address.
/// - `refundId` MUST be non-zero and is used for idempotency / replay protection:
/// the facet marks `refundId` as used and reverts if the same `refundId` is submitted again.
/// - `item.amount` MUST be non-zero.
/// - `item.token` and `item.unwrapWETH` semantics are described in {RefundItem}.
struct RefundParams {
/// @notice bytes32-encoded recipient address.
bytes32 to;
/// @notice Mandatory idempotency / replay-protection key.
bytes32 refundId;
/// @notice Refund transfer instruction.
RefundItem item;
}
/// @notice Scratch space for intermediate values in `executeBungeeManualOp` / `executeBungeeAutoOp`.
/// @dev
/// This struct exists only to avoid "stack too deep" during execution of the Bungee ops.
/// Instead of keeping many separate local variables on the stack, the facet creates a single
/// `LocalVars memory v;` and stores all working state there.
///
/// High-level meaning:
/// - We resolve who provides funds (`fromAddr`), pull ERC20s if needed, and possibly approve a spender.
/// - We aggregate native (ETH) input and compute how much ETH must be forwarded into the external Bungee call.
/// - We snapshot router balances of the expected "staging" asset before and after the Bungee call to determine
/// how much actually remained on the router (`stagedDelta`).
/// - If the pipeline is about to BREAK, `stagedDelta` is enforced against `minStageAmount` and then surfaced
/// back through `maskedParams`.
struct LocalVars {
/// @notice Final resolved address whose funds are being used for this step.
/// @dev
/// Comes from `p.from` (or `prevMaskedParams.to` if `p.from == 0`) via `checkMaskedParams`.
/// On origin calls, if `p.from` was non-zero it MUST equal `msg.sender`.
address fromAddr;
/// @notice ERC20 token address actually being spent for this Bungee call, if any.
/// @dev
/// `_collectInputs` enforces that all non-native inputs are the same ERC20;
/// if there are no ERC20 inputs this will remain the zero address.
address erc20TokenIn;
/// @notice Asset we are tracking as "staged" on the router after the Bungee call.
/// @dev
/// This is `p.stageAsset` decoded to an `address`. `address(0)` means native ETH.
/// We snapshot this asset’s balance on the router before and after the external call
/// to measure how much value actually stayed here for the BREAK step.
address stageAssetAddr;
/// @notice Masked emergency withdrawal recipient that will be propagated downstream.
/// @dev
/// Obtained from `checkMaskedParams`. If a non-zero emergency recipient was provided
/// on origin, it must equal `msg.sender`; otherwise we fall back to `prevMaskedParams.emergencyTo`.
bytes32 emergencyTo;
/// @notice Total native amount requested from inputs.
/// @dev
/// Sum of all `p.amounts[i]` where the corresponding `p.tokens[i] == address(0)`.
/// Represents user-supplied ETH that should go into the Bungee call.
uint256 nativeRequired;
/// @notice Total ERC20 amount being provided across inputs for `erc20TokenIn`.
/// @dev
/// `_collectInputs` accumulates this. If `fromAddr` is not the router itself,
/// we `safeTransferFrom` this amount to the router before approval/call.
uint256 erc20TotalIn;
/// @notice The ERC20 allowance amount we grant to the downstream spender.
/// @dev
/// For manual mode: `max(erc20TotalIn, minimumApprovalAmount)`.
/// For auto mode: `max(erc20TotalIn, autoTx.approval.amount)`.
/// After the call we always reset this allowance back to zero.
uint256 approveAmount;
/// @notice Router balance of `stageAssetAddr` before performing the external Bungee call.
/// @dev
/// For ETH, this is `address(this).balance`.
/// For ERC20, this is `IERC20(stageAssetAddr).balanceOf(address(this))`.
uint256 balBefore;
/// @notice Exact ETH value that will be forwarded to the external Bungee call.
/// @dev
/// Computed as `nativeRequired + p.nativeValue + buildTx.value` (manual)
/// or `nativeRequired + p.nativeValue + autoTx.value` (auto).
/// `_checkMsgValue` enforces:
/// - On origin call: `msg.value` MUST equal this.
/// - On resume call: `msg.value` MUST be 0 and the router balance MUST already cover it.
uint256 valueToSend;
/// @notice Router balance of `stageAssetAddr` after the external Bungee call returns (and after approvals are cleared).
/// @dev
/// Measured the same way as `balBefore`.
uint256 balAfter;
/// @notice Adjusted "before" balance used to measure the actually staged output.
/// @dev
/// For ETH, we conceptually subtract `valueToSend` from `balBefore` so we do not count
/// ETH that we intentionally forwarded into the external call as if it remained staged.
/// For ERC20, this is simply `balBefore`.
uint256 effectiveBefore;
/// @notice Net amount of `stageAssetAddr` that truly ended up staged on the router.
/// @dev
/// Computed as `balAfter - effectiveBefore`.
/// If `nextOp == BreakOps.BREAK_CODE`, this must be `>= p.minStageAmount`, otherwise we revert.
/// On success in BREAK mode, this becomes `maskedParams.amountOut` and we mark
/// `maskedParams.to = address(this)` so the BREAK step knows funds are held by the router.
uint256 stagedDelta;
}
/// @notice Emitted once per refunded item when {executeBungeeRefundOp} performs a payout.
/// @dev
/// - `refundId` is the idempotency key provided in {RefundParams}. May be zero.
/// - `token` reflects the item token (native ETH is represented as `address(0)`).
/// - If `unwrapped == true`, the item source token was WETH and the user received native ETH.
/// (I.e., transfer asset is ETH, while the source token is WETH.)
/// @param refundId The refund id used for replay protection (may be zero).
/// @param to The recipient of the refund.
/// @param token The asset identifier: `address(0)` for native ETH, otherwise ERC20 token address.
/// @param amount The amount refunded.
/// @param unwrapped True if WETH was unwrapped into native ETH before sending.
event RefundExecuted(
bytes32 indexed refundId,
address indexed to,
address indexed token,
uint256 amount,
bool unwrapped
);
/// @notice Executes the manual Bungee op.
/// @dev
/// Two routing modes depending on `nextOp`:
///
/// 1. Terminal mode:
/// - `nextOp == bytes32(0)`.
/// - We:
/// * pull/aggregate inputs (ERC20 and/or native),
/// * optionally approve `approvalData.allowanceTarget`,
/// * call `buildTx.txTarget` with the composed ETH value,
/// * reset approval to zero.
/// - We DO NOT require or expose staged output.
/// - Return convention:
/// * `chainIdTo = 0`
/// * `updatedParams = ""`
/// * `maskedParams.amountOut = 0`
/// * `maskedParams.to = 0`
/// * `maskedParams.emergencyTo = propagated emergencyTo`
/// * `result = ICoreFacet.ExecutionResult.Succeeded`
///
/// 2. Pre-break mode:
/// - `nextOp == BreakOps.BREAK_CODE`.
/// - After the external call succeeds, we snapshot how much of `p.stageAsset`
/// actually ended up sitting on the router ("stagedDelta"), where for ETH we
/// adjust the "before" snapshot by subtracting the ETH we were about to forward.
/// - We REQUIRE `stagedDelta >= p.minStageAmount`, otherwise revert.
/// - Return convention:
/// * `chainIdTo = 0`
/// * `updatedParams = ""`
/// * `maskedParams.amountOut = stagedDelta`
/// * `maskedParams.to = address(this)` (router now holds staged funds)
/// * `maskedParams.emergencyTo = propagated emergencyTo`
/// * `result = ICoreFacet.ExecutionResult.Succeeded`
///
/// msg.value rules (enforced internally via `_checkMsgValue`):
/// - On origin call (`CoreFacetStorage.currentRequestId() == 0`), `msg.value` MUST equal
/// the ETH we are about to forward downstream.
/// - On resume call, `msg.value` MUST be zero, and the router's own ETH balance MUST
/// already cover the required call value.
///
/// @param isOpHalfDone Reserved flag propagated by the router, ignored by this facet.
/// @param op Operation code; must equal `BUNGEE_MANUAL_START_CODE`.
/// @param nextOp Next pipeline step. Must be either:
/// - `bytes32(0)` for terminal mode, or
/// - `BreakOps.BREAK_CODE` if we intend to pause after staging funds.
/// @param params ABI-encoded {ManualStartParams} describing inputs, approvals, call target, etc.
/// @param prevMaskedParams Masked params from the previous pipeline step. Supplies fallback
/// `from`, `emergencyTo`, and previous `amountOut` for sentinel `type(uint256).max`.
/// @return chainIdTo Always 0. This facet does not itself schedule a cross-chain hop.
/// @return updatedParams Always empty bytes (`""`). No param mutation for downstream ops.
/// @return maskedParams
/// - Terminal mode: {amountOut=0, to=0, emergencyTo=propagated}
/// - Pre-break mode: {amountOut=stagedDelta, to=router, emergencyTo=propagated}
/// @return result Always `ICoreFacet.ExecutionResult.Succeeded` if we didn't revert.
function executeBungeeManualOp(
bool isOpHalfDone,
bytes32 op,
bytes32 nextOp,
bytes calldata params,
ICoreFacet.MaskedParams calldata prevMaskedParams
)
external
payable
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Executes the auto Bungee op.
/// @dev
/// Same behavioral contract as {executeBungeeManualOp}, except that:
/// - Instead of `ManualBuildTx`, we use `AutoTxData` (which is expected to be known/curated on-chain).
/// - All approvals / target contracts must still pass allowlist checks.
///
/// Modes:
///
/// 1. Terminal mode (`nextOp == 0`):
/// - Pull inputs (ERC20/native), approve if needed, call `autoTx.to` with `autoTx.data`
/// and the computed ETH value, then revoke approvals.
/// - Return masked params with `amountOut=0`, `to=0`, `emergencyTo` propagated.
///
/// 2. Pre-break mode (`nextOp == BreakOps.BREAK_CODE`):
/// - Same call flow, but we also:
/// * snapshot router balance of `p.stageAsset` before/after (ETH "before" is adjusted
/// by subtracting the ETH we're about to forward),
/// * compute `stagedDelta`,
/// * require `stagedDelta >= p.minStageAmount`,
/// * return `amountOut=stagedDelta`, `to=router`, `emergencyTo` propagated.
///
/// msg.value rules:
/// - On origin call, `msg.value` MUST equal the ETH to be forwarded.
/// - On resume call, `msg.value` MUST be 0 and the router must already hold enough ETH.
///
/// @param isOpHalfDone Reserved pipeline flag, ignored here.
/// @param op Operation code; must equal `BUNGEE_AUTO_START_CODE`.
/// @param nextOp Must be either zero (terminal) or `BreakOps.BREAK_CODE` (break/stage).
/// @param params ABI-encoded {AutoStartParams}.
/// @param prevMaskedParams Previous step's masked params for fallback `from`, `emergencyTo`,
/// and substitution for `type(uint256).max` amounts.
/// @return chainIdTo Always 0. Auto route itself doesn't hop chains here.
/// @return updatedParams Always empty (`""`). No param forwarding mutation.
/// @return maskedParams
/// - Terminal mode: {amountOut=0, to=0, emergencyTo=propagated}
/// - Pre-break mode: {amountOut=stagedDelta, to=router, emergencyTo=propagated}
/// @return result Always `ICoreFacet.ExecutionResult.Succeeded` if successful.
function executeBungeeAutoOp(
bool isOpHalfDone,
bytes32 op,
bytes32 nextOp,
bytes calldata params,
ICoreFacet.MaskedParams calldata prevMaskedParams
)
external
payable
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Executes a backend-triggered refund from the router’s balance on the origin network.
/// @dev
/// Purpose:
/// - Some Bungee/Socket routes may produce delayed refunds (funds arrive to the router later).
/// - This op allows an operator to transfer already-received funds out to the user.
///
/// Execution constraints (enforced by implementation):
/// - `op` MUST equal `BungeeOps.BUNGEE_REFUND_CODE`.
/// - `nextOp` MUST be `bytes32(0)` (refund is always terminal).
/// - Must run on the origin network only: `CoreFacetStorage.isOriginNetwork() == true`.
/// - Caller must be the diamond (`onlyDiamond` in the facet implementation); access control
/// may additionally be enforced at the router/diamond level.
///
/// Idempotency / replay protection:
/// - `RefundParams.refundId` MUST be non-zero.
/// - The facet records `refundId` as consumed and reverts if it was already used.
///
/// Refund item semantics:
/// - If `item.token == 0`: sends native ETH; `item.unwrapWETH` MUST be false.
/// - If `item.token != 0 && item.unwrapWETH == false`: transfers ERC20 token via `safeTransfer`.
/// - If `item.unwrapWETH == true`: `item.token` MUST equal WETH configured in Core storage;
/// the facet unwraps WETH into ETH and sends native ETH.
///
/// Events:
/// - Emits {RefundExecuted} once for this payout.
///
/// @param isOpHalfDone Reserved pipeline flag propagated by the router; ignored by this op.
/// @param op Operation code; must equal `BUNGEE_REFUND_CODE`.
/// @param nextOp Must be `bytes32(0)` (terminal).
/// @param params ABI-encoded {RefundParams}.
/// @param prevMaskedParams Unused for refunds (present for unified op interface).
/// @return chainIdTo Always 0 (no cross-chain hop is scheduled by this op).
/// @return updatedParams Always empty bytes (`""`).
/// @return maskedParams Always zeroed (refund op does not produce staged output).
/// @return result Always {ICoreFacet.ExecutionResult.Succeeded} if the op does not revert.
function executeBungeeRefundOp(
bool isOpHalfDone,
bytes32 op,
bytes32 nextOp,
bytes calldata params,
ICoreFacet.MaskedParams calldata prevMaskedParams
)
external
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Adds or removes a downstream call target from the allowlist.
/// @dev
/// - `target` cannot be the zero address.
/// - Only callable by an account with `OPERATOR_ROLE` on the diamond.
/// - Calls to Bungee (Socket) routers and helpers MUST go through allowlisted targets.
///
/// @param target The external contract that this facet is allowed to call.
/// @param allowed Whether that contract address is considered allowed.
function setBungeeTarget(address target, bool allowed) external;
/// @notice Adds or removes an address from the spender allowlist.
/// @dev
/// - `spender` cannot be the zero address.
/// - Only callable by an account with `OPERATOR_ROLE` on the diamond.
/// - Before giving any ERC20 allowance, the facet checks that the spender is allowlisted.
///
/// @param spender The spender (router / proxy / aggregator) to add or remove.
/// @param allowed Whether this spender address is allowed to receive approvals.
function setBungeeSpender(address spender, bool allowed) external;
/// @notice Returns whether a given `target` is currently allowlisted as a valid Bungee call target.
/// @param target The contract address to check.
/// @return isAllowed True if `target` is allowed, false otherwise.
function isBungeeTargetAllowed(address target) external view returns (bool isAllowed);
/// @notice Returns whether a given `spender` is allowlisted for ERC20 approvals.
/// @param spender The spender address to check.
/// @return isAllowed True if `spender` is allowed, false otherwise.
function isBungeeSpenderAllowed(address spender) external view returns (bool isAllowed);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ProcessedOps} from "../libraries/CoreFacetStorage.sol";
interface ICoreFacet {
/// @notice Result of an execution step.
enum ExecutionResult {
/// @dev The operation failed.
Failed,
/// @dev The operation succeeded.
Succeeded,
/// @dev The operation was interrupted and should resume on the next chain.
Interrupted
}
/// @notice State of a cross-chain operation tracked by requestId.
enum CrossChainOpState {
/// @dev Operation has not yet been processed.
Unknown,
/// @dev Operation successfully completed.
Succeeded,
/// @dev Operation was reverted (e.g. emergency cancel).
Reverted
}
/// @notice Signed invoice data for execution payment.
struct Invoice {
uint256 executionPrice;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
/// @notice Signed invoice data for execution payment.
struct InvoiceV4 {
Invoice invoice;
uint256 feeShare;
address feeShareRecipient;
address feeToken;
}
/// @notice Holds result values between cross-chain operation steps.
struct MaskedParams {
uint256 amountOut;
bytes32 to;
bytes32 emergencyTo;
}
/// @notice Execution context passed between operation steps.
struct ExecutionContext {
MaskedParams maskedParams;
bytes updatedParams;
}
/// @notice Emitted when a cross-chain operation completes or transfers to another chain.
/// @param currentChainId The chain ID where the event occurred.
/// @param currentRequestId The current request ID being processed.
/// @param nextChainId Target chain ID if continuation is needed.
/// @param nextRequestId New request ID for the next chain.
/// @param result Result of the execution.
/// @param lastOp Index of the last successfully executed operation.
event ComplexOpProcessed(
uint64 indexed currentChainId,
bytes32 indexed currentRequestId,
uint64 indexed nextChainId,
bytes32 nextRequestId,
ExecutionResult result,
uint8 lastOp
);
/// @notice Emitted when execution fees are paid.
/// @param payer The address that submitted the request and paid the fee.
/// @param token The token in which fee was paid.
/// @param accountant The address that received the fee.
/// @param executionPrice Amount of ETH paid for the execution.
event FeePaid(address indexed payer, address token, address accountant, uint256 executionPrice);
/// @notice Sets the address book contract address.
/// @param addressBook_ The address of the address book contract.
/// @dev Can only be called by an account with DEFAULT_ADMIN_ROLE.
function setAddressBook(address addressBook_) external;
/// @notice Caches WETH from address book.
/// @dev Can only be called by an account with DEFAULT_ADMIN_ROLE.
function resetWETH() external;
/// @notice Caches Treasury from address book.
/// @dev Can only be called by an account with DEFAULT_ADMIN_ROLE.
function resetTreasury() external;
/// @notice Verifies that the caller is the trusted router from the source chain and correct selector is passed.
/// @param selector The function selector, expected to be `ICoreFacet.resume.selector`.
/// @param from The sender address from the source chain.
/// @param chainIdFrom The chain ID from which the call originated.
/// @return True if validation passed, otherwise reverts.
function receiveValidatedData(
bytes4 selector,
bytes32 from,
uint64 chainIdFrom
) external returns (bool);
/// @notice Starts the execution of a cross-chain operation on the origin network.
/// @param operations List of operation names to execute.
/// @param params Encoded parameters for each operation.
/// @param receipt Signed receipt (invoice) for fee validation.
/// @param bridgeOptions Encoded options for bridging to other chains.
function start(
string[] calldata operations,
bytes[] calldata params,
InvoiceV4 calldata receipt,
bytes memory bridgeOptions
) external payable;
/// @notice Resumes the execution of a cross-chain operation on the destination network.
/// @param requestId The request ID of the cross-chain operation.
/// @param cPos The index of the operation to resume from.
/// @param operations List of operation names.
/// @param params Parameters for each operation.
/// @param bridgeOptions Encoded options for continued bridging.
/// @dev Can only be called by the configured receiver and requires valid requestId context.
function resume(
bytes32 requestId,
uint8 cPos,
string[] calldata operations,
bytes[] memory params,
bytes memory bridgeOptions
) external;
/// @notice Current nonce counter for a given address.
/// @param account User address whose nonce is requested.
/// @return Present value of `RouterStorage.DS.nonces[account]`.
function nonces(address account) external view returns (uint256);
/// @notice Parameters hash with which a cross-chain request was started.
/// @param requestId Identifier of the cross-chain request.
/// @return keccak256 hash stored in `RouterStorage.DS.startedOps[requestId]`.
function startedOps(bytes32 requestId) external view returns (bytes32);
/// @notice Processing state of a cross-chain request.
/// @param requestId Identifier of the cross-chain request.
/// @return The structure with information about cross-chain transaction delivery (see defination of ProcessedOps).
function processedOps(bytes32 requestId) external view returns (ProcessedOps memory);
/// @notice Cached AddressBook contract address.
/// @return Address stored in `RouterStorage.DS.AddressBook`.
function addressBook() external view returns (address);
/// @notice Cached Treasury contract address.
/// @return Address stored in `RouterStorage.DS.treasury`.
function treasury() external view returns (address);
/// @notice Returns address of wrapped ETH contract.
/// @return The address of wrapped ETH contract.
function WETH() external view returns (address);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./ICoreFacet.sol";
interface ICrosschainFacet {
/// @notice Parameters for ERC20 `permit` signature-based approval.
struct PermitParams {
bytes32 token;
bytes32 owner;
uint256 amount;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
/// @notice Parameters for synthetic token operations (mint/burn/lock/unlock).
struct SynthParams {
bytes32 tokenIn;
uint256 amountIn;
bytes32 from;
bytes32 to;
uint64 chainIdTo;
uint64 tokenInChainIdFrom;
bytes32 emergencyTo;
}
/// @notice Parameters for wrapping and unwrapping tokens (e.g. ETH <-> WETH).
struct WrapParams {
bytes32 tokenIn;
uint256 amountIn;
bytes32 from;
bytes32 to;
}
/// @notice Parameters for emergency cancellation of a cross-chain request.
struct CancelParams {
bytes32 requestId;
bytes32 hashSynthParams;
uint64 chainIdTo;
SynthParams emergencyParams;
}
/// @notice Handles main operational logic for core operation types (permit, mint, lock, etc.).
/// @param isOpHalfDone True if operation is continuing from another chain.
/// @param op Current operation code.
/// @param nextOp Next operation code.
/// @param params Parameters for the operation.
/// @param prevMaskedParams Previous masked parameters to inherit state.
/// @return chainIdTo Target chain ID (if any).
/// @return updatedParams Updated params to pass for cross-chain.
/// @return maskedParams Resulting masked params.
/// @return result Execution result (Succeeded, Failed, or Interrupted).
function executeCrosschainOp(
bool isOpHalfDone,
bytes32 op,
bytes32 nextOp,
bytes memory params,
ICoreFacet.MaskedParams memory prevMaskedParams
)
external
payable
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./ICoreFacet.sol";
interface ICurveFacet {
/// @notice Parameters for adding liquidity to a pool.
struct AddParams {
bytes32 tokenIn;
uint256 amountIn;
bytes32 from;
bytes32 to;
bytes32 pool;
uint256 minAmountOut;
uint8 i;
bytes32 emergencyTo;
}
/// @notice Parameters for removing liquidity from a pool.
struct RemoveParams {
bytes32 tokenIn;
uint256 amountIn;
bytes32 from;
bytes32 to;
bytes32 pool;
uint256 minAmountOut;
uint8 j;
bytes32 emergencyTo;
}
/// @notice Parameters for performing a token swap.
struct SwapParams {
bytes32 tokenIn;
uint256 amountIn;
bytes32 from;
bytes32 to;
bytes32 pool;
uint256 minAmountOut;
uint8 i;
uint8 j;
bytes32 emergencyTo;
}
/// @notice Emitted when a new adapter is set for a liquidity pool.
/// @param pool The address of the liquidity pool.
/// @param adapter The address of the adapter associated with the pool.
event PoolAdapterSet(address pool, address adapter);
/// @notice Sets the adapter for a specific liquidity pool.
/// @param pool_ The address of the liquidity pool.
/// @param poolAdapter_ The address of the corresponding pool adapter.
/// @dev Can only be called by an account with OPERATOR_ROLE.
/// Reverts if the pool address is zero.
function setPoolAdapter(
address pool_,
address poolAdapter_
) external;
/// @notice Handles core (permit, mint, lock, etc.) and pool-related operations (add, remove, swap).
/// @param isOpHalfDone True if operation is resuming.
/// @param op Operation code.
/// @param nextOp Next operation code.
/// @param params Parameters for the operation.
/// @param prevMaskedParams Previously accumulated masked params.
/// @return chainIdTo Target chain ID.
/// @return updatedParams Updated parameters (for cross-chain execution).
/// @return maskedParams Updated masked state.
/// @return result Execution result.
function executeCurveAMMOp(
bool isOpHalfDone,
bytes32 op,
bytes32 nextOp,
bytes memory params,
ICoreFacet.MaskedParams memory prevMaskedParams
)
external
payable
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Adapter contract that the router uses for a specific liquidity pool.
/// @param pool Address of the liquidity pool.
/// @return Address stored in `RouterStorage.DS.poolAdapter[pool]`.
function poolAdapter(address pool) external view returns (address);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./ICoreFacet.sol";
import {ResumedOps} from "../libraries/OFTFacetStorage.sol";
interface IOFTFacet {
/// @notice State of a lzCompose operation tracked by guid.
enum OftOpState {
/// @dev Operation has not yet been processed.
Unknown,
/// @dev Operation successfully completed.
Succeeded,
/// @dev Operation was reverted (e.g. emergency cancel).
Reverted,
/// @dev Operation was corrected.
Pushed
}
/// @notice Patterns of interaction between the OFT contract and the token.
enum OftType {
/// @dev The token itself supports the OFT standard.
Token,
/// @dev The OFT contract is the owner of the token.
Owner,
/// @dev The OFT contract is an adapter for the token.
Adapter
}
/// @notice Parameters for send OFT token operations to dist chain.
struct OftParams {
bytes32 oft;
uint256 amountIn;
bytes32 from;
bytes32 to;
uint64 chainIdTo;
uint32 dstEid;
bytes extraOptions;
uint256 nativeFeeLimit;
OftType oftTypeSource;
OftType oftTypeDist;
bytes32 emergencyTo;
}
/// @notice Parameters for execute oft operation.
/// @param cPos Current operation number.
/// @param operationsCode Array of operation codes.
/// @param operations Array of operation names to execute.
/// @param params Array of encoded parameters for each operation.
/// @param bridgeOptions Encoded bridge-related options for further continuation.
/// @param prevMaskedParams Previous masked parameters to inherit state.
struct ExecuteOftParams {
uint256 cPos;
bytes32[] operationsCode;
string[] operations;
bytes[] params;
bytes bridgeOptions;
ICoreFacet.MaskedParams prevMaskedParams;
}
/// @notice Parameters for emergency oft operations.
struct EmergencyOftParams {
bytes32 guid;
OftParams oftParams;
}
/// @notice Parameters for push OFT token operations in dist chain.
struct pushOftParams {
bytes32 guid;
}
/// @notice Emitted when the new value of endPoint is set.
/// @param endPoint The endPoint address.
event SetEndPoint(address endPoint);
/// @notice Emitted when a cross-chain operation completes or transfers to another chain.
/// @param guid The current ID being processed.
/// @param nextChainId Target chain ID if continuation is needed.
/// @param oftOpState The state of OFT operation.
event OFTProcessed(
bytes32 indexed guid,
uint64 indexed nextChainId,
OftOpState indexed oftOpState
);
/// @notice Handles main operational logic for send OFT tokens operation.
/// @param ep Parameters for execute oft operation
/// @return chainIdTo Id of the chain to which OFT tokens were sent.
/// @return updatedParams Id of operation in Layer Zero protocol.
/// @return maskedParams Updated masked parameters to inherit state.
/// @return result xecution result (Succeeded, Failed, or Interrupted).
function executeOftOp(
ExecuteOftParams memory ep
)
external
payable
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Processing a message sent with OFT tokens.
/// @param from OFT token address.
/// @param guid Id of operation in Layer Zero protocol.
/// @param message Message with parameters for further processing.
/// @param sender Executor address.
/// @param extraData Options for sending a message.
function lzCompose(
address from,
bytes32 guid,
bytes calldata message,
address sender,
bytes calldata extraData
) external payable;
/// @notice Sets the EndPoint contract address.
/// @param endPoint The address of the EndPoint contract.
/// @dev Can only be called by an account with DEFAULT_ADMIN_ROLE.
function setEndPoint(address endPoint) external;
/// @notice Retrun EndPoint contract address.
/// @return Address stored in `OFTFacetStorage.DS.endPoint`.
function endPoint() external view returns(address);
/// @notice struct whith hashs of the BMo operation and subsequent operations obtained in the lzCompose() function
/// @param guid The id of LZ operation.
/// @return keccak256 hash stored in `OFTFacetStorage.DS.resumedOps[guid]`.
function resumedOps(bytes32 guid) external view returns (ResumedOps memory);
/// @notice Processing state of a lzCompose request.
/// @param guid The id of LZ operation.
/// @return 0 – Unknown, 1 – Succeeded, 2 – Reverted (see `CrossChainOpState`).
function processedOftOps(bytes32 guid) external view returns (uint8);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./ICoreFacet.sol";
interface IRubicFacet {
/// @notice Parameters to start a Rubic route from the router.
/// @dev
/// - `tokens[i]` is the input asset address encoded as bytes32. `address(0)` (cast from bytes32) means native token (ETH).
/// - `amounts[i]` is the amount for `tokens[i]`. If `amounts[i] == type(uint256).max`, the facet will substitute
/// `prevMaskedParams.amountOut` for that entry.
///
/// - `from` is a bytes32-encoded address indicating whose funds to pull:
/// * If `from == 0`, the facet will fallback to `prevMaskedParams.to`
/// (i.e. use the output holder from the previous op in the pipeline).
/// * If `from != 0`, then on the origin call it MUST match `msg.sender`.
///
/// - `emergencyTo` is the emergency withdrawal recipient:
/// * If `emergencyTo == 0`, we fallback to `prevMaskedParams.emergencyTo`.
/// * If `emergencyTo != 0`, then on the origin call it MUST match `msg.sender`.
///
/// - `nativeValue` is extra ETH that must be forwarded to Rubic in addition to any ETH that appears
/// in `tokens[i] == address(0)`. The facet enforces msg.value rules so that:
/// * on origin call, `msg.value` must exactly equal the ETH we're going to forward;
/// * on resume call, `msg.value` must be 0 and the router must already hold enough ETH.
///
/// - `stageAsset` / `minStageAmount` are only enforced if the router is about to `break`
/// (i.e. `nextOp == BreakOps.BREAK_CODE`):
/// * `stageAsset` is the asset we expect to remain "staged" (left behind) on the router after Rubic finishes.
/// It is encoded as bytes32; `address(0)` means native ETH.
/// * The facet snapshots the router's balance of `stageAsset` before and after the Rubic call.
/// For ETH, the "before" snapshot is adjusted by subtracting the ETH we are about to forward,
/// so we don't count forwarded call value as staged profit.
/// * The delta is the actually staged amount. The facet requires `delta >= minStageAmount`.
/// * If that check passes, the facet will report this staged amount through `maskedParams.amountOut`
/// and will set `maskedParams.to = address(this)` so the next step (the break) knows the funds
/// are already sitting on the router.
struct RubicStartParams {
/// @notice Input asset addresses as bytes32; `0` means native token.
bytes32[] tokens;
/// @notice Amounts for each input asset; can be `type(uint256).max` to reuse `prevMaskedParams.amountOut`.
uint256[] amounts;
/// @notice Opaque calldata blob for Rubic's proxy (`startViaRubic`).
bytes facetCallData;
/// @notice Source of funds (bytes32-encoded address). `0` means "use prevMaskedParams.to".
bytes32 from;
/// @notice Extra ETH that must be sent into Rubic on top of summed native inputs.
uint256 nativeValue;
/// @notice Emergency recipient. `0` means "use prevMaskedParams.emergencyTo".
bytes32 emergencyTo;
/// @notice Asset we require to stay staged on the router if the next op is BREAK.
/// @dev bytes32-encoded token address; `0` means native ETH.
bytes32 stageAsset;
/// @notice Minimal acceptable staged amount of `stageAsset` after the Rubic call,
/// @dev enforced only when `nextOp == BreakOps.BREAK_CODE`.
uint256 minStageAmount;
}
/// @notice Scratch container for intermediate values in `executeRubicOp`.
/// @dev
/// This struct exists purely to avoid "stack too deep" in `executeRubicOp`.
/// Instead of keeping many independent local variables (which quickly exceeds the
/// stack slot limit), we group them under a single in-memory struct `LocalVars v`.
///
/// High-level meaning of the fields:
/// - We resolve who is the actual payer of ERC20 inputs (`fromAddr`), and we may pull
/// their tokens into the router.
/// - We accumulate total native input required by the Rubic route (`nativeRequired`)
/// and compute the final ETH that must be forwarded to Rubic (`valueToSend`).
/// - We snapshot router balances of the expected "staging" asset before and after
/// calling Rubic (`balBefore`, `balAfter`), then compute how much actually remained
/// on the router (`stagedDelta`). This is enforced against `minStageAmount` when
/// `nextOp == BreakOps.BREAK_CODE`.
/// - We also keep the Rubic proxy address (`proxy`), the chosen staging asset
/// (`stageAssetAddr`), and the deduplicated ERC20 count (`idx`).
/// - We carry forward the masked emergency recipient (`emergencyTo`) to propagate it
/// into `maskedParams.emergencyTo`.
struct LocalVars {
/// @notice Final resolved address that provides ERC20 inputs for this Rubic step.
/// @dev
/// Derived from `p.from` and `prevMaskedParams.to` using `checkMaskedParams`.
/// If `p.from` was zero, this will typically be the previous op's output holder.
/// Otherwise, on origin calls, it must match `msg.sender`.
address fromAddr;
/// @notice Address of the Rubic ERC20 proxy we will call (`startViaRubic`).
/// @dev
/// Loaded from facet storage (`RubicFacetStorage.ds().erc20Proxy`). Must be non-zero
/// or we revert. Used both for approvals and for the external call.
address proxy;
/// @notice Asset we are tracking as "staged" on the router after Rubic finishes.
/// @dev
/// This is `p.stageAsset` decoded to `address`. `address(0)` means native ETH.
/// We snapshot this asset's balance on the router before/after the Rubic call
/// to measure how much value actually remained here to be consumed by BREAK.
address stageAssetAddr;
/// @notice Total native amount requested by the route inputs.
/// @dev
/// This is the sum of all `p.amounts[i]` where `p.tokens[i] == address(0)`.
/// It reflects the ETH that is part of the user-supplied swap/bridge input.
uint256 nativeRequired;
/// @notice The exact ETH value that must be forwarded to the Rubic proxy.
/// @dev
/// Computed as `nativeRequired + p.nativeValue`.
/// Enforced via `_checkMsgValue`:
/// - On origin calls, `msg.value` must equal this.
/// - On resume calls, `msg.value` must be zero and the router must already
/// hold at least this much ETH.
uint256 valueToSend;
/// @notice Router balance of `stageAssetAddr` before calling Rubic.
/// @dev
/// For ETH, this is `address(this).balance`.
/// For ERC20, this is `IERC20(stageAssetAddr).balanceOf(address(this))`.
/// Captured to later compute how much actually stayed on the router.
uint256 balBefore;
/// @notice Router balance of `stageAssetAddr` after calling Rubic.
/// @dev
/// Same measurement logic as `balBefore`, but taken after `startViaRubic`
/// returns and after we reset approvals.
uint256 balAfter;
/// @notice Adjusted "before" balance used to measure staged value.
/// @dev
/// For ETH, we conceptually subtract the ETH we intentionally forwarded
/// (`valueToSend`) so we do not count that outgoing ETH as if it had
/// "remained on the router". For ERC20, this is just `balBefore`.
uint256 effectiveBefore;
/// @notice Net amount of `stageAssetAddr` that actually ended up staged on the router.
/// @dev
/// Computed as `balAfter - effectiveBefore`.
/// If `nextOp == BreakOps.BREAK_CODE`, we require `stagedDelta >= p.minStageAmount`.
/// When that holds, we propagate `stagedDelta` via `maskedParams.amountOut`
/// and mark `maskedParams.to = address(this)` so BREAK knows funds are on the router.
uint256 stagedDelta;
/// @notice Number of distinct ERC20 tokens that we aggregated for this Rubic call.
/// @dev
/// While iterating inputs we merge same-token amounts. `idx` is how many unique
/// tokens we ended up with. We then truncate the temporary `erc20Tokens` /
/// `erc20Amounts` arrays to length `idx` before calling `startViaRubic`.
uint256 idx;
/// @notice Masked emergency withdrawal recipient to forward in `maskedParams.emergencyTo`.
/// @dev
/// Comes from `checkMaskedParams`, which enforces (on origin) that if a non-zero
/// emergency address is provided it must equal `msg.sender`, or otherwise falls
/// back to `prevMaskedParams.emergencyTo`.
bytes32 emergencyTo;
}
/// @notice Executes the Rubic step in the pipeline.
/// @dev
/// Flow types:
///
/// 1. Terminal flow (no break next):
/// - `nextOp == bytes32(0)`.
/// - Facet pulls/approves inputs, forwards assets into Rubic, calls Rubic proxy,
/// and then clears approvals.
/// - The pipeline stops here.
/// - Return convention:
/// * `chainIdTo = 0`
/// * `updatedParams = ""`
/// * `maskedParams.amountOut = 0`
/// * `maskedParams.to = 0`
/// * `maskedParams.emergencyTo = propagated emergencyTo`
/// * `result = ICoreFacet.ExecutionResult.Succeeded`
///
/// 2. Pre-break flow:
/// - `nextOp == BreakOps.BREAK_CODE`.
/// - Facet does the same Rubic interaction (pull assets, approve, forward ETH, call proxy, reset approvals),
/// but additionally:
/// * It snapshots the router's balance of `p.stageAsset` before and after the call.
/// For ETH, it subtracts the ETH we are about to forward so we don't "double count" the call value.
/// * It computes the staged delta = (adjusted afterBalance - adjusted beforeBalance).
/// * Requires `stagedDelta >= p.minStageAmount`, otherwise revert.
/// * It returns:
/// - `maskedParams.amountOut = stagedDelta`
/// - `maskedParams.to = address(this)` (router holds the staged funds)
/// - `maskedParams.emergencyTo = propagated emergencyTo`
/// - `chainIdTo = 0`
/// - `updatedParams = ""`
/// - `result = ICoreFacet.ExecutionResult.Succeeded`
///
/// Common rules for both flows:
/// - ERC20 inputs: we aggregate all requested ERC20 amounts per token, optionally pull them
/// from `from` (or `prevMaskedParams.to` if `from == 0`), approve the Rubic proxy for those amounts,
/// and later reset approvals to zero.
/// - Native inputs: we sum all native amounts (tokens[i] == address(0)) plus `nativeValue`.
/// - `msg.value` policy:
/// * On origin call (`CoreFacetStorage.currentRequestId() == 0`), `msg.value` MUST equal the ETH
/// we are about to forward.
/// * On resume call, `msg.value` MUST be 0, and the router's ETH balance MUST be large enough to fund the call.
///
/// @param isOpHalfDone Reserved flag propagated by the router. Not used by Rubic logic.
/// @param op Operation code; must be the Rubic start code (`RUBIC_START_CODE`).
/// @param nextOp Next operation code. Must be either:
/// - `bytes32(0)` for terminal flow, or
/// - `BreakOps.BREAK_CODE` if we're staging funds for a BREAK next.
/// @param params ABI-encoded `RubicStartParams` specifying inputs, routing data, staging expectations, etc.
/// @param prevMaskedParams Masked params from the previous pipeline step. Supplies default `from`,
/// `emergencyTo`, and `amountOut` substitutions.
/// @return chainIdTo Always 0. RubicFacet itself does not directly schedule a cross-chain hop.
/// @return updatedParams Always empty (`""`). RubicFacet does not mutate downstream op params.
/// @return maskedParams
/// Terminal flow (`nextOp == 0`):
/// - amountOut = 0
/// - to = 0
/// - emergencyTo = propagated
/// Pre-break flow (`nextOp == BreakOps.BREAK_CODE`):
/// - amountOut = stagedDelta (measured staged amount)
/// - to = address(this) (router now holds the staged funds)
/// - emergencyTo = propagated
/// @return result Always `ICoreFacet.ExecutionResult.Succeeded` if we didn't revert.
function executeRubicOp(
bool isOpHalfDone,
bytes32 op,
bytes32 nextOp,
bytes calldata params,
ICoreFacet.MaskedParams calldata prevMaskedParams
)
external
payable
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
/// @notice Sets the Rubic ERC20 proxy address for the current chain.
/// @dev
/// - Restricted to `OPERATOR_ROLE` enforced in the facet implementation via `onlyRole`.
/// - Must not be the zero address.
/// @param erc20Proxy Address of Rubic's ERC20 proxy contract on this chain.
function setRubicProxy(address erc20Proxy) external;
/// @notice Returns the Rubic ERC20 proxy address configured for this chain.
/// @return The address of the Rubic ERC20 proxy contract currently stored in facet storage.
function rubicProxy() external view returns (address);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./ICoreFacet.sol";
interface IRunnerFacet {
/// @notice Immutable configuration parameters embedded into a UniversalRunner clone.
/// @dev These values are encoded as immutable args in the clone bytecode (no storage used).
/// They define who can pull funds, on which chain, until when, and under which logical request.
/// @param router Router (diamond) address that is allowed to call collect() on the runner.
/// @param factory UniversalRunnerFactory that deployed this runner via CREATE2.
/// @param beneficiary Recipient of emergencyWithdraw() after deadline expiry.
/// @param token Asset expected on the runner:
/// - non-zero: ERC20 token to be collected,
/// - zero: native asset (ETH) to be collected.
/// @param deadline Last valid timestamp (inclusive) when collect() is allowed to succeed.
/// @param chainId Chain id on which this runner is valid; must equal block.chainid.
/// @param requestId Logical request identifier associated with this runner instance.
struct RunnerImmutableArgs {
address router;
address factory;
address beneficiary;
address token;
uint64 deadline;
uint64 chainId;
bytes32 requestId;
}
/// @notice Parameters for the RUNNER_COLLECT pipeline operation.
/// @dev Encoded into `params` when calling executeRunnerCollectOp via the router.
/// @param runnerArgs Immutable configuration that will be embedded into the UniversalRunner clone.
/// @param salt CREATE2 salt used to deterministically derive the runner address.
/// @param amountIn Desired amount to pull from the runner (or type(uint256).max when using masking).
/// @param emergencyTo Emergency recipient for the overall pipeline; propagated into masked params.
struct RunnerCollectParams {
RunnerImmutableArgs runnerArgs;
bytes32 salt;
uint256 amountIn;
bytes32 emergencyTo;
}
/// @notice Scratch space for executeRunnerCollectOp to avoid "stack too deep" errors.
/// @dev
/// Groups together intermediate values used during RUNNER_COLLECT execution so they
/// do not each occupy a separate stack slot. This struct is not stored on-chain and
/// is only used inside RunnerFacet.executeRunnerCollectOp.
/// @param amountIn Resolved amount to collect from the runner after applying masking rules.
/// @param emergencyTo Resolved emergency recipient encoded as bytes32 (address-packed).
/// @param factory UniversalRunnerFactory address loaded from RunnerFacetStorage.
/// @param runner Deployed UniversalRunner clone address used for the collect() call.
/// @param collected Actual amount returned by runner.collect(), compared against amountIn.
/// @param emergencyToAddr Emergency recipient decoded as an address, used to validate beneficiary.
struct LocalVars {
uint256 amountIn;
bytes32 emergencyTo;
address factory;
address runner;
uint256 collected;
address emergencyToAddr;
}
/// @notice Thrown when the caller lacks the required role to perform an admin-only action.
/// @dev Used by {setUniversalRunnerFactory}.
error RunnerFacetMissingRole();
/// @notice Thrown when a zero factory address is provided to an admin setter.
/// @dev Protects configuration from being set to address(0).
error RunnerFacetFactoryZero();
/// @notice Thrown when the universal runner factory is not configured.
/// @dev Raised in executeRunnerCollectOp when no factory address is set in storage.
error RunnerFacetFactoryNotSet();
/// @notice Thrown when the factory in params does not match the configured factory.
/// @dev Ensures that the runner is deployed only through the trusted UniversalRunnerFactory.
error RunnerFacetFactoryMismatch();
/// @notice Thrown when executeRunnerCollectOp is called with an unsupported op code.
/// @dev `op` must equal RUNNER_COLLECT_CODE for this facet.
error RunnerFacetWrongOp();
/// @notice Thrown when the router in RunnerImmutableArgs does not match the current diamond.
/// @dev Protects against misconfigured or spoofed runner args.
error RunnerFacetWrongRouter();
/// @notice Thrown when the runner's configured chainId does not match the current block.chainid.
/// @dev Prevents re-use of runner configs across chains.
error RunnerFacetChainIdMismatch();
/// @notice Thrown when the runner deadline has already passed at the time of execution.
/// @dev Used to prevent collect() from being used after expiration.
error RunnerFacetDeadlineExpired();
/// @notice Thrown when the resolved amount to collect is zero.
/// @dev Applies both when amountIn is zero directly or after masking resolution.
error RunnerFacetAmountZero();
/// @notice Thrown when the runner returns less funds than requested.
/// @dev Indicates that the actual collected amount is strictly less than `amountIn`.
error RunnerFacetInsufficientCollected();
/// @notice Thrown when executeRunnerCollectOp is called in a "half-done" (resumed) mode.
/// @dev RUNNER_COLLECT is a one-step operation and does not support isOpHalfDone == true.
error RunnerFacetHalfDoneNotSupported();
/// @notice Thrown when the resolved emergency recipient address is zero.
/// @dev Ensures that RUNNER_COLLECT always has a non-zero emergencyTo in masked params.
error RunnerFacetEmergencyToZero();
/// @notice Thrown when the runner's configured beneficiary does not match the resolved emergencyTo.
/// @dev Binds UniversalRunner.beneficiary to the pipeline initiator by enforcing beneficiary == emergencyTo.
error RunnerFacetWrongBeneficiary();
/// @notice Sets the UniversalRunnerFactory used to deploy runner clones.
/// @dev Admin-gated. Reverts if `factory` is the zero address.
/// @param factory The UniversalRunnerFactory contract address.
function setUniversalRunnerFactory(address factory) external;
/// @notice Executes the RUNNER_COLLECT operation as part of the router pipeline.
/// @dev
/// High-level behavior:
/// - Validates that `op` equals RUNNER_COLLECT_CODE and that `isOpHalfDone` is false.
/// - Requires `msg.value == 0` (no native value is accepted at this stage).
/// - Decodes RunnerCollectParams from `params`.
/// - Ensures:
/// * factory is configured and matches `runnerArgs.factory`,
/// * `runnerArgs.router == address(this)` (the current diamond),
/// * `runnerArgs.chainId == block.chainid`,
/// * `runnerArgs.deadline >= block.timestamp`.
/// - Resolves `amountIn` and `emergencyTo` using `prevMaskedParams` and masking rules.
/// - Deploys a UniversalRunner clone via factory if it does not exist yet.
/// - Calls `collect(runnerArgs.token, address(this), amountIn)` on the runner.
/// - Requires that the collected amount is at least `amountIn`.
/// - Returns updated masked params with:
/// * `amountOut = collected`,
/// * `to = address(this)`,
/// * `emergencyTo` propagated from the resolved value.
///
/// This operation does not schedule a cross-chain hop and does not mutate params for next ops.
///
/// @param isOpHalfDone Indicates whether the router is resuming a half-executed op; must be false.
/// @param op Operation code for this step; must equal RUNNER_COLLECT_CODE.
/// @param nextOp Operation code for the next step in the pipeline; ignored by this facet.
/// @param params ABI-encoded RunnerCollectParams structure.
/// @param prevMaskedParams Masked params from the previous step in the pipeline.
/// @return chainIdTo Always 0, since RUNNER_COLLECT does not initiate a cross-chain operation.
/// @return updatedParams Always empty bytes, as this op does not mutate subsequent params.
/// @return maskedParams Updated masked params with the collected amount and router as `to`.
/// @return result Execution status; always ExecutionResult.Succeeded on success, otherwise reverts.
function executeRunnerCollectOp(
bool isOpHalfDone,
bytes32 op,
bytes32 nextOp,
bytes calldata params,
ICoreFacet.MaskedParams calldata prevMaskedParams
)
external
payable
returns (
uint64 chainIdTo,
bytes memory updatedParams,
ICoreFacet.MaskedParams memory maskedParams,
ICoreFacet.ExecutionResult result
);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import "@openzeppelin/contracts/utils/Counters.sol";
/// @notice Structure with information about cross-chain transaction delivery.
/// @param hashSynthParams hash from the parameters of a successfully delivered cross-chain transaction.
/// @param crossChainOpState cross-chain transaction delivery status.
struct ProcessedOps {
bytes32 hashSynthParams;
uint8 crossChainOpState;
}
library CoreFacetStorage {
/// @notice Fixed storage slot for the CoreFacet data structure.
bytes32 private constant POSITION = keccak256("crosscurve.core.facet.storage");
/// @notice Slot for the currentRequestId.
/// @dev keccak256("crosscurve.core.facet.transient.currentRequestId");
bytes32 private constant CURRENT_REQUEST_ID_POS = 0x4c0fc00f7060fb51f491b90bbb038205c36b6fbc6a8aed52d27b18d2967b53f4;
/// @notice Slot for the currentChainIdFrom.
/// @dev keccak256("crosscurve.core.facet.transient.currentChainIdFrom");
bytes32 private constant CURRENT_CHAIN_ID_FROM_POS = 0x67b6f0fb6bca8398a49a7966c252819542f4d36560c9c6961937957fdf20f9f4;
/// @notice Slot for the isOriginNetwork.
/// @dev keccak256("crosscurve.core.facet.transient.isOriginNetwork");
bytes32 private constant IS_ORIGIN_NETWORK_POS = 0xb13bd13498ce9409b85a4287d16ff0fcdaca732822a47a97d2bdada3325aa451;
/// @notice Slot for the isDiamondCall.
/// @dev keccak256("crosscurve.core.facet.transient.isDiamondCall");
bytes32 private constant IS_DIAMOND_CALL = 0x2e445143a211ecbb689de5812742d02a96369c482aec76832fc56490cf3cc6f2;
/// @notice Slot for the isReentrancyLocked.
/// @dev keccak256("crosscurve.core.facet.transient.isReentrancyLocked");
bytes32 private constant IS_REENTRANCY_LOCKED = 0x1e0578bb15c7ba996e367f5cb2606a2c0e49f47e3ec9ce25487e41761d562498;
/// @notice Slot for msgValueLeft budget (origin start only).
/// @dev keccak256("crosscurve.core.facet.transient.msgValueLeft");
bytes32 private constant MSG_VALUE_LEFT_POS = 0x7f3e5ed8ce4a8806c618eaa07afc27516c5c690793b80b8262a28aa8675561fc;
/// @notice Thrown when an op tries to spend more ETH than was provided in msg.value for the whole start().
error MsgValueBudgetExceeded(uint256 requested, uint256 left);
/// @notice Layout for all persistent CoreFacet state.
/// @dev Every variable needed by CoreFacet logic is grouped here and kept
/// at the single slot defined in `POSITION`.
struct DS {
mapping(address => Counters.Counter) nonces;
mapping(bytes32 => bytes32) startedOps;
mapping(bytes32 => ProcessedOps) processedOps;
address addressBook;
address treasury;
address WETH;
}
/// @notice Accessor to the CoreFacet storage struct.
/// @dev Assembly ties the returned reference to the predefined slot so the
/// compiler understands where the struct actually lives in storage.
/// @return s Pointer to the `DS` struct residing at `POSITION`.
function ds() internal pure returns (DS storage s) {
bytes32 pos = POSITION;
assembly {
s.slot := pos
}
}
function setCurrentRequestId(bytes32 currentRequestId_) internal {
assembly ("memory-safe") {
tstore(CURRENT_REQUEST_ID_POS, currentRequestId_)
}
}
function currentRequestId() internal view returns (bytes32 currentRequestId_) {
assembly ("memory-safe") {
currentRequestId_ := tload(CURRENT_REQUEST_ID_POS)
}
}
function setCurrentChainIdFrom(uint64 currentChainIdFrom_) internal {
assembly ("memory-safe") {
tstore(CURRENT_CHAIN_ID_FROM_POS, currentChainIdFrom_)
}
}
function currentChainIdFrom() internal view returns (uint64 currentChainIdFrom_) {
assembly ("memory-safe") {
currentChainIdFrom_ := tload(CURRENT_CHAIN_ID_FROM_POS)
}
}
function setOriginNetwork(bool isOriginNetwork_) internal {
assembly ("memory-safe") {
tstore(IS_ORIGIN_NETWORK_POS, isOriginNetwork_)
}
}
function isOriginNetwork() internal view returns (bool isOriginNetwork_) {
assembly ("memory-safe") {
isOriginNetwork_ := tload(IS_ORIGIN_NETWORK_POS)
}
}
function setDiamondCall(bool isDiamondCall_) internal {
assembly ("memory-safe") {
tstore(IS_DIAMOND_CALL, isDiamondCall_)
}
}
function isDiamondCall() internal view returns (bool isDiamondCall_) {
assembly ("memory-safe") {
isDiamondCall_ := tload(IS_DIAMOND_CALL)
}
}
function setReentrancyLock(bool isLocked_) internal {
assembly ("memory-safe") {
tstore(IS_REENTRANCY_LOCKED, isLocked_)
}
}
function isReentrancyLocked() internal view returns (bool isLocked_) {
assembly ("memory-safe") {
isLocked_ := tload(IS_REENTRANCY_LOCKED)
}
}
function setMsgValueLeft(uint256 msgValueLeft_) internal {
assembly ("memory-safe") {
tstore(MSG_VALUE_LEFT_POS, msgValueLeft_)
}
}
function msgValueLeft() internal view returns (uint256 msgValueLeft_) {
assembly ("memory-safe") {
msgValueLeft_ := tload(MSG_VALUE_LEFT_POS)
}
}
function consumeMsgValue(uint256 amount) internal {
uint256 left;
assembly ("memory-safe") {
left := tload(MSG_VALUE_LEFT_POS)
}
if (amount > left) {
revert MsgValueBudgetExceeded(amount, left);
}
unchecked {
left -= amount;
}
assembly ("memory-safe") {
tstore(MSG_VALUE_LEFT_POS, left)
}
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library CurveFacetStorage {
/// @notice Fixed storage slot for the CurveFacet data structure.
bytes32 private constant POSITION = keccak256("crosscurve.curve.facet.storage");
/// @notice Layout for all persistent CurveFacet state.
/// @dev Every variable needed by CurveFacet logic is grouped here and kept
/// at the single slot defined in `POSITION`.
struct DS {
mapping (address => address) poolAdapter;
}
/// @notice Accessor to the CurveFacet storage struct.
/// @dev Assembly ties the returned reference to the predefined slot so the
/// compiler understands where the struct actually lives in storage.
/// @return s Pointer to the `DS` struct residing at `POSITION`.
function ds() internal pure returns (DS storage s) {
bytes32 pos = POSITION;
assembly {
s.slot := pos
}
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
/// @param bmoOpHash Hash from the parameters of the BMo operation, which was reverted
/// @param opsHash Hash from the sequence of operation parameters that was reverted
struct ResumedOps{
bytes32 bmoOpHash;
bytes32 opsHash;
}
library OFTFacetStorage {
/// @notice Fixed storage slot for the CoreFacet data structure.
bytes32 private constant POSITION = keccak256("crosscurve.oft.facet.storage");
/// @notice Slot for the lzComposeIsProcessed.
/// @dev keccak256("crosscurve.oft.facet.transient.lzComposeIsProcessed");
bytes32 private constant LZ_COMPOSE_IS_PROCESSED = 0x30273a0c1aadada7e550a5e2c5e4d1944f67a058b948828ccf2622923a9aae4b;
/// @notice Layout for all persistent CoreFacet state.
/// @dev Every variable needed by CoreFacet logic is grouped here and kept
/// at the single slot defined in `POSITION`.
struct DS {
mapping(bytes32 => ResumedOps) resumedOps;
mapping(bytes32 => uint8) processedOps;
mapping (bytes32 => bool) guids;
address endPoint;
}
/// @notice Accessor to the CoreFacet storage struct.
/// @dev Assembly ties the returned reference to the predefined slot so the
/// compiler understands where the struct actually lives in storage.
/// @return s Pointer to the `DS` struct residing at `POSITION`.
function ds() internal pure returns (DS storage s) {
bytes32 pos = POSITION;
assembly {
s.slot := pos
}
}
function setLzComposeIsProcessed(bool lzComposeIsProcessed_) internal {
assembly ("memory-safe") {
tstore(LZ_COMPOSE_IS_PROCESSED, lzComposeIsProcessed_)
}
}
function lzComposeIsProcessed() internal view returns (bool lzComposeIsProcessed_) {
assembly ("memory-safe") {
lzComposeIsProcessed_ := tload(LZ_COMPOSE_IS_PROCESSED)
}
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library BreakOps {
/// @notice Operation code for starting a break ("br").
/// @dev The router must map this code to `executeBreakOp`, which:
/// - records break metadata and pipeline tail,
/// - stashes the produced funds,
/// - returns `ExecutionResult.Interrupted`.
bytes32 public constant BREAK_CODE = keccak256(abi.encodePacked("br"));
/// @notice Operation code for cancelling a break ("!br").
/// @dev The router must map this code to `executeCancelBreakOp`, which:
/// - requires the break to still be pending,
/// - transfers escrowed funds to `emergencyTo`,
/// - marks the break as cancelled.
bytes32 public constant CANCEL_BREAK_CODE = keccak256(abi.encodePacked("!br"));
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library BungeeOps {
/// @notice Operation code for the Bungee "manual" start (off-chain built tx).
bytes32 public constant BUNGEE_MANUAL_START_CODE = keccak256(abi.encodePacked("BMAN"));
/// @notice Operation code for the Bungee "auto" start (on-chain tx data).
bytes32 public constant BUNGEE_AUTO_START_CODE = keccak256(abi.encodePacked("BAUTO"));
/// @notice Operation code for backend-triggered refunds from router balance.
bytes32 public constant BUNGEE_REFUND_CODE = keccak256(abi.encodePacked("BRFD"));
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library CrosschainOps {
/// @notice Operation code for lock and mint operations.
bytes32 public constant LOCK_MINT_CODE = keccak256(abi.encodePacked("LM"));
/// @notice Operation code for burn and unlock operations.
bytes32 public constant BURN_UNLOCK_CODE = keccak256(abi.encodePacked("BU"));
/// @notice Operation code for burn and mint operations.
bytes32 public constant BURN_MINT_CODE = keccak256(abi.encodePacked("BM"));
/// @notice Operation code for emergency unlock (cancel lock) operations.
bytes32 public constant EMERGENCY_UNLOCK_CODE = keccak256(abi.encodePacked("!M"));
/// @notice Operation code for emergency mint (cancel burn) operations.
bytes32 public constant EMERGENCY_MINT_CODE = keccak256(abi.encodePacked("!U"));
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library CurveAMMOps {
/// @notice Operation code for adding liquidity or assets.
bytes32 public constant ADD_CODE = keccak256(abi.encodePacked("A"));
/// @notice Operation code for removing liquidity or assets.
bytes32 public constant REMOVE_CODE = keccak256(abi.encodePacked("R"));
/// @notice Operation code for token or asset swap.
bytes32 public constant SWAP_CODE = keccak256(abi.encodePacked("S"));
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library OftOps {
/// @notice Operation code for OFT burn and mint operations.
bytes32 public constant BURN_MINT_OFT_CODE = keccak256(abi.encodePacked("BMo"));
/// @notice Operation code for emergency OFT mint operations.
bytes32 public constant EMERGENCY_MINT_OFT_CODE = keccak256(abi.encodePacked("!Mo"));
/// @notice Operation code for push OFT operations in dist chain.
bytes32 public constant PUSH_MINT_OFT_CODE = keccak256(abi.encodePacked("PMo"));
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library RubicOps {
/// @notice Operation code for Rubic start (terminal step).
bytes32 public constant RUBIC_START_CODE = keccak256(abi.encodePacked("RS"));
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library RunnerOps {
/// @notice Operation code for the "collect from UniversalRunner" operation.
/// @dev Routed to {IRunnerFacet.executeRunnerCollectOp} when present
/// as the first operation in the pipeline.
bytes32 public constant RUNNER_COLLECT_CODE = keccak256("RUNNER_COLLECT");
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
library UtilsOps {
/// @notice Operation code for `permit` functionality.
bytes32 public constant PERMIT_CODE = keccak256(abi.encodePacked("P"));
/// @notice Operation code for token wrapping.
bytes32 public constant WRAP_CODE = keccak256(abi.encodePacked("W"));
/// @notice Operation code for token unwrapping.
bytes32 public constant UNWRAP_CODE = keccak256(abi.encodePacked("Uw"));
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity 0.8.25;
import {ICoreFacet} from "./interfaces/ICoreFacet.sol";
import {ICrosschainFacet} from "./interfaces/ICrosschainFacet.sol";
import {ICurveFacet} from "./interfaces/ICurveFacet.sol";
import {IOFTFacet} from "./interfaces/IOFTFacet.sol";
import {IRubicFacet} from "./interfaces/IRubicFacet.sol";
import {IBungeeFacet} from "./interfaces/IBungeeFacet.sol";
import {IBreakFacet} from "./interfaces/IBreakFacet.sol";
import {IRunnerFacet} from "./interfaces/IRunnerFacet.sol";
import {IAddressBook} from "../interfaces/IAddressBook.sol";
import {CoreFacetStorage} from "./libraries/CoreFacetStorage.sol";
import {CrosschainOps} from "./libraries/ops/CrosschainOps.sol";
import {CurveAMMOps} from "./libraries/ops/CurveAMMOps.sol";
import {OftOps} from "./libraries/ops/OftOps.sol";
import {RubicOps} from "./libraries/ops/RubicOps.sol";
import {BungeeOps} from "./libraries/ops/BungeeOps.sol";
import {BreakOps} from "./libraries/ops/BreakOps.sol";
import {RunnerOps} from "./libraries/ops/RunnerOps.sol";
import {UtilsOps} from "./libraries/ops/UtilsOps.sol";
import {TypecastLib} from "../utils/TypecastLib.sol";
abstract contract OpsCrosschain {
/// @notice Operation code for lock and mint operations.
bytes32 public constant LOCK_MINT_CODE = CrosschainOps.LOCK_MINT_CODE;
/// @notice Operation code for burn and unlock operations.
bytes32 public constant BURN_UNLOCK_CODE = CrosschainOps.BURN_UNLOCK_CODE;
/// @notice Operation code for burn and mint operations.
bytes32 public constant BURN_MINT_CODE = CrosschainOps.BURN_MINT_CODE;
/// @notice Operation code for emergency unlock (cancel lock) operations.
bytes32 public constant EMERGENCY_UNLOCK_CODE = CrosschainOps.EMERGENCY_UNLOCK_CODE;
/// @notice Operation code for emergency mint (cancel burn) operations.
bytes32 public constant EMERGENCY_MINT_CODE = CrosschainOps.EMERGENCY_MINT_CODE;
/// @notice Operation code for `permit` functionality.
bytes32 public constant PERMIT_CODE = UtilsOps.PERMIT_CODE;
/// @notice Operation code for token wrapping.
bytes32 public constant WRAP_CODE = UtilsOps.WRAP_CODE;
/// @notice Operation code for token unwrapping.
bytes32 public constant UNWRAP_CODE = UtilsOps.UNWRAP_CODE;
}
abstract contract OpsCurveAMM {
/// @notice Operation code for adding liquidity or assets.
bytes32 public constant ADD_CODE = CurveAMMOps.ADD_CODE;
/// @notice Operation code for removing liquidity or assets.
bytes32 public constant REMOVE_CODE = CurveAMMOps.REMOVE_CODE;
/// @notice Operation code for token or asset swap.
bytes32 public constant SWAP_CODE = CurveAMMOps.SWAP_CODE;
}
abstract contract OpsOFT {
/// @notice Operation code for OFT burn and mint operations.
bytes32 public constant BURN_MINT_OFT_CODE = OftOps.BURN_MINT_OFT_CODE;
/// @notice Operation code for emergency OFT mint operations.
bytes32 public constant EMERGENCY_MINT_OFT_CODE = OftOps.EMERGENCY_MINT_OFT_CODE;
/// @notice Operation code for push OFT operations in dist chain.
bytes32 public constant PUSH_MINT_OFT_CODE = OftOps.PUSH_MINT_OFT_CODE;
}
abstract contract OpsRubic {
bytes32 public constant RUBIC_START_CODE = RubicOps.RUBIC_START_CODE;
}
abstract contract OpsBungee {
bytes32 public constant BUNGEE_MANUAL_START_CODE = BungeeOps.BUNGEE_MANUAL_START_CODE;
bytes32 public constant BUNGEE_AUTO_START_CODE = BungeeOps.BUNGEE_AUTO_START_CODE;
bytes32 public constant BUNGEE_REFUND_CODE = BungeeOps.BUNGEE_REFUND_CODE;
}
abstract contract Ops {
using CoreFacetStorage for CoreFacetStorage.DS;
/// @notice The modifier checks that the current operation is being on CoreFacet.
modifier onlyDiamond() {
require(CoreFacetStorage.isDiamondCall(), "Router: isn't Diamond call");
require(!CoreFacetStorage.isReentrancyLocked(), "Router: reentrancy");
CoreFacetStorage.setReentrancyLock(true);
_;
CoreFacetStorage.setReentrancyLock(false);
}
function getOpSelector(bytes32 op) internal pure returns (bytes4 selector) {
if (
op == UtilsOps.PERMIT_CODE
|| op == UtilsOps.WRAP_CODE
|| op == UtilsOps.UNWRAP_CODE
) {
return ICrosschainFacet.executeCrosschainOp.selector;
} else if (
op == CrosschainOps.LOCK_MINT_CODE
|| op == CrosschainOps.BURN_UNLOCK_CODE
|| op == CrosschainOps.BURN_MINT_CODE
|| op == CrosschainOps.EMERGENCY_UNLOCK_CODE
|| op == CrosschainOps.EMERGENCY_MINT_CODE
) {
return ICrosschainFacet.executeCrosschainOp.selector;
} else if (
op == CurveAMMOps.ADD_CODE
|| op == CurveAMMOps.REMOVE_CODE
|| op == CurveAMMOps.SWAP_CODE
) {
return ICurveFacet.executeCurveAMMOp.selector;
} else if (
op == OftOps.BURN_MINT_OFT_CODE
|| op == OftOps.EMERGENCY_MINT_OFT_CODE
|| op == OftOps.PUSH_MINT_OFT_CODE
) {
return IOFTFacet.executeOftOp.selector;
} else if (op == RubicOps.RUBIC_START_CODE) {
return IRubicFacet.executeRubicOp.selector;
} else if (op == BungeeOps.BUNGEE_MANUAL_START_CODE) {
return IBungeeFacet.executeBungeeManualOp.selector;
} else if (op == BungeeOps.BUNGEE_AUTO_START_CODE) {
return IBungeeFacet.executeBungeeAutoOp.selector;
} else if (op == BungeeOps.BUNGEE_REFUND_CODE) {
return IBungeeFacet.executeBungeeRefundOp.selector;
} else if (op == BreakOps.BREAK_CODE) {
return IBreakFacet.executeBreakOp.selector;
} else if (op == BreakOps.CANCEL_BREAK_CODE) {
return IBreakFacet.executeCancelBreakOp.selector;
} else if (op == RunnerOps.RUNNER_COLLECT_CODE) {
return IRunnerFacet.executeRunnerCollectOp.selector;
}
revert("Ops: op is not supported");
}
/// @notice Returns the final values for amountIn, from, and emergencyTo based on masking logic.
/// @param currentAmountIn The current input amount, can be type(uint256).max for masking.
/// @param currentFrom The current sender address (as bytes32), may be 0.
/// @param currentEmergencyTo The current emergency address (as bytes32), may be 0.
/// @param prevMaskedParams Previously masked values used to fill in missing data.
/// @return amountIn Final resolved input amount.
/// @return from Final resolved sender address.
/// @return emergencyTo Final resolved emergency address.
function checkMaskedParams(
uint256 currentAmountIn,
bytes32 currentFrom,
bytes32 currentEmergencyTo,
ICoreFacet.MaskedParams memory prevMaskedParams
) internal view returns (uint256 amountIn, bytes32 from, bytes32 emergencyTo) {
amountIn = currentAmountIn == type(uint256).max ? prevMaskedParams.amountOut : currentAmountIn;
if (currentFrom != 0) {
require(TypecastLib.castToAddress(currentFrom) == msg.sender, "Router: wrong sender");
from = currentFrom;
} else {
from = prevMaskedParams.to;
}
if (CoreFacetStorage.currentRequestId() == 0 && currentEmergencyTo != 0) {
require(TypecastLib.castToAddress(currentEmergencyTo) == msg.sender, "Router: wrong emergencyTo");
emergencyTo = currentEmergencyTo;
} else {
emergencyTo = prevMaskedParams.emergencyTo;
}
}
/// @notice Validates and resolves the correct `to` address for an operation sequence.
/// @dev Calls `coreOpCheckTo` on the current contract (as ICrosschainFacet), and if it returns zero,
/// falls back to calling `poolOpCheckTo` on the current contract (as ICurveFacet).
/// @param to The initially proposed recipient address (encoded as bytes32).
/// @param emergencyTo The emergency fallback address (encoded as bytes32).
/// @param chainId The chain ID for which the address resolution is taking place.
/// @param currentOp The identifier of the current operation (hashed name).
/// @param nextOp The identifier of the next operation in sequence (hashed name).
/// @return correctTo The final resolved recipient address (encoded as bytes32). If invalid, may revert.
function checkTo(bytes32 to, bytes32 emergencyTo, uint64 chainId, bytes32 currentOp, bytes32 nextOp) internal view returns (bytes32 correctTo) {
CoreFacetStorage.DS storage ds = CoreFacetStorage.ds();
require(
nextOp == 0 && to != 0 || nextOp != 0 && to == 0,
"Router: wrong to"
);
if (CoreFacetStorage.currentRequestId() == 0 && currentOp != UtilsOps.WRAP_CODE && currentOp != UtilsOps.UNWRAP_CODE) {
require(TypecastLib.castToAddress(emergencyTo) == msg.sender, "Router: emergencyTo is not equal the sender");
}
if (nextOp == bytes32(0)) {
correctTo = to;
} else if (nextOp == CrosschainOps.LOCK_MINT_CODE) {
correctTo = IAddressBook(ds.addressBook).portalV3(chainId);
} else if (nextOp == CrosschainOps.BURN_UNLOCK_CODE || nextOp == CrosschainOps.BURN_MINT_CODE) {
correctTo = IAddressBook(ds.addressBook).synthesisV3(chainId);
} else if (UtilsOps.WRAP_CODE == nextOp || UtilsOps.UNWRAP_CODE == nextOp) {
correctTo = IAddressBook(ds.addressBook).routerV3(chainId);
} else if (nextOp == CurveAMMOps.ADD_CODE || nextOp == CurveAMMOps.REMOVE_CODE || nextOp == CurveAMMOps.SWAP_CODE) {
correctTo = IAddressBook(ds.addressBook).routerV3(chainId);
} else if (nextOp == OftOps.BURN_MINT_OFT_CODE || nextOp == OftOps.EMERGENCY_MINT_OFT_CODE || nextOp == OftOps.PUSH_MINT_OFT_CODE) {
correctTo = IAddressBook(ds.addressBook).routerV3(chainId);
} else if (nextOp == RubicOps.RUBIC_START_CODE) {
correctTo = IAddressBook(ds.addressBook).routerV3(chainId);
} else if (nextOp == BreakOps.BREAK_CODE) {
correctTo = IAddressBook(ds.addressBook).routerV3(chainId);
} else if (
nextOp == BungeeOps.BUNGEE_MANUAL_START_CODE ||
nextOp == BungeeOps.BUNGEE_AUTO_START_CODE
) {
correctTo = IAddressBook(ds.addressBook).routerV3(chainId);
} else if (nextOp == RunnerOps.RUNNER_COLLECT_CODE) {
correctTo = IAddressBook(ds.addressBook).routerV3(chainId);
}
}
/// @notice Validates a sequence of operation codes starting from a given position.
/// @param cPos Starting index in the operation sequence.
/// @param operationsCode Array of operation codes to validate.
/// @return true if the sequence is valid, false otherwise.
function checkOperations(uint256 cPos, bytes32[] memory operationsCode) internal view returns (bool) {
for (uint256 i = cPos; i < operationsCode.length - 1; i++) {
bytes32 operationCode = operationsCode[i];
if (operationCode == BreakOps.CANCEL_BREAK_CODE) {
if (operationsCode.length != 2) {
return false;
}
if (i != 0) {
return false;
}
if (i != operationsCode.length - 2) {
return false;
}
if (CoreFacetStorage.currentRequestId() != 0) {
return false;
}
continue;
}
if (
CoreFacetStorage.currentRequestId() != 0 &&
i == cPos &&
!(
operationCode == CrosschainOps.LOCK_MINT_CODE ||
operationCode == CrosschainOps.BURN_UNLOCK_CODE ||
operationCode == CrosschainOps.BURN_MINT_CODE ||
operationCode == CrosschainOps.EMERGENCY_UNLOCK_CODE ||
operationCode == CrosschainOps.EMERGENCY_MINT_CODE ||
operationCode == OftOps.BURN_MINT_OFT_CODE
)
) {
return false;
}
if (
(
operationCode == UtilsOps.PERMIT_CODE ||
operationCode == UtilsOps.WRAP_CODE ||
operationCode == OftOps.EMERGENCY_MINT_OFT_CODE ||
operationCode == OftOps.PUSH_MINT_OFT_CODE
) && i != 0
) {
return false;
}
if (operationCode == UtilsOps.UNWRAP_CODE && i != operationsCode.length - 2) {
return false;
}
if (
operationCode == CrosschainOps.LOCK_MINT_CODE &&
operationsCode[i + 1] == CrosschainOps.LOCK_MINT_CODE
) {
return false;
}
if (
operationCode == CrosschainOps.BURN_UNLOCK_CODE &&
operationsCode[i + 1] == CrosschainOps.BURN_UNLOCK_CODE
) {
return false;
}
if (
operationCode == CrosschainOps.BURN_MINT_CODE &&
operationsCode[i + 1] == CrosschainOps.BURN_MINT_CODE
) {
return false;
}
if (
(
operationCode == CrosschainOps.EMERGENCY_UNLOCK_CODE ||
operationCode == CrosschainOps.EMERGENCY_MINT_CODE ||
operationCode == OftOps.EMERGENCY_MINT_OFT_CODE
) &&
operationsCode.length > 2
) {
return false;
}
if (operationCode == BreakOps.BREAK_CODE) {
if (i == 0) {
return false;
}
if (i != operationsCode.length - 2) {
return false;
}
if (CoreFacetStorage.currentRequestId() != 0) {
return false;
}
bytes32 prev = operationsCode[i - 1];
if (
prev != RubicOps.RUBIC_START_CODE &&
prev != BungeeOps.BUNGEE_MANUAL_START_CODE &&
prev != BungeeOps.BUNGEE_AUTO_START_CODE &&
prev != RunnerOps.RUNNER_COLLECT_CODE
) {
return false;
}
continue;
}
if (operationCode == RubicOps.RUBIC_START_CODE) {
bool isTerminal = (i == operationsCode.length - 2);
bool followedByBr = (operationsCode[i + 1] == BreakOps.BREAK_CODE);
if (!isTerminal && !followedByBr) {
return false;
}
continue;
}
if (
operationCode == BungeeOps.BUNGEE_MANUAL_START_CODE ||
operationCode == BungeeOps.BUNGEE_AUTO_START_CODE
) {
bool isTerminal = (i == operationsCode.length - 2);
bool followedByBr = (operationsCode[i + 1] == BreakOps.BREAK_CODE);
if (!isTerminal && !followedByBr) {
return false;
}
continue;
}
if (operationCode == BungeeOps.BUNGEE_REFUND_CODE) {
if (i != 0) {
return false;
}
if (i != operationsCode.length - 2) {
return false;
}
if (CoreFacetStorage.currentRequestId() != 0) {
return false;
}
if (!CoreFacetStorage.isOriginNetwork()) {
return false;
}
continue;
}
if (operationCode == RunnerOps.RUNNER_COLLECT_CODE) {
if (i != 0) {
return false;
}
if (CoreFacetStorage.currentRequestId() != 0) {
return false;
}
if (!CoreFacetStorage.isOriginNetwork()) {
return false;
}
continue;
}
}
return true;
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) Eywa.Fi, 2021-2025 - all rights reserved
pragma solidity ^0.8.17;
library TypecastLib {
function castToAddress(bytes32 x) internal pure returns (address) {
return address(uint160(uint256(x)));
}
function castToBytes32(address a) internal pure returns (bytes32) {
return bytes32(uint256(uint160(a)));
}
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "cancun",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"address","name":"adapter","type":"address"}],"name":"PoolAdapterSet","type":"event"},{"inputs":[],"name":"ADD_CODE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REMOVE_CODE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SWAP_CODE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bytes32","name":"op","type":"bytes32"},{"internalType":"bytes32","name":"nextOp","type":"bytes32"},{"internalType":"bytes","name":"params","type":"bytes"},{"components":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"bytes32","name":"emergencyTo","type":"bytes32"}],"internalType":"struct ICoreFacet.MaskedParams","name":"prevMaskedParams","type":"tuple"}],"name":"executeCurveAMMOp","outputs":[{"internalType":"uint64","name":"chainIdTo","type":"uint64"},{"internalType":"bytes","name":"updatedParams","type":"bytes"},{"components":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"bytes32","name":"emergencyTo","type":"bytes32"}],"internalType":"struct ICoreFacet.MaskedParams","name":"maskedParams","type":"tuple"},{"internalType":"enum ICoreFacet.ExecutionResult","name":"result","type":"uint8"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"}],"name":"poolAdapter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool_","type":"address"},{"internalType":"address","name":"poolAdapter_","type":"address"}],"name":"setPoolAdapter","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
6080604052348015600e575f80fd5b506119cc8061001c5f395ff3fe60806040526004361061006e575f3560e01c8063bae633611161004c578063bae633611461011b578063bcf4f0a61461013e578063beadbe321461015f578063f5b541a614610173575f80fd5b80632d07ae691461007257806395ad709a146100995780639ba520ad14610107575b5f80fd5b34801561007d575f80fd5b506100866101a6565b6040519081526020015b60405180910390f35b3480156100a4575f80fd5b506100ef6100b3366004611576565b6001600160a01b039081165f9081527ff7ba6083e63c71062a85498a64e063b755730ac3a92d494147a56ecca6d95b4260205260409020541690565b6040516001600160a01b039091168152602001610090565b348015610112575f80fd5b506100866101d0565b61012e61012936600461166c565b6101e5565b604051610090949392919061175e565b348015610149575f80fd5b5061015d6101583660046117c7565b610710565b005b34801561016a575f80fd5b506100866108c4565b34801561017e575f80fd5b506100867f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b92981565b604051602960f91b60208201526021015b6040516020818303038152906040528051906020012081565b604051604160f81b60208201526021016101b7565b60408051606081810183525f80835260208301819052928201839052905f7f2e445143a211ecbb689de5812742d02a96369c482aec76832fc56490cf3cc6f25c6102765760405162461bcd60e51b815260206004820152601a60248201527f526f757465723a2069736e2774204469616d6f6e642063616c6c00000000000060448201526064015b60405180910390fd5b7f1e0578bb15c7ba996e367f5cb2606a2c0e49f47e3ec9ce25487e41761d5624985c156102da5760405162461bcd60e51b8152602060048201526012602482015271526f757465723a207265656e7472616e637960701b604482015260640161026d565b6102e460016108d9565b604051604160f81b602082015288906021016040516020818303038152906040528051906020012003610488575f86806020019051810190610326919061189c565b90505f61033c610337836080015190565b6108ff565b9050610356826020015183604001518460e001518a61098a565b60e0850181905260408501919091526020840191909152606083015161037e91468d8d610aa8565b6060830152815161039a905b60408401518385602001516111b3565b81516001600160a01b0382169063dc64ef45905b60208501516060860151608087015160a088015160c089015160e0808b01516040519189901b6001600160e01b03191682526001600160a01b039788166004830152602482019690965293861660448501529185166064840152608483015260ff1660a4820152911660c482015260e4016020604051808303815f875af115801561043b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061045f91906118b7565b8085526060830151602086015260e0830151604086015261047f906111e5565b925050506106e7565b604051602960f91b602082015288906021016040516020818303038152906040528051906020012003610545575f868060200190518101906104ca919061189c565b90505f6104db610337836080015190565b90506104f5826020015183604001518460e001518a61098a565b60e0850181905260408501919091526020840191909152606083015161051d91468d8d610aa8565b6060830152815161052d9061038a565b81516001600160a01b0382169063cd7bfd58906103ae565b604051605360f81b6020820152889060210160405160208183030381529060405280519060200120036106e4575f8680602001905181019061058791906118ce565b90505f610598610337836080015190565b90506105b3826020015183604001518461010001518a61098a565b61010085018190526040850191909152602084019190915260608301516105dc91468d8d610aa8565b606083015281516105ec9061038a565b81516001600160a01b03821690636469c44f9060208501516060860151608087015160a088015160c089015160e08a01516101008b015160405160e08a901b6001600160e01b03191681526001600160a01b039889166004820152602481019790975294871660448701529286166064860152608485019190915260ff90811660a48501521660c483015290911660e4820152610104016020604051808303815f875af115801561069f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106c391906118b7565b80855260608301516020860152610100830151604086015261047f906111e5565b505f5b5f935060405180602001604052805f81525092506107045f6108d9565b95509550955095915050565b604051632474521560e21b81527f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929600482018190523360248301529030906391d1485490604401602060405180830381865afa158015610772573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107969190611953565b6107e25760405162461bcd60e51b815260206004820152601860248201527f437572766546616365743a206d697373696e6720726f6c650000000000000000604482015260640161026d565b6001600160a01b0383166108385760405162461bcd60e51b815260206004820152601860248201527f437572766546616365743a207a65726f20616464726573730000000000000000604482015260640161026d565b6001600160a01b038381165f8181527ff7ba6083e63c71062a85498a64e063b755730ac3a92d494147a56ecca6d95b42602090815260409182902080546001600160a01b0319169487169485179055815192835282019290925281517fec3d3c5fd0e9bfa5a7de49c176f7699b00e2fe558782f174a9d510c72218d69a929181900390910190a1505050565b604051605360f81b60208201526021016101b7565b807f1e0578bb15c7ba996e367f5cb2606a2c0e49f47e3ec9ce25487e41761d5624985d50565b6001600160a01b038181165f9081527ff7ba6083e63c71062a85498a64e063b755730ac3a92d494147a56ecca6d95b42602052604090205416806109855760405162461bcd60e51b815260206004820181905260248201527f437572766546616365743a20706f6f6c2061646170746572206e6f7420736574604482015260640161026d565b919050565b5f805f5f19871461099b578661099e565b83515b925085156109fd5733866001600160a01b0316146109f55760405162461bcd60e51b81526020600482015260146024820152732937baba32b91d103bb937b7339039b2b73232b960611b604482015260640161026d565b859150610a05565b836020015191505b7f4c0fc00f7060fb51f491b90bbb038205c36b6fbc6a8aed52d27b18d2967b53f45c158015610a3357508415155b15610a975733856001600160a01b031614610a905760405162461bcd60e51b815260206004820152601960248201527f526f757465723a2077726f6e6720656d657267656e6379546f00000000000000604482015260640161026d565b5083610a9e565b5060408301515b9450945094915050565b5f7f1985103ae1175f43d643c2baccb8e4caab00e02289861e71bc8dd662b8d8101f82158015610ad757508615155b80610aea57508215801590610aea575086155b610b295760405162461bcd60e51b815260206004820152601060248201526f526f757465723a2077726f6e6720746f60801b604482015260640161026d565b7f4c0fc00f7060fb51f491b90bbb038205c36b6fbc6a8aed52d27b18d2967b53f45c158015610b7d5750604051605760f81b6020820152602101604051602081830303815290604052805190602001208414155b8015610baf575060405161557760f01b6020820152602201604051602081830303815290604052805190602001208414155b15610c205733866001600160a01b031614610c205760405162461bcd60e51b815260206004820152602b60248201527f526f757465723a20656d657267656e6379546f206973206e6f7420657175616c60448201526a103a34329039b2b73232b960a91b606482015260840161026d565b82610c2d578691506111a9565b604051614c4d60f01b6020820152602201604051602081830303815290604052805190602001208303610cd757600381015460405163535cfbb760e11b81526001600160401b03871660048201526001600160a01b039091169063a6b9f76e906024015b602060405180830381865afa158015610cac573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cd091906118b7565b91506111a9565b60405161425560f01b602082015260220160405160208183030381529060405280519060200120831480610d30575060405161424d60f01b60208201526022016040516020818303038152906040528051906020012083145b15610d705760038101546040516374a748ff60e11b81526001600160401b03871660048201526001600160a01b039091169063e94e91fe90602401610c91565b604051605760f81b60208201528390602101604051602081830303815290604052805190602001201480610dca575060405161557760f01b6020820152839060220160405160208183030381529060405280519060200120145b15610e0a5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b604051604160f81b602082015260210160405160208183030381529060405280519060200120831480610e615750604051602960f91b60208201526021016040516020818303038152906040528051906020012083145b80610e905750604051605360f81b60208201526021016040516020818303038152906040528051906020012083145b15610ed05760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b60405162424d6f60e81b602082015260230160405160208183030381529060405280519060200120831480610f2b575060405162214d6f60e81b60208201526023016040516020818303038152906040528051906020012083145b80610f5c575060405162504d6f60e81b60208201526023016040516020818303038152906040528051906020012083145b15610f9c5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b60405161525360f01b60208201526022016040516020818303038152906040528051906020012083036110045760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b60405161313960f11b602082015260220160405160208183030381529060405280519060200120830361106c5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b604051632126a0a760e11b6020820152602401604051602081830303815290604052805190602001208314806110ca575060405164424155544f60d81b60208201526025016040516020818303038152906040528051906020012083145b1561110a5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b7f33c4137323016cc703f06d36ae2c7f43f1a70fa030334bd76934bba665c80d6a83036111a95760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401602060405180830381865afa158015611182573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111a691906118b7565b91505b5095945050505050565b306001600160a01b038416036111d3576111ce848383611263565b6111df565b6111df848484846112cb565b50505050565b5f815f0361125b577fb13bd13498ce9409b85a4287d16ff0fcdaca732822a47a97d2bdada3325aa4515c156112535760405162461bcd60e51b8152602060048201526014602482015273437572766546616365743a20736c69707061676560601b604482015260640161026d565b506002919050565b506001919050565b6040516001600160a01b0383166024820152604481018290526112c690849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611303565b505050565b6040516001600160a01b03808516602483015283166044820152606481018290526111df9085906323b872dd60e01b9060840161128f565b5f611357826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166113d69092919063ffffffff16565b905080515f14806113775750808060200190518101906113779190611953565b6112c65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161026d565b60606113e484845f856113ec565b949350505050565b60608247101561144d5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161026d565b5f80866001600160a01b03168587604051611468919061196e565b5f6040518083038185875af1925050503d805f81146114a2576040519150601f19603f3d011682016040523d82523d5f602084013e6114a7565b606091505b50915091506114b8878383876114c3565b979650505050505050565b606083156115315782515f0361152a576001600160a01b0385163b61152a5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161026d565b50816113e4565b6113e483838151156115465781518083602001fd5b8060405162461bcd60e51b815260040161026d9190611984565b80356001600160a01b0381168114610985575f80fd5b5f60208284031215611586575f80fd5b61158f82611560565b9392505050565b80151581146115a3575f80fd5b50565b634e487b7160e01b5f52604160045260245ffd5b60405161012081016001600160401b03811182821017156115dd576115dd6115a6565b60405290565b604051601f8201601f191681016001600160401b038111828210171561160b5761160b6115a6565b604052919050565b5f60608284031215611623575f80fd5b604051606081018181106001600160401b0382111715611645576116456115a6565b80604052508091508235815260208301356020820152604083013560408201525092915050565b5f805f805f60e08688031215611680575f80fd5b853561168b81611596565b945060208681013594506040870135935060608701356001600160401b03808211156116b5575f80fd5b818901915089601f8301126116c8575f80fd5b8135818111156116da576116da6115a6565b6116ec601f8201601f191685016115e3565b91508082528a84828501011115611701575f80fd5b80848401858401375f848284010152508094505050506117248760808801611613565b90509295509295909350565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160401b038516815260c060208201525f61177f60c0830186611730565b9050835160408301526020840151606083015260408401516080830152600383106117b857634e487b7160e01b5f52602160045260245ffd5b8260a083015295945050505050565b5f80604083850312156117d8575f80fd5b6117e183611560565b91506117ef60208401611560565b90509250929050565b805160ff81168114610985575f80fd5b5f61010080838503121561181a575f80fd5b604051908101906001600160401b038211818310171561183c5761183c6115a6565b81604052809250835181526020840151602082015260408401516040820152606084015160608201526080840151608082015260a084015160a082015261188560c085016117f8565b60c082015260e084015160e0820152505092915050565b5f61010082840312156118ad575f80fd5b61158f8383611808565b5f602082840312156118c7575f80fd5b5051919050565b5f61012082840312156118df575f80fd5b6118e76115ba565b825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a082015261192960c084016117f8565b60c082015261193a60e084016117f8565b60e0820152610100928301519281019290925250919050565b5f60208284031215611963575f80fd5b815161158f81611596565b5f82518060208501845e5f920191825250919050565b602081525f61158f602083018461173056fea2646970667358221220aa8464f9eeaffe565b89c00354f372312f40dc52786d7dc92939bd00e447a83b64736f6c63430008190033
Deployed Bytecode
0x60806040526004361061006e575f3560e01c8063bae633611161004c578063bae633611461011b578063bcf4f0a61461013e578063beadbe321461015f578063f5b541a614610173575f80fd5b80632d07ae691461007257806395ad709a146100995780639ba520ad14610107575b5f80fd5b34801561007d575f80fd5b506100866101a6565b6040519081526020015b60405180910390f35b3480156100a4575f80fd5b506100ef6100b3366004611576565b6001600160a01b039081165f9081527ff7ba6083e63c71062a85498a64e063b755730ac3a92d494147a56ecca6d95b4260205260409020541690565b6040516001600160a01b039091168152602001610090565b348015610112575f80fd5b506100866101d0565b61012e61012936600461166c565b6101e5565b604051610090949392919061175e565b348015610149575f80fd5b5061015d6101583660046117c7565b610710565b005b34801561016a575f80fd5b506100866108c4565b34801561017e575f80fd5b506100867f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b92981565b604051602960f91b60208201526021015b6040516020818303038152906040528051906020012081565b604051604160f81b60208201526021016101b7565b60408051606081810183525f80835260208301819052928201839052905f7f2e445143a211ecbb689de5812742d02a96369c482aec76832fc56490cf3cc6f25c6102765760405162461bcd60e51b815260206004820152601a60248201527f526f757465723a2069736e2774204469616d6f6e642063616c6c00000000000060448201526064015b60405180910390fd5b7f1e0578bb15c7ba996e367f5cb2606a2c0e49f47e3ec9ce25487e41761d5624985c156102da5760405162461bcd60e51b8152602060048201526012602482015271526f757465723a207265656e7472616e637960701b604482015260640161026d565b6102e460016108d9565b604051604160f81b602082015288906021016040516020818303038152906040528051906020012003610488575f86806020019051810190610326919061189c565b90505f61033c610337836080015190565b6108ff565b9050610356826020015183604001518460e001518a61098a565b60e0850181905260408501919091526020840191909152606083015161037e91468d8d610aa8565b6060830152815161039a905b60408401518385602001516111b3565b81516001600160a01b0382169063dc64ef45905b60208501516060860151608087015160a088015160c089015160e0808b01516040519189901b6001600160e01b03191682526001600160a01b039788166004830152602482019690965293861660448501529185166064840152608483015260ff1660a4820152911660c482015260e4016020604051808303815f875af115801561043b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061045f91906118b7565b8085526060830151602086015260e0830151604086015261047f906111e5565b925050506106e7565b604051602960f91b602082015288906021016040516020818303038152906040528051906020012003610545575f868060200190518101906104ca919061189c565b90505f6104db610337836080015190565b90506104f5826020015183604001518460e001518a61098a565b60e0850181905260408501919091526020840191909152606083015161051d91468d8d610aa8565b6060830152815161052d9061038a565b81516001600160a01b0382169063cd7bfd58906103ae565b604051605360f81b6020820152889060210160405160208183030381529060405280519060200120036106e4575f8680602001905181019061058791906118ce565b90505f610598610337836080015190565b90506105b3826020015183604001518461010001518a61098a565b61010085018190526040850191909152602084019190915260608301516105dc91468d8d610aa8565b606083015281516105ec9061038a565b81516001600160a01b03821690636469c44f9060208501516060860151608087015160a088015160c089015160e08a01516101008b015160405160e08a901b6001600160e01b03191681526001600160a01b039889166004820152602481019790975294871660448701529286166064860152608485019190915260ff90811660a48501521660c483015290911660e4820152610104016020604051808303815f875af115801561069f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106c391906118b7565b80855260608301516020860152610100830151604086015261047f906111e5565b505f5b5f935060405180602001604052805f81525092506107045f6108d9565b95509550955095915050565b604051632474521560e21b81527f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929600482018190523360248301529030906391d1485490604401602060405180830381865afa158015610772573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107969190611953565b6107e25760405162461bcd60e51b815260206004820152601860248201527f437572766546616365743a206d697373696e6720726f6c650000000000000000604482015260640161026d565b6001600160a01b0383166108385760405162461bcd60e51b815260206004820152601860248201527f437572766546616365743a207a65726f20616464726573730000000000000000604482015260640161026d565b6001600160a01b038381165f8181527ff7ba6083e63c71062a85498a64e063b755730ac3a92d494147a56ecca6d95b42602090815260409182902080546001600160a01b0319169487169485179055815192835282019290925281517fec3d3c5fd0e9bfa5a7de49c176f7699b00e2fe558782f174a9d510c72218d69a929181900390910190a1505050565b604051605360f81b60208201526021016101b7565b807f1e0578bb15c7ba996e367f5cb2606a2c0e49f47e3ec9ce25487e41761d5624985d50565b6001600160a01b038181165f9081527ff7ba6083e63c71062a85498a64e063b755730ac3a92d494147a56ecca6d95b42602052604090205416806109855760405162461bcd60e51b815260206004820181905260248201527f437572766546616365743a20706f6f6c2061646170746572206e6f7420736574604482015260640161026d565b919050565b5f805f5f19871461099b578661099e565b83515b925085156109fd5733866001600160a01b0316146109f55760405162461bcd60e51b81526020600482015260146024820152732937baba32b91d103bb937b7339039b2b73232b960611b604482015260640161026d565b859150610a05565b836020015191505b7f4c0fc00f7060fb51f491b90bbb038205c36b6fbc6a8aed52d27b18d2967b53f45c158015610a3357508415155b15610a975733856001600160a01b031614610a905760405162461bcd60e51b815260206004820152601960248201527f526f757465723a2077726f6e6720656d657267656e6379546f00000000000000604482015260640161026d565b5083610a9e565b5060408301515b9450945094915050565b5f7f1985103ae1175f43d643c2baccb8e4caab00e02289861e71bc8dd662b8d8101f82158015610ad757508615155b80610aea57508215801590610aea575086155b610b295760405162461bcd60e51b815260206004820152601060248201526f526f757465723a2077726f6e6720746f60801b604482015260640161026d565b7f4c0fc00f7060fb51f491b90bbb038205c36b6fbc6a8aed52d27b18d2967b53f45c158015610b7d5750604051605760f81b6020820152602101604051602081830303815290604052805190602001208414155b8015610baf575060405161557760f01b6020820152602201604051602081830303815290604052805190602001208414155b15610c205733866001600160a01b031614610c205760405162461bcd60e51b815260206004820152602b60248201527f526f757465723a20656d657267656e6379546f206973206e6f7420657175616c60448201526a103a34329039b2b73232b960a91b606482015260840161026d565b82610c2d578691506111a9565b604051614c4d60f01b6020820152602201604051602081830303815290604052805190602001208303610cd757600381015460405163535cfbb760e11b81526001600160401b03871660048201526001600160a01b039091169063a6b9f76e906024015b602060405180830381865afa158015610cac573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cd091906118b7565b91506111a9565b60405161425560f01b602082015260220160405160208183030381529060405280519060200120831480610d30575060405161424d60f01b60208201526022016040516020818303038152906040528051906020012083145b15610d705760038101546040516374a748ff60e11b81526001600160401b03871660048201526001600160a01b039091169063e94e91fe90602401610c91565b604051605760f81b60208201528390602101604051602081830303815290604052805190602001201480610dca575060405161557760f01b6020820152839060220160405160208183030381529060405280519060200120145b15610e0a5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b604051604160f81b602082015260210160405160208183030381529060405280519060200120831480610e615750604051602960f91b60208201526021016040516020818303038152906040528051906020012083145b80610e905750604051605360f81b60208201526021016040516020818303038152906040528051906020012083145b15610ed05760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b60405162424d6f60e81b602082015260230160405160208183030381529060405280519060200120831480610f2b575060405162214d6f60e81b60208201526023016040516020818303038152906040528051906020012083145b80610f5c575060405162504d6f60e81b60208201526023016040516020818303038152906040528051906020012083145b15610f9c5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b60405161525360f01b60208201526022016040516020818303038152906040528051906020012083036110045760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b60405161313960f11b602082015260220160405160208183030381529060405280519060200120830361106c5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b604051632126a0a760e11b6020820152602401604051602081830303815290604052805190602001208314806110ca575060405164424155544f60d81b60208201526025016040516020818303038152906040528051906020012083145b1561110a5760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401610c91565b7f33c4137323016cc703f06d36ae2c7f43f1a70fa030334bd76934bba665c80d6a83036111a95760038101546040516397c2cd9160e01b81526001600160401b03871660048201526001600160a01b03909116906397c2cd9190602401602060405180830381865afa158015611182573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111a691906118b7565b91505b5095945050505050565b306001600160a01b038416036111d3576111ce848383611263565b6111df565b6111df848484846112cb565b50505050565b5f815f0361125b577fb13bd13498ce9409b85a4287d16ff0fcdaca732822a47a97d2bdada3325aa4515c156112535760405162461bcd60e51b8152602060048201526014602482015273437572766546616365743a20736c69707061676560601b604482015260640161026d565b506002919050565b506001919050565b6040516001600160a01b0383166024820152604481018290526112c690849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611303565b505050565b6040516001600160a01b03808516602483015283166044820152606481018290526111df9085906323b872dd60e01b9060840161128f565b5f611357826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166113d69092919063ffffffff16565b905080515f14806113775750808060200190518101906113779190611953565b6112c65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161026d565b60606113e484845f856113ec565b949350505050565b60608247101561144d5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840161026d565b5f80866001600160a01b03168587604051611468919061196e565b5f6040518083038185875af1925050503d805f81146114a2576040519150601f19603f3d011682016040523d82523d5f602084013e6114a7565b606091505b50915091506114b8878383876114c3565b979650505050505050565b606083156115315782515f0361152a576001600160a01b0385163b61152a5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161026d565b50816113e4565b6113e483838151156115465781518083602001fd5b8060405162461bcd60e51b815260040161026d9190611984565b80356001600160a01b0381168114610985575f80fd5b5f60208284031215611586575f80fd5b61158f82611560565b9392505050565b80151581146115a3575f80fd5b50565b634e487b7160e01b5f52604160045260245ffd5b60405161012081016001600160401b03811182821017156115dd576115dd6115a6565b60405290565b604051601f8201601f191681016001600160401b038111828210171561160b5761160b6115a6565b604052919050565b5f60608284031215611623575f80fd5b604051606081018181106001600160401b0382111715611645576116456115a6565b80604052508091508235815260208301356020820152604083013560408201525092915050565b5f805f805f60e08688031215611680575f80fd5b853561168b81611596565b945060208681013594506040870135935060608701356001600160401b03808211156116b5575f80fd5b818901915089601f8301126116c8575f80fd5b8135818111156116da576116da6115a6565b6116ec601f8201601f191685016115e3565b91508082528a84828501011115611701575f80fd5b80848401858401375f848284010152508094505050506117248760808801611613565b90509295509295909350565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160401b038516815260c060208201525f61177f60c0830186611730565b9050835160408301526020840151606083015260408401516080830152600383106117b857634e487b7160e01b5f52602160045260245ffd5b8260a083015295945050505050565b5f80604083850312156117d8575f80fd5b6117e183611560565b91506117ef60208401611560565b90509250929050565b805160ff81168114610985575f80fd5b5f61010080838503121561181a575f80fd5b604051908101906001600160401b038211818310171561183c5761183c6115a6565b81604052809250835181526020840151602082015260408401516040820152606084015160608201526080840151608082015260a084015160a082015261188560c085016117f8565b60c082015260e084015160e0820152505092915050565b5f61010082840312156118ad575f80fd5b61158f8383611808565b5f602082840312156118c7575f80fd5b5051919050565b5f61012082840312156118df575f80fd5b6118e76115ba565b825181526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a082015261192960c084016117f8565b60c082015261193a60e084016117f8565b60e0820152610100928301519281019290925250919050565b5f60208284031215611963575f80fd5b815161158f81611596565b5f82518060208501845e5f920191825250919050565b602081525f61158f602083018461173056fea2646970667358221220aa8464f9eeaffe565b89c00354f372312f40dc52786d7dc92939bd00e447a83b64736f6c63430008190033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in FRAX
0
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.