Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/token/nomina/NominaBridgeL1.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { INomina } from "src/interfaces/nomina/INomina.sol";
import { NominaBridgeCommon } from "./NominaBridgeCommon.sol";
import { IOmniPortal } from "src/interfaces/IOmniPortal.sol";
import { XTypes } from "src/libraries/XTypes.sol";
import { ConfLevel } from "src/libraries/ConfLevel.sol";
import { Predeploys } from "src/libraries/nomina/Predeploys.sol";
import { NominaBridgeNative } from "./NominaBridgeNative.sol";
/**
* @title NominaBridgeL1
* @notice The Ethereum side of Nomina's native token bridge. Partner to NominaBridgeNative, which is
* deployed to Nomina's EVM.
*/
contract NominaBridgeL1 is NominaBridgeCommon {
/**
* @notice Emitted when an account deposits NOM, bridging it to Ethereum.
*/
event Bridge(address indexed payor, address indexed to, uint256 amount);
/**
* @notice Emitted when NOM tokens are withdrawn for an account.
*/
event Withdraw(address indexed to, uint256 amount);
/**
* @notice xcall gas limit for NominaBridgeNative.withdraw
*/
uint64 public constant XCALL_WITHDRAW_GAS_LIMIT = 80_000;
/**
* @notice The OMNI token contract.
*/
IERC20 public immutable OMNI;
/**
* @notice The NOM token contract.
*/
IERC20 public immutable NOMINA;
/**
* @notice The OmniPortal contract.
*/
IOmniPortal public portal;
constructor(address omni, address nomina) {
OMNI = IERC20(omni);
NOMINA = IERC20(nomina);
_disableInitializers();
}
function initialize(address owner_, address portal_) external initializer {
require(portal_ != address(0), "NominaBridge: no zero addr");
__Ownable_init(owner_);
portal = IOmniPortal(portal_);
}
function initializeV2() external reinitializer(2) {
OMNI.approve(address(NOMINA), type(uint256).max);
INomina(address(NOMINA)).convert(address(this), OMNI.balanceOf(address(this)));
}
/**
* @notice Withdraw `amount` L1 NOM to `to`. Only callable via xcall from NominaBridgeNative.
* @dev Nomina native <> L1 bridge accounting rules ensure that this contract will always
* have enough balance to cover the withdrawal.
*/
function withdraw(address to, uint256 amount) external whenNotPaused(ACTION_WITHDRAW) {
XTypes.MsgContext memory xmsg = portal.xmsg();
require(msg.sender == address(portal), "NominaBridge: not xcall");
require(xmsg.sender == Predeploys.NominaBridgeNative, "NominaBridge: not bridge");
require(xmsg.sourceChainId == portal.omniChainId(), "NominaBridge: not omni portal");
NOMINA.transfer(to, amount);
emit Withdraw(to, amount);
}
/**
* @notice Bridge `amount` NOM to `to` on Nomina's EVM.
*/
function bridge(address to, uint256 amount) external payable whenNotPaused(ACTION_BRIDGE) {
_bridge(msg.sender, to, amount);
}
/**
* @dev Trigger a withdraw of `amount` NOM to `to` on Nomina's EVM, via xcall.
*/
function _bridge(address payor, address to, uint256 amount) internal {
require(amount > 0, "NominaBridge: amount must be > 0");
require(to != address(0), "NominaBridge: no bridge to zero");
uint64 omniChainId = portal.omniChainId();
bytes memory xcalldata = abi.encodeCall(NominaBridgeNative.withdraw, (payor, to, amount));
require(
msg.value >= portal.feeFor(omniChainId, xcalldata, XCALL_WITHDRAW_GAS_LIMIT),
"NominaBridge: insufficient fee"
);
require(NOMINA.transferFrom(payor, address(this), amount), "NominaBridge: transfer failed");
portal.xcall{ value: msg.value }(
omniChainId, ConfLevel.Finalized, Predeploys.NominaBridgeNative, xcalldata, XCALL_WITHDRAW_GAS_LIMIT
);
emit Bridge(payor, to, amount);
}
/**
* @notice Return the xcall fee required to bridge `amount` to `to`.
*/
function bridgeFee(address payor, address to, uint256 amount) public view returns (uint256) {
return portal.feeFor(
portal.omniChainId(),
abi.encodeCall(NominaBridgeNative.withdraw, (payor, to, amount)),
XCALL_WITHDRAW_GAS_LIMIT
);
}
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"src/interfaces/nomina/INomina.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
interface INomina {
function convert(address to, uint256 amount) external;
}
"
},
"src/token/nomina/NominaBridgeCommon.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.24;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { PausableUpgradeable } from "src/utils/PausableUpgradeable.sol";
/**
* @title NominaBridgeCommon
* @notice Common constants and functions for NominaBridge contracts
*/
abstract contract NominaBridgeCommon is OwnableUpgradeable, PausableUpgradeable {
/// @notice Pausable key for withdraws
bytes32 public constant ACTION_WITHDRAW = keccak256("withdraw");
/// @notice Pausable key for bridges
bytes32 public constant ACTION_BRIDGE = keccak256("bridge");
/// @notice Revert if `action` is paused
modifier whenNotPaused(bytes32 action) {
require(!_isPaused(action), "NominaBridge: paused");
_;
}
/// @notice Pause `action`
function pause(bytes32 action) external onlyOwner {
_pause(action);
}
/// @notice Unpause `action`
function unpause(bytes32 action) external onlyOwner {
_unpause(action);
}
/// @notice Pause all actions
function pause() external onlyOwner {
_pauseAll();
}
/// @notice Unpause all actions
function unpause() external onlyOwner {
_unpauseAll();
}
/// @notice Returns true if `action` is paused
function isPaused(bytes32 action) external view returns (bool) {
return _isPaused(action);
}
}
"
},
"src/interfaces/IOmniPortal.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
import { XTypes } from "../libraries/XTypes.sol";
/**
* @title IOmniPortal
* @notice The OmniPortal is the on-chain interface to Omni's cross-chain
* messaging protocol. It is used to initiate and execute cross-chain calls.
*/
interface IOmniPortal {
/**
* @notice Emitted when an xcall is made to a contract on another chain
* @param destChainId Destination chain ID
* @param offset Offset this XMsg in the source -> dest XStream
* @param sender msg.sender of the source xcall
* @param to Address of the contract to call on the destination chain
* @param data Encoded function calldata
* @param gasLimit Gas limit for execution on destination chain
* @param fees Fees paid for the xcall
*/
event XMsg(
uint64 indexed destChainId,
uint64 indexed shardId,
uint64 indexed offset,
address sender,
address to,
bytes data,
uint64 gasLimit,
uint256 fees
);
/**
* @notice Emitted when an XMsg is executed on its destination chain
* @param sourceChainId Source chain ID
* @param shardId Shard ID of the XStream (last byte is the confirmation level)
* @param offset Offset the XMsg in the source -> dest XStream
* @param gasUsed Gas used in execution of the XMsg
* @param relayer Address of the relayer who submitted the XMsg
* @param success Whether the execution succeeded
* @param err Result of XMsg execution, if success == false. Limited to
* xreceiptMaxErrorBytes(). Empty if success == true.
*/
event XReceipt(
uint64 indexed sourceChainId,
uint64 indexed shardId,
uint64 indexed offset,
uint256 gasUsed,
address relayer,
bool success,
bytes err
);
/**
* @notice Maximum allowed xmsg gas limit
*/
function xmsgMaxGasLimit() external view returns (uint64);
/**
* @notice Minimum allowed xmsg gas limit
*/
function xmsgMinGasLimit() external view returns (uint64);
/**
* @notice Maximum number of bytes allowed in xmsg data
*/
function xmsgMaxDataSize() external view returns (uint16);
/**
* @notice Maximum number of bytes allowed in xreceipt result
*/
function xreceiptMaxErrorSize() external view returns (uint16);
/**
* @notice Returns the fee oracle address
*/
function feeOracle() external view returns (address);
/**
* @notice Returns the chain ID of the chain to which this portal is deployed
*/
function chainId() external view returns (uint64);
/**
* @notice Returns the chain ID of Omni's EVM execution chain
*/
function omniChainId() external view returns (uint64);
/**
* @notice Returns the offset of the last outbound XMsg sent to destChainId in shardId
*/
function outXMsgOffset(uint64 destChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the offset of the last inbound XMsg received from srcChainId in shardId
*/
function inXMsgOffset(uint64 srcChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the offset of the last inbound XBlock received from srcChainId in shardId
*/
function inXBlockOffset(uint64 srcChainId, uint64 shardId) external view returns (uint64);
/**
* @notice Returns the current XMsg being executed via this portal.
* - xmsg().sourceChainId Chain ID of the source xcall
* - xmsg().sender msg.sender of the source xcall
* If no XMsg is being executed, all fields will be zero.
* - xmsg().sourceChainId == 0
* - xmsg().sender == address(0)
*/
function xmsg() external view returns (XTypes.MsgContext memory);
/**
* @notice Returns true the current transaction is an xcall, false otherwise
*/
function isXCall() external view returns (bool);
/**
* @notice Returns the shard ID is supported by this portal
*/
function isSupportedShard(uint64 shardId) external view returns (bool);
/**
* @notice Returns the destination chain ID is supported by this portal
*/
function isSupportedDest(uint64 destChainId) external view returns (bool);
/**
* @notice Calculate the fee for calling a contract on another chain
* Fees denominated in wei.
* @param destChainId Destination chain ID
* @param data Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function feeFor(uint64 destChainId, bytes calldata data, uint64 gasLimit) external view returns (uint256);
/**
* @notice Call a contract on another chain.
* @param destChainId Destination chain ID
* @param conf Confirmation level;
* @param to Address of contract to call on destination chain
* @param data ABI Encoded function calldata
* @param gasLimit Execution gas limit, enforced on destination chain
*/
function xcall(uint64 destChainId, uint8 conf, address to, bytes calldata data, uint64 gasLimit) external payable;
/**
* @notice Submit a batch of XMsgs to be executed on this chain
* @param xsub An xchain submission, including an attestation root w/ validator signatures,
* and a block header and message batch, proven against the attestation root.
*/
function xsubmit(XTypes.Submission calldata xsub) external;
/**
* @notice Returns the current network (supported chain IDs and shards)
*/
function network() external view returns (XTypes.Chain[] memory);
}
"
},
"src/libraries/XTypes.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title XTypes
* @dev Defines xchain types, core to Omni's xchain messaging protocol. These
* types mirror those defined in omni/lib/xchain/types.go.
*/
library XTypes {
/**
* @notice A cross chain message - the product of an xcall. This matches the XMsg type used
* throughout Omni's cross-chain messaging protocol. Msg is used to construct and verify
* XSubmission merkle trees / proofs.
* @custom:field destChainId Chain ID of the destination chain
* @custom:field shardId Shard ID of the XStream (last byte is the confirmation level)
* @custom:field offset Monotonically incremented offset of Msg in source -> dest Stream
* @custom:field sender msg.sender of xcall on source chain
* @custom:field to Target address to call on destination chain
* @custom:field data Data to provide to call on destination chain
* @custom:field gasLimit Gas limit to use for call execution on destination chain
*/
struct Msg {
uint64 destChainId;
uint64 shardId;
uint64 offset;
address sender;
address to;
bytes data;
uint64 gasLimit;
}
/**
* @notice Msg context exposed during its execution to consuming xapps.
* @custom:field sourceChainId Chain ID of the source chain
* @custom:field sender msg.sender of xcall on source chain
*/
struct MsgContext {
uint64 sourceChainId;
address sender;
}
/**
* @notice BlockHeader of an XBlock.
* @custom:field sourceChainId Chain ID of the source chain
* @custom:field consensusChainId Chain ID of the Omni consensus chain
* @custom:field confLevel Confirmation level of the cross chain block
* @custom:field offset Offset of the cross chain block
* @custom:field sourceBlockHeight Height of the source chain block
* @custom:field sourceBlockHash Hash of the source chain block
*/
struct BlockHeader {
uint64 sourceChainId;
uint64 consensusChainId;
uint8 confLevel;
uint64 offset;
uint64 sourceBlockHeight;
bytes32 sourceBlockHash;
}
/**
* @notice The required parameters to submit xmsgs to an OmniPortal. Constructed by the relayer
* by watching Omni's consensus chain, and source chain blocks.
* @custom:field attestationRoot Merkle root of xchain block (XBlockRoot), attested to and signed by validators
* @custom:field validatorSetId Unique identifier of the validator set that attested to this root
* @custom:field blockHeader Block header, identifies xchain block
* @custom:field msgs Messages to execute
* @custom:field proof Multi proof of block header and messages, proven against attestationRoot
* @custom:field proofFlags Multi proof flags
* @custom:field signatures Array of validator signatures of the attestationRoot, and their public keys
*/
struct Submission {
bytes32 attestationRoot;
uint64 validatorSetId;
BlockHeader blockHeader;
Msg[] msgs;
bytes32[] proof;
bool[] proofFlags;
SigTuple[] signatures;
}
/**
* @notice A tuple of a validator's ethereum address and signature over some digest.
* @custom:field validatorAddr Validator ethereum address
* @custom:field signature Validator signature over some digest; Ethereum 65 bytes [R || S || V] format.
*/
struct SigTuple {
address validatorAddr;
bytes signature;
}
/**
* @notice An Omni validator, specified by their ethereum address and voting power.
* @custom:field addr Validator ethereum address
* @custom:field power Validator voting power
*/
struct Validator {
address addr;
uint64 power;
}
/**
* @notice A chain in the "omni network" specified by its chain ID and supported shards.
* @custom:field chainId Chain ID
* @custom:field shards Supported shards
*/
struct Chain {
uint64 chainId;
uint64[] shards;
}
}
"
},
"src/libraries/ConfLevel.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.12;
/**
* @title ConfLevel
* @notice XMsg confirmation levels. Matches ConfLevels in lib/xchain/types.go
* @dev We prefer explicit constants over Enums, because we want uint8 values to start at 1, not 0, as they do in
* lib/xchain/types.go, such that 0 can represent "unset". Note only latest and finalized levels are supported
* on-chain.
*/
library ConfLevel {
/**
* @notice XMsg confirmation level "latest", last byte of xmsg.shardId.
*/
uint8 internal constant Latest = 1;
/**
* @notice XMsg confirmation level "finalized", last byte of xmsg.shardId.
*/
uint8 internal constant Finalized = 4;
/**
* @notice Returns true if the given level is valid.
*/
function isValid(uint8 level) internal pure returns (bool) {
return level == Latest || level == Finalized;
}
/**
* @notice Returns broadcast shard version of the given level.
*/
function toBroadcastShard(uint8 level) internal pure returns (uint64) {
return uint64(level) | 0x0100;
}
}
"
},
"src/libraries/nomina/Predeploys.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.24;
/**
* @title Predeploys
* @notice Halo predeploy addresses (match halo/genutil/evm/predeploys.go)
*/
library Predeploys {
uint256 internal constant NamespaceSize = 1024;
address internal constant NominaNamespace = 0x121e240000000000000000000000000000000000;
address internal constant OctaneNamespace = 0xCCcCCc0000000000000000000000000000000000;
// nomina predeploys
address internal constant PortalRegistry = 0x121E240000000000000000000000000000000001;
address internal constant NominaBridgeNative = 0x121E240000000000000000000000000000000002;
address internal constant WNomina = 0x121E240000000000000000000000000000000003;
// octane predeploys
address internal constant Staking = 0xCCcCcC0000000000000000000000000000000001;
address internal constant Slashing = 0xCccCCC0000000000000000000000000000000002;
address internal constant Upgrade = 0xccCCcc0000000000000000000000000000000003;
address internal constant Distribution = 0xCcCcCC0000000000000000000000000000000004;
address internal constant Redenom = 0xCCCcCC0000000000000000000000000000000005;
function namespaces() internal pure returns (address[] memory ns) {
ns = new address[](2);
ns[0] = NominaNamespace;
ns[1] = OctaneNamespace;
}
/**
* @notice Return true if `addr` is not proxied
*/
function notProxied(address addr) internal pure returns (bool) {
return addr == WNomina;
}
/**
* @notice Return implementation address for a proxied predeploy
*/
function impl(address addr) internal pure returns (address) {
require(isPredeploy(addr), "Predeploys: not a predeploy");
require(!notProxied(addr), "Predeploys: not proxied");
// max uint160 is odd, which gives us unique implementation for each predeploy
return address(type(uint160).max - uint160(addr));
}
/**
* @notice Return true if `addr` is an active predeploy
*/
function isActivePredeploy(address addr) internal pure returns (bool) {
return addr == PortalRegistry || addr == NominaBridgeNative || addr == WNomina || addr == Staking
|| addr == Slashing || addr == Upgrade || addr == Distribution || addr == Redenom;
}
/**
* @notice Return true if `addr` is in some predeploy namespace
*/
function isPredeploy(address addr) internal pure returns (bool) {
return (uint160(addr) >> 10 == uint160(NominaNamespace) >> 10)
|| (uint160(addr) >> 10 == uint160(OctaneNamespace) >> 10);
}
}
"
},
"src/token/nomina/NominaBridgeNative.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.24;
import { NominaBridgeCommon } from "./NominaBridgeCommon.sol";
import { IOmniPortal } from "src/interfaces/IOmniPortal.sol";
import { NominaBridgeL1 } from "./NominaBridgeL1.sol";
import { ConfLevel } from "src/libraries/ConfLevel.sol";
import { XTypes } from "src/libraries/XTypes.sol";
/**
* @title NominaBridgeNative
* @notice The Nomina side of Nomina's native token bridge. Partner to NominaBridgeL1, which is deployed to Ethereum.
* This contract is predeployed to Nomina's EVM, prefunded with native NOM tokens to match totalL1Supply, such
* that each L1 token has a "sibling" native token on Nomina.
* @dev This contract is predeployed, and requires storage slots to be set in genesis.
* initialize(...) is called pre-deployment, in script/genesis/AllocPredeploys.s.sol
* Initializers on the implementation are disabled via manual storage updates, rather than in a constructor.
* If a new implementation is required, a constructor should be added.
*/
contract NominaBridgeNative is NominaBridgeCommon {
/**
* @notice Emitted when an account deposits NOM, bridging it to Ethereum.
*/
event Bridge(address indexed payor, address indexed to, uint256 amount);
/**
* @notice Emitted when NOM tokens are withdrawn for an account.
* If success is false, the amount is claimable by the account.
*/
event Withdraw(address indexed payor, address indexed to, uint256 amount, bool success);
/**
* @notice Emitted when an account claims NOM tokens that failed to be withdrawn.
*/
event Claimed(address indexed claimant, address indexed to, uint256 amount);
/**
* @notice Emitted on setup(...)
*/
event Setup(uint64 l1ChainId, address portal, address l1Bridge, uint256 l1Deposits);
/**
* @notice The conversion rate from OMNI to NOM.
*/
uint8 private constant _CONVERSION_RATE = 75;
/**
* @notice Reserved gas after transfer to write to claimable mapping if needed.
*/
uint64 private constant CLAIMABLE_GAS = 32_000;
/**
* @notice xcall gas limit for NominaBridgeL1.withdraw
*/
uint64 public constant XCALL_WITHDRAW_GAS_LIMIT = 80_000;
/**
* @notice L1 chain id, configurable so that this contract can be used on testnets.
*/
uint64 public l1ChainId;
/**
* @notice The OmniPortal contract.
*/
IOmniPortal public portal;
/**
* @notice Total NOM tokens deposited to NominaBridgeL1.
*
* If l1Deposits == totalL1Supply, all NOM tokens are on Nomina's EVM.
* If l1Deposits == 0, withdraws to L1 are blocked.
*
* Without validator rewards, l1Deposits == 0 would mean all NOM tokens are on Ethereum.
* However with validator rewards, some NOM may remain on Nomina.
*
* This balance is synced on each withdraw to Nomina, and decremented on each bridge to back L1.
*/
uint256 public l1Deposits;
/**
* @notice The address of the NominaBridgeL1 contract deployed to Ethereum.
*/
address public l1Bridge;
/**
* @notice Track claimable amount per address. Claimable amount increases when withdraw transfer fails.
*/
mapping(address => uint256) public claimable;
constructor() {
_disableInitializers();
}
function initialize(address owner_) external initializer {
__Ownable_init(owner_);
}
function initializeV2() external reinitializer(2) {
l1Deposits *= _CONVERSION_RATE;
}
/**
* @notice Withdraw `amount` native NOM to `to`. Only callable via xcall from NominaBridgeL1.
* @param payor The address of the account with NOM on L1.
* @param to The address to receive the NOM on Nomina.
* @param amount The amount of NOM to withdraw.
*/
function withdraw(address payor, address to, uint256 amount) external whenNotPaused(ACTION_WITHDRAW) {
XTypes.MsgContext memory xmsg = portal.xmsg();
require(msg.sender == address(portal), "NominaBridge: not xcall"); // this protects against reentrancy
require(xmsg.sender == l1Bridge, "NominaBridge: not bridge");
require(xmsg.sourceChainId == l1ChainId, "NominaBridge: not L1");
l1Deposits += amount;
(bool success,) = to.call{ value: amount, gas: gasleft() - CLAIMABLE_GAS }("");
if (!success) claimable[payor] += amount;
emit Withdraw(payor, to, amount, success);
}
/**
* @notice Bridge `amount` NOM to `to` on L1.
*/
function bridge(address to, uint256 amount) external payable whenNotPaused(ACTION_BRIDGE) {
_bridge(to, amount);
}
/**
* @dev Trigger a withdraw of `amount` NOM to `to` on L1, via xcall.
*/
function _bridge(address to, uint256 amount) internal {
require(to != address(0), "NominaBridge: no bridge to zero");
require(amount > 0, "NominaBridge: amount must be > 0");
require(amount <= l1Deposits, "NominaBridge: no liquidity");
require(msg.value >= amount + bridgeFee(to, amount), "NominaBridge: insufficient funds");
l1Deposits -= amount;
// if fee is overpaid, forward excess to portal.
// balance of this contract should continue to reflect funds bridged to L1.
portal.xcall{ value: msg.value - amount }(
l1ChainId,
ConfLevel.Finalized,
l1Bridge,
abi.encodeCall(NominaBridgeL1.withdraw, (to, amount)),
XCALL_WITHDRAW_GAS_LIMIT
);
emit Bridge(msg.sender, to, amount);
}
/**
* @notice Return the xcall fee required to bridge `amount` to `to`.
*/
function bridgeFee(address to, uint256 amount) public view returns (uint256) {
return portal.feeFor(l1ChainId, abi.encodeCall(NominaBridgeL1.withdraw, (to, amount)), XCALL_WITHDRAW_GAS_LIMIT);
}
/**
* @notice Claim NOM tokens that failed to be withdrawn via xmsg from NominaBridgeL1.
* @dev We require this be made by xcall, because an account on Nomina may not be authorized to
* claim for that address on L1. Consider the case wherein the address of the L1 contract that initiated the
* withdraw is the same as the address of a contract on Nomina deployed and owned by a malicious actor.
*/
function claim(address to) external whenNotPaused(ACTION_WITHDRAW) {
XTypes.MsgContext memory xmsg = portal.xmsg();
require(msg.sender == address(portal), "NominaBridge: not xcall");
require(xmsg.sourceChainId == l1ChainId, "NominaBridge: not L1");
require(to != address(0), "NominaBridge: no claim to zero");
address claimant = xmsg.sender;
require(claimable[claimant] > 0, "NominaBridge: nothing to claim");
uint256 amount = claimable[claimant];
claimable[claimant] = 0;
(bool success,) = to.call{ value: amount }("");
require(success, "NominaBridge: transfer failed");
emit Claimed(claimant, to, amount);
}
//////////////////////////////////////////////////////////////////////////////
// Admin functions //
//////////////////////////////////////////////////////////////////////////////
/**
* @notice Setup core contract parameters, done by owner immediately after pre-deployment.
* @param l1ChainId_ The chain id of the L1 network.
* @param portal_ The address of the NominaPortal contract.
* @param l1Bridge_ The address of the L1 NominaBridge contract.
* @param l1Deposits_ The number of tokens deposited to L1 bridge contract at setup
* (to account for genesis prefunds)
*/
function setup(uint64 l1ChainId_, address portal_, address l1Bridge_, uint256 l1Deposits_) external onlyOwner {
l1ChainId = l1ChainId_;
portal = IOmniPortal(portal_);
l1Bridge = l1Bridge_;
l1Deposits = l1Deposits_;
emit Setup(l1ChainId_, portal_, l1Bridge_, l1Deposits_);
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
OwnableStorage storage $ = _getOwnableStorage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"src/utils/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.8.24;
/**
* @title PausableUpgradeable
* @notice Contract module which provides a way to pause certain functions by key.
* @dev We use a map of bytes32 key to bools, rather than uint256 bitmap, to allow keys to be generated dynamically.
* This allows for flexible pausing, but at higher gas cost.
*/
contract PausableUpgradeable {
/// @notice Emitted when a key is paused.
event Paused(bytes32 indexed key);
/// @notice Emitted when a key is unpaused.
event Unpaused(bytes32 indexed key);
/// @custom:storage-location erc7201:omni.storage.Pauseable
struct PauseableStorage {
mapping(bytes32 => bool) _paused;
}
// keccak256(abi.encode(uint256(keccak256("omni.storage.Pauseable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageSlot = 0xff37105740f03695c8f3597f3aff2b92fbe1c80abea3c28731ecff2efd693400;
function _getPauseableStorage() internal pure returns (PauseableStorage storage $) {
assembly {
$.slot := PausableStorageSlot
}
}
/**
* @dev Special key for pausing all keys.
*/
bytes32 public constant KeyPauseAll = keccak256("PAUSE_ALL");
/**
* @notice Pause by key.
*/
function _pause(bytes32 key) internal {
PauseableStorage storage $ = _getPauseableStorage();
require(!$._paused[key], "Pausable: paused");
$._paused[key] = true;
emit Paused(key);
}
/**
* @notice Unpause by key.
*/
function _unpause(bytes32 key) internal {
PauseableStorage storage $ = _getPauseableStorage();
require($._paused[key], "Pausable: not paused");
$._paused[key] = false;
emit Unpaused(key);
}
/**
* @notice Returns true if `key` is paused, or all keys are paused.
*/
function _isPaused(bytes32 key) internal view returns (bool) {
PauseableStorage storage $ = _getPauseableStorage();
return $._paused[KeyPauseAll] || $._paused[key];
}
/**
* @notice Returns true if either `key1` or `key2` is paused, or all keys are paused.
*/
function _isPaused(bytes32 key1, bytes32 key2) internal view returns (bool) {
PauseableStorage storage $ = _getPauseableStorage();
return $._paused[KeyPauseAll] || $._paused[key1] || $._paused[key2];
}
/**
* @notice Returns true if all keys are paused.
*/
function _isAllPaused() internal view returns (bool) {
PauseableStorage storage $ = _getPauseableStorage();
return $._paused[KeyPauseAll];
}
/**
* @notice Pause all keys.
*/
function _pauseAll() internal {
_pause(KeyPauseAll);
}
/**
* @notice Unpause all keys.
*/
function _unpauseAll() internal {
_unpause(KeyPauseAll);
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reinitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
*
* NOTE: Consider following the ERC-7201 formula to derive storage locations.
*/
function _initializableStorageSlot() internal pure virtual returns (bytes32) {
return INITIALIZABLE_STORAGE;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
bytes32 slot = _initializableStorageSlot();
assembly {
$.slot := slot
}
}
}
"
}
},
"settings": {
"remappings": [
"forge-std/=node_modules/forge-std/src/",
"src/=src/",
"test/=test/",
"avs/=../avs/src/",
"solve/=../solve/",
"core/=/",
"nomina/=../nomina/",
"@openzeppelin-upgrades/contracts/=node_modules/@openzeppelin/contracts-upgradeable/",
"@hyperlane-xyz/=node_modules/@hyperlane-xyz/",
"@nomad-xyz/=node_modules/@nomad-xyz/",
"@openzeppelin-v4/=node_modules/@openzeppelin-v4/",
"@openzeppelin/=node_modules/@openzeppelin/",
"@uniswap/=node_modules/@uniswap/",
"createx/=node_modules/createx/",
"eigenlayer-contracts/=node_modules/eigenlayer-contracts/",
"eigenlayer-middleware/=node_modules/eigenlayer-middleware/",
"elliptic-curve-solidity/=node_modules/elliptic-curve-solidity/",
"multiproof/=node_modules/multiproof/",
"murky/=node_modules/murky/",
"solady/=node_modules/solady/",
"solmate/=node_modules/solmate/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false
}
}}
Submitted on: 2025-09-19 17:28:50
Comments
Log in to comment.
No comments yet.