NominaBridgeL1

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
  }
}}

Tags:
ERC20, Multisig, Pausable, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x11bb7d5dfd3490d874a49300308b29fe5616fe1f|verified:true|block:23397722|tx:0xe7f0509b060bab58156bd2ed40a6c58d76d69a9ae8a27ae4e92e84ee356e29fd|first_check:1758295728

Submitted on: 2025-09-19 17:28:50

Comments

Log in to comment.

No comments yet.