Gateway202509

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/upgrade/Gateway202509.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import "../Gateway.sol";

// New Gateway logic contract with an fee initializer
contract Gateway202509 is Gateway {
    constructor(address beefyClient, address agentExecutor) Gateway(beefyClient, agentExecutor) {}

    // Override parent initializer to prevent re-initialization of storage.
    function initialize(bytes calldata) external override {
        // Ensure that arbitrary users cannot initialize storage in this logic contract.
        if (ERC1967.load() == address(0)) {
            revert Unauthorized();
        }
        AssetsStorage.Layout storage assets = AssetsStorage.layout();
        assets.foreignTokenDecimals = 10;
        assets.maxDestinationFee = 20_000_000_000; // 2 DOT
    }
}
"
    },
    "src/Gateway.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol";
import {Verification} from "./Verification.sol";
import {Initializer} from "./Initializer.sol";
import {AgentExecutor} from "./AgentExecutor.sol";
import {IGatewayBase} from "./interfaces/IGatewayBase.sol";
import {
    OperatingMode,
    ParaID,
    Channel,
    ChannelID,
    MultiAddress,
    InboundMessageV1,
    CommandV1,
    InboundMessageV2,
    CommandV2,
    CommandKind,
    CallsV1,
    HandlersV1,
    CallsV2,
    HandlersV2,
    IGatewayV1,
    IGatewayV2
} from "./Types.sol";
import {Network} from "./v2/Types.sol";
import {Upgrade} from "./Upgrade.sol";
import {IInitializable} from "./interfaces/IInitializable.sol";
import {IUpgradable} from "./interfaces/IUpgradable.sol";
import {ERC1967} from "./utils/ERC1967.sol";
import {Address} from "./utils/Address.sol";
import {SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {Math} from "./utils/Math.sol";
import {ScaleCodec} from "./utils/ScaleCodec.sol";
import {Functions} from "./Functions.sol";
import {Constants} from "./Constants.sol";

import {CoreStorage} from "./storage/CoreStorage.sol";
import {PricingStorage} from "./storage/PricingStorage.sol";
import {AssetsStorage} from "./storage/AssetsStorage.sol";

import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol";

contract Gateway is IGatewayBase, IGatewayV1, IGatewayV2, IInitializable, IUpgradable {
    using Address for address;
    using SafeNativeTransfer for address payable;

    // Address of the code to be run within `Agent.sol` using delegatecall
    address public immutable AGENT_EXECUTOR;

    // Consensus client for Polkadot
    address public immutable BEEFY_CLIENT;

    // Message handlers can only be dispatched by the gateway itself
    modifier onlySelf() {
        if (msg.sender != address(this)) {
            revert IGatewayBase.Unauthorized();
        }
        _;
    }

    // Makes functions nonreentrant
    modifier nonreentrant() {
        assembly {
            if tload(0) { revert(0, 0) }

            // Set the flag to mark the function is currently executing.
            tstore(0, 1)
        }
        _;
        // Unlocks the guard, making the pattern composable.
        // After the function exits, it can be called again, even in the same transaction.
        assembly {
            tstore(0, 0)
        }
    }

    constructor(address beefyClient, address agentExecutor) {
        BEEFY_CLIENT = beefyClient;
        AGENT_EXECUTOR = agentExecutor;
    }

    /*
    *     _________
    *     \_   ___ \   ____    _____    _____    ____    ____
    *     /    \  \/  /  _ \  /     \  /     \  /  _ \  /    \
    *     \     \____(  <_> )|  Y Y  \|  Y Y  \(  <_> )|   |  \
    *      \______  / \____/ |__|_|  /|__|_|  / \____/ |___|  /
    *             \/               \/       \/              \/
    */

    // Verify that a message commitment is considered finalized by our BEEFY light client.
    function _verifyCommitment(bytes32 commitment, Verification.Proof calldata proof, bool isV2)
        internal
        view
        virtual
        returns (bool)
    {
        return Verification.verifyCommitment(
            BEEFY_CLIENT,
            ScaleCodec.encodeU32(uint32(ParaID.unwrap(Constants.BRIDGE_HUB_PARA_ID))),
            commitment,
            proof,
            isV2
        );
    }

    /*
    *     _____   __________ .___          ____
    *    /  _  \  \______   \|   | ___  __/_   |
    *   /  /_\  \  |     ___/|   | \  \/ / |   |
    *  /    |    \ |    |    |   |  \   /  |   |
    *  \____|__  / |____|    |___|   \_/   |___|
    *          \/
    */

    /**
     * APIv1 Constants
     */

    // Gas used for:
    // 1. Mapping a command id to an implementation function
    // 2. Calling implementation function
    uint256 constant DISPATCH_OVERHEAD_GAS_V1 = 10_000;

    /**
     * APIv1 External API
     */

    /// @dev Submit a message from Polkadot for verification and dispatch
    /// @param message A message produced by the OutboundQueue pallet on BridgeHub
    /// @param leafProof A message proof used to verify that the message is in the merkle tree
    ///        committed by the OutboundQueue pallet.
    /// @param headerProof A proof that the commitment is included in parachain header that was
    ///        finalized by BEEFY.
    function submitV1(
        InboundMessageV1 calldata message,
        bytes32[] calldata leafProof,
        Verification.Proof calldata headerProof
    ) external nonreentrant {
        uint256 startGas = gasleft();

        Channel storage channel = Functions.ensureChannel(message.channelID);

        // Ensure this message is not being replayed
        if (message.nonce != channel.inboundNonce + 1) {
            revert IGatewayBase.InvalidNonce();
        }

        // Increment nonce for origin.
        // This also prevents the re-entrancy case in which a malicious party tries to re-enter by
        // calling `submitInbound` again with the same (message, leafProof, headerProof) arguments.
        channel.inboundNonce++;

        // Produce the commitment (message root) by applying the leaf proof to the message leaf
        bytes32 leafHash = keccak256(abi.encode(message));
        bytes32 commitment = MerkleProof.processProof(leafProof, leafHash);

        // Verify that the commitment is included in a parachain header finalized by BEEFY.
        if (!_verifyCommitment(commitment, headerProof, false)) {
            revert IGatewayBase.InvalidProof();
        }

        // Make sure relayers provide enough gas so that inner message dispatch
        // does not run out of gas.
        uint256 maxDispatchGas = message.maxDispatchGas;
        if (gasleft() * 63 / 64 < maxDispatchGas + DISPATCH_OVERHEAD_GAS_V1) {
            revert IGatewayBase.NotEnoughGas();
        }

        bool success = true;

        // Dispatch message to a handler
        if (message.command == CommandV1.AgentExecute) {
            try Gateway(this).v1_handleAgentExecute{gas: maxDispatchGas}(message.params) {}
            catch {
                success = false;
            }
        } else if (message.command == CommandV1.SetOperatingMode) {
            try Gateway(this).v1_handleSetOperatingMode{gas: maxDispatchGas}(message.params) {}
            catch {
                success = false;
            }
        } else if (message.command == CommandV1.Upgrade) {
            try Gateway(this).v1_handleUpgrade{gas: maxDispatchGas}(message.params) {}
            catch {
                success = false;
            }
        } else if (message.command == CommandV1.SetTokenTransferFees) {
            try Gateway(this).v1_handleSetTokenTransferFees{gas: maxDispatchGas}(message.params) {}
            catch {
                success = false;
            }
        } else if (message.command == CommandV1.SetPricingParameters) {
            try Gateway(this).v1_handleSetPricingParameters{gas: maxDispatchGas}(message.params) {}
            catch {
                success = false;
            }
        } else if (message.command == CommandV1.UnlockNativeToken) {
            try Gateway(this).v1_handleUnlockNativeToken{gas: maxDispatchGas}(message.params) {}
            catch {
                success = false;
            }
        } else if (message.command == CommandV1.RegisterForeignToken) {
            try Gateway(this).v1_handleRegisterForeignToken{gas: maxDispatchGas}(message.params) {}
            catch {
                success = false;
            }
        } else if (message.command == CommandV1.MintForeignToken) {
            try Gateway(this).v1_handleMintForeignToken{gas: maxDispatchGas}(
                message.channelID, message.params
            ) {} catch {
                success = false;
            }
        } else {
            success = false;
        }

        // Calculate a gas refund, capped to protect against huge spikes in `tx.gasprice`
        // that could drain funds unnecessarily. During these spikes, relayers should back off.
        uint256 gasUsed = v1_transactionBaseGas() + (startGas - gasleft());
        uint256 refund = gasUsed * Math.min(tx.gasprice, message.maxFeePerGas);

        // Add the reward to the refund amount. If the sum is more than the funds available
        // in the gateway, then reduce the total amount
        uint256 amount = Math.min(refund + message.reward, address(this).balance);

        // Do the payment if there funds available in the gateway
        if (amount > Functions.dustThreshold()) {
            payable(msg.sender).safeNativeTransfer(amount);
        }

        emit IGatewayV1.InboundMessageDispatched(
            message.channelID, message.nonce, message.id, success
        );
    }

    function operatingMode()
        external
        view
        override(IGatewayV1, IGatewayV2)
        returns (OperatingMode)
    {
        return CoreStorage.layout().mode;
    }

    function channelOperatingModeOf(ChannelID channelID) external view returns (OperatingMode) {
        return CallsV1.channelOperatingModeOf(channelID);
    }

    function channelNoncesOf(ChannelID channelID) external view returns (uint64, uint64) {
        return CallsV1.channelNoncesOf(channelID);
    }

    function agentOf(bytes32 agentID)
        external
        view
        override(IGatewayV1, IGatewayV2)
        returns (address)
    {
        return Functions.ensureAgent(agentID);
    }

    function pricingParameters() external view returns (UD60x18, uint128) {
        return CallsV1.pricingParameters();
    }

    function implementation() public view returns (address) {
        return ERC1967.load();
    }

    function isTokenRegistered(address token)
        external
        view
        override(IGatewayV1, IGatewayV2)
        returns (bool)
    {
        return CallsV1.isTokenRegistered(token);
    }

    function depositEther() external payable {
        emit Deposited(msg.sender, msg.value);
    }

    function queryForeignTokenID(address token) external view returns (bytes32) {
        return AssetsStorage.layout().tokenRegistry[token].foreignID;
    }

    // Total fee for registering a token
    function quoteRegisterTokenFee() external view returns (uint256) {
        return CallsV1.quoteRegisterTokenFee();
    }

    // Register an Ethereum-native token in the gateway and on AssetHub
    function registerToken(address token) external payable nonreentrant {
        CallsV1.registerToken(token);
    }

    // Total fee for sending a token
    function quoteSendTokenFee(address token, ParaID destinationChain, uint128 destinationFee)
        external
        view
        returns (uint256)
    {
        return CallsV1.quoteSendTokenFee(token, destinationChain, destinationFee);
    }

    // Transfer ERC20 tokens to a Polkadot parachain
    function sendToken(
        address token,
        ParaID destinationChain,
        MultiAddress calldata destinationAddress,
        uint128 destinationFee,
        uint128 amount
    ) external payable nonreentrant {
        CallsV1.sendToken(
            token, msg.sender, destinationChain, destinationAddress, destinationFee, amount
        );
    }

    // @dev Get token address by tokenID
    function tokenAddressOf(bytes32 tokenID) external view returns (address) {
        return CallsV1.tokenAddressOf(tokenID);
    }

    /**
     * APIv1 Inbound Message Handlers
     */

    // Execute code within an agent
    function v1_handleAgentExecute(bytes calldata data) external onlySelf {
        HandlersV1.agentExecute(AGENT_EXECUTOR, data);
    }

    /// @dev Perform an upgrade of the gateway
    function v1_handleUpgrade(bytes calldata data) external onlySelf {
        HandlersV1.upgrade(data);
    }

    // @dev Set the operating mode of the gateway
    function v1_handleSetOperatingMode(bytes calldata data) external onlySelf {
        HandlersV1.setOperatingMode(data);
    }

    // @dev Set token fees of the gateway
    function v1_handleSetTokenTransferFees(bytes calldata data) external onlySelf {
        HandlersV1.setTokenTransferFees(data);
    }

    // @dev Set pricing params of the gateway
    function v1_handleSetPricingParameters(bytes calldata data) external onlySelf {
        HandlersV1.setPricingParameters(data);
    }

    // @dev Transfer Ethereum native token back from polkadot
    function v1_handleUnlockNativeToken(bytes calldata data) external onlySelf {
        HandlersV1.unlockNativeToken(AGENT_EXECUTOR, data);
    }

    // @dev Register a new fungible Polkadot token for an agent
    function v1_handleRegisterForeignToken(bytes calldata data) external onlySelf {
        HandlersV1.registerForeignToken(data);
    }

    // @dev Mint foreign token from polkadot
    function v1_handleMintForeignToken(ChannelID channelID, bytes calldata data)
        external
        onlySelf
    {
        HandlersV1.mintForeignToken(channelID, data);
    }

    /**
     * APIv1 Internal functions
     */

    // Best-effort attempt at estimating the base gas use of `submitInbound` transaction, outside
    // the block of code that is metered.
    // This includes:
    // * Cost paid for every transaction: 21000 gas
    // * Cost of calldata: Zero byte = 4 gas, Non-zero byte = 16 gas
    // * Cost of code inside submitInitial that is not metered: 14_698
    //
    // The major cost of calldata are the merkle proofs, which should dominate anything else
    // (including the message payload) Since the merkle proofs are hashes, they are much more
    // likely to be composed of more non-zero bytes than zero bytes.
    //
    // Reference: Ethereum Yellow Paper
    function v1_transactionBaseGas() internal pure returns (uint256) {
        return 21_000 + 14_698 + (msg.data.length * 16);
    }

    /*
    *     _____   __________ .___         ________
    *    /  _  \  \______   \|   | ___  __\_____  \
    *   /  /_\  \  |     ___/|   | \  \/ / /  ____/§
    *  /    |    \ |    |    |   |  \   / /       \
    *  \____|__  / |____|    |___|   \_/  \_______ \
    *          \/                                 \/
    */

    /// Overhead in selecting the dispatch handler for an arbitrary command
    uint256 internal constant DISPATCH_OVERHEAD_GAS_V2 = 24_000;

    function v2_submit(
        InboundMessageV2 calldata message,
        bytes32[] calldata leafProof,
        Verification.Proof calldata headerProof,
        bytes32 rewardAddress
    ) external nonreentrant {
        CoreStorage.Layout storage $ = CoreStorage.layout();

        if ($.inboundNonce.get(message.nonce)) {
            revert IGatewayBase.InvalidNonce();
        }

        bytes32 leafHash = keccak256(abi.encode(message));

        $.inboundNonce.set(message.nonce);

        // Produce the commitment (message root) by applying the leaf proof to the message leaf
        bytes32 commitment = MerkleProof.processProof(leafProof, leafHash);

        // Verify that the commitment is included in a parachain header finalized by BEEFY.
        if (!_verifyCommitment(commitment, headerProof, true)) {
            revert IGatewayBase.InvalidProof();
        }

        // Dispatch the message payload. The boolean returned indicates whether all commands succeeded.
        bool success = v2_dispatch(message);

        // Emit the event with a success value "true" if all commands successfully executed, otherwise "false"
        // if all or some of the commands failed.
        emit IGatewayV2.InboundMessageDispatched(
            message.nonce, message.topic, success, rewardAddress
        );
    }

    function v2_outboundNonce() external view returns (uint64) {
        return CallsV2.outboundNonce();
    }

    function v2_isDispatched(uint64 nonce) external view returns (bool) {
        return CoreStorage.layout().inboundNonce.get(nonce);
    }

    // See docs for `IGateway.v2_sendMessage`
    function v2_sendMessage(
        bytes calldata xcm,
        bytes[] calldata assets,
        bytes calldata claimer,
        uint128 executionFee,
        uint128 relayerFee
    ) external payable nonreentrant {
        CallsV2.sendMessage(xcm, assets, claimer, executionFee, relayerFee);
    }

    // See docs for `IGateway.v2_registerToken`
    function v2_registerToken(
        address token,
        uint8 network,
        uint128 executionFee,
        uint128 relayerFee
    ) external payable nonreentrant {
        require(network == uint8(Network.Polkadot), IGatewayV2.InvalidNetwork());
        CallsV2.registerToken(token, Network(network), executionFee, relayerFee);
    }

    // See docs for `IGateway.v2_createAgent`
    function v2_createAgent(bytes32 id) external {
        CallsV2.createAgent(id);
    }

    /**
     * APIv2 Message Handlers
     */

    //  Perform an upgrade of the gateway
    function v2_handleUpgrade(bytes calldata data) external onlySelf {
        HandlersV2.upgrade(data);
    }

    // Set the operating mode of the gateway
    function v2_handleSetOperatingMode(bytes calldata data) external onlySelf {
        HandlersV2.setOperatingMode(data);
    }

    // Unlock Native token
    function v2_handleUnlockNativeToken(bytes calldata data) external onlySelf {
        HandlersV2.unlockNativeToken(AGENT_EXECUTOR, data);
    }

    // Mint foreign token from polkadot
    function v2_handleRegisterForeignToken(bytes calldata data) external onlySelf {
        HandlersV2.registerForeignToken(data);
    }

    // Mint foreign token from polkadot
    function v2_handleMintForeignToken(bytes calldata data) external onlySelf {
        HandlersV2.mintForeignToken(data);
    }

    // Call an arbitrary contract function
    function v2_handleCallContract(bytes32 origin, bytes calldata data) external onlySelf {
        HandlersV2.callContract(origin, AGENT_EXECUTOR, data);
    }

    /**
     * APIv2 Internal functions
     */

    // Internal helper to dispatch a single command
    function _dispatchCommand(CommandV2 calldata command, bytes32 origin)
        internal
        returns (bool)
    {
        // check that there is enough gas available to forward to the command handler
        if (gasleft() * 63 / 64 < command.gas + DISPATCH_OVERHEAD_GAS_V2) {
            revert IGatewayV2.InsufficientGasLimit();
        }

        if (command.kind == CommandKind.Upgrade) {
            try Gateway(this).v2_handleUpgrade{gas: command.gas}(command.payload) {}
            catch {
                return false;
            }
        } else if (command.kind == CommandKind.SetOperatingMode) {
            try Gateway(this).v2_handleSetOperatingMode{gas: command.gas}(command.payload) {}
            catch {
                return false;
            }
        } else if (command.kind == CommandKind.UnlockNativeToken) {
            try Gateway(this).v2_handleUnlockNativeToken{gas: command.gas}(command.payload) {}
            catch {
                return false;
            }
        } else if (command.kind == CommandKind.RegisterForeignToken) {
            try Gateway(this).v2_handleRegisterForeignToken{gas: command.gas}(command.payload) {}
            catch {
                return false;
            }
        } else if (command.kind == CommandKind.MintForeignToken) {
            try Gateway(this).v2_handleMintForeignToken{gas: command.gas}(command.payload) {}
            catch {
                return false;
            }
        } else if (command.kind == CommandKind.CallContract) {
            try Gateway(this).v2_handleCallContract{gas: command.gas}(origin, command.payload) {}
            catch {
                return false;
            }
        } else {
            // Unknown command
            return false;
        }
        return true;
    }

    // Dispatch all the commands within the batch of commands in the message payload. Each command is processed
    // independently, such that failures emit a `CommandFailed` event without stopping execution of subsequent commands.
    function v2_dispatch(InboundMessageV2 calldata message) internal returns (bool) {
        bool allCommandsSucceeded = true;

        for (uint256 i = 0; i < message.commands.length; i++) {
            if (!_dispatchCommand(message.commands[i], message.origin)) {
                emit IGatewayV2.CommandFailed(message.nonce, i);
                allCommandsSucceeded = false;
            }
        }

        return allCommandsSucceeded;
    }

    /**
     * Upgrades
     */

    /// Initialize storage within the `GatewayProxy` contract using this initializer.
    ///
    /// This initializer cannot be called externally via the proxy as the function selector
    /// is overshadowed in the proxy.
    ///
    /// This implementation is only intended to initialize storage for initial deployments
    /// of the `GatewayProxy` contract to transient or long-lived testnets.
    ///
    /// The `GatewayProxy` deployed to Ethereum mainnet already has its storage initialized.
    /// When its logic contract needs to upgraded, a new logic contract should be developed
    /// that inherits from this base `Gateway` contract. Particularly, the `initialize` function
    /// must be overridden to ensure selective initialization of storage fields relevant
    /// to the upgrade.
    ///
    /// ```solidity
    /// contract Gateway202508 is Gateway {
    ///     function initialize(bytes calldata data) external override {
    ///         if (ERC1967.load() == address(0)) {
    ///             revert Unauthorized();
    ///         }
    ///         # Initialization routines here...
    ///     }
    /// }
    /// ```
    ///
    function initialize(bytes calldata data) external virtual {
        Initializer.initialize(data);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     *
     * _Available since v4.7._
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     *
     * _Available since v4.7._
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * _Available since v4.7._
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}
"
    },
    "src/Verification.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {SubstrateMerkleProof} from "./utils/SubstrateMerkleProof.sol";
import {BeefyClient} from "./BeefyClient.sol";
import {ScaleCodec} from "./utils/ScaleCodec.sol";
import {SubstrateTypes} from "./SubstrateTypes.sol";

library Verification {
    /// @dev Merkle proof for parachain header finalized by BEEFY
    /// Reference: https://github.com/paritytech/polkadot/blob/09b61286da11921a3dda0a8e4015ceb9ef9cffca/runtime/rococo/src/lib.rs#L1312
    struct HeadProof {
        // The leaf index of the parachain being proven
        uint256 pos;
        // The number of leaves in the merkle tree
        uint256 width;
        // The proof items
        bytes32[] proof;
    }

    /// @dev An MMRLeaf without the `leaf_extra` field.
    /// Reference: https://github.com/paritytech/substrate/blob/14e0a0b628f9154c5a2c870062c3aac7df8983ed/primitives/consensus/beefy/src/mmr.rs#L52
    struct MMRLeafPartial {
        uint8 version;
        uint32 parentNumber;
        bytes32 parentHash;
        uint64 nextAuthoritySetID;
        uint32 nextAuthoritySetLen;
        bytes32 nextAuthoritySetRoot;
    }

    /// @dev Parachain header
    /// References:
    /// * https://paritytech.github.io/substrate/master/sp_runtime/generic/struct.Header.html
    /// * https://github.com/paritytech/substrate/blob/14e0a0b628f9154c5a2c870062c3aac7df8983ed/primitives/runtime/src/generic/header.rs#L41
    struct ParachainHeader {
        bytes32 parentHash;
        uint256 number;
        bytes32 stateRoot;
        bytes32 extrinsicsRoot;
        DigestItem[] digestItems;
    }

    /// @dev Represents a digest item within a parachain header.
    /// References:
    /// * https://paritytech.github.io/substrate/master/sp_runtime/generic/enum.DigestItem.html
    /// * https://github.com/paritytech/substrate/blob/14e0a0b628f9154c5a2c870062c3aac7df8983ed/primitives/runtime/src/generic/digest.rs#L75
    struct DigestItem {
        uint256 kind;
        bytes4 consensusEngineID;
        bytes data;
    }

    /// @dev A chain of proofs
    struct Proof {
        // The parachain header containing the message commitment as a digest item
        ParachainHeader header;
        // The proof used to generate a merkle root of parachain heads
        HeadProof headProof;
        // The MMR leaf to be proven
        MMRLeafPartial leafPartial;
        // The MMR leaf prove
        bytes32[] leafProof;
        // The order in which proof items should be combined
        uint256 leafProofOrder;
    }

    error InvalidParachainHeader();

    /// @dev IDs of enum variants of DigestItem
    /// Reference: https://github.com/paritytech/substrate/blob/14e0a0b628f9154c5a2c870062c3aac7df8983ed/primitives/runtime/src/generic/digest.rs#L201
    uint256 public constant DIGEST_ITEM_OTHER = 0;
    uint256 public constant DIGEST_ITEM_CONSENSUS = 4;
    uint256 public constant DIGEST_ITEM_SEAL = 5;
    uint256 public constant DIGEST_ITEM_PRERUNTIME = 6;
    uint256 public constant DIGEST_ITEM_RUNTIME_ENVIRONMENT_UPDATED = 8;

    /// @dev Enum variant ID for CustomDigestItem::Snowbridge
    bytes1 public constant DIGEST_ITEM_OTHER_SNOWBRIDGE = 0x00;

    /// @dev Enum variant ID for CustomDigestItem::SnowbridgeV2
    bytes1 public constant DIGEST_ITEM_OTHER_SNOWBRIDGE_V2 = 0x01;

    /// @dev Verify the message commitment by applying several proofs
    ///
    /// 1. First check that the commitment is included in the digest items of the parachain header
    /// 2. Generate the root of the parachain heads merkle tree
    /// 3. Construct an MMR leaf containing the parachain heads root.
    /// 4. Verify that the MMR leaf is included in the MMR maintained by the BEEFY light client.
    ///
    /// Background info:
    ///
    /// In the Polkadot relay chain, for every block:
    /// 1. A merkle root of finalized parachain headers is constructed:
    ///    https://github.com/paritytech/polkadot/blob/09b61286da11921a3dda0a8e4015ceb9ef9cffca/runtime/rococo/src/lib.rs#L1312.
    /// 2. An MMR leaf is produced, containing this parachain headers root, and is then inserted into the
    ///    MMR maintained by the `merkle-mountain-range` pallet:
    ///    https://github.com/paritytech/substrate/tree/master/frame/merkle-mountain-range
    ///
    /// @param beefyClient The address of the BEEFY light client
    /// @param encodedParaID The SCALE-encoded parachain ID of BridgeHub
    /// @param commitment The message commitment root expected to be contained within the
    ///                   digest of BridgeHub parachain header.
    /// @param proof The chain of proofs described above
    function verifyCommitment(
        address beefyClient,
        bytes4 encodedParaID,
        bytes32 commitment,
        Proof calldata proof,
        bool isV2
    ) external view returns (bool) {
        // Verify that parachain header contains the commitment
        if (!isCommitmentInHeaderDigest(commitment, proof.header, isV2)) {
            return false;
        }

        if (proof.headProof.pos >= proof.headProof.width) {
            return false;
        }

        // Compute the merkle leaf hash of our parachain
        bytes32 parachainHeadHash = createParachainHeaderMerkleLeaf(encodedParaID, proof.header);

        // Compute the merkle root hash of all parachain heads
        bytes32 parachainHeadsRoot = SubstrateMerkleProof.computeRoot(
            parachainHeadHash, proof.headProof.pos, proof.headProof.width, proof.headProof.proof
        );

        bytes32 leafHash = createMMRLeaf(proof.leafPartial, parachainHeadsRoot);

        // Verify that the MMR leaf is part of the MMR maintained by the BEEFY light client
        return BeefyClient(beefyClient).verifyMMRLeafProof(
            leafHash, proof.leafProof, proof.leafProofOrder
        );
    }

    // Verify that a message commitment is in the header digest
    function isCommitmentInHeaderDigest(
        bytes32 commitment,
        ParachainHeader calldata header,
        bool isV2
    ) internal pure returns (bool) {
        bytes1 digestItemOtherKind = isV2 ? DIGEST_ITEM_OTHER_SNOWBRIDGE_V2 : DIGEST_ITEM_OTHER_SNOWBRIDGE;
        
        for (uint256 i = 0; i < header.digestItems.length; i++) {
            // First check if the digest item is of the correct kind (DIGEST_ITEM_OTHER)
            // and has the correct length (33 bytes)
            if (header.digestItems[i].kind == DIGEST_ITEM_OTHER && 
                header.digestItems[i].data.length == 33 &&
                header.digestItems[i].data[0] == digestItemOtherKind &&
                commitment == bytes32(header.digestItems[i].data[1:])
            ) {
                return true;
            }
        }
        return false;
    }

    // SCALE-Encodes: Vec<DigestItem>
    // Reference: https://github.com/paritytech/substrate/blob/14e0a0b628f9154c5a2c870062c3aac7df8983ed/primitives/runtime/src/generic/digest.rs#L40
    function encodeDigestItems(DigestItem[] calldata digestItems)
        internal
        pure
        returns (bytes memory)
    {
        // encode all digest items into a buffer
        bytes memory accum = hex"";
        for (uint256 i = 0; i < digestItems.length; i++) {
            accum = bytes.concat(accum, encodeDigestItem(digestItems[i]));
        }
        // Encode number of digest items, followed by encoded digest items
        return bytes.concat(ScaleCodec.checkedEncodeCompactU32(digestItems.length), accum);
    }

    function encodeDigestItem(DigestItem calldata digestItem)
        internal
        pure
        returns (bytes memory)
    {
        if (
            digestItem.kind == DIGEST_ITEM_PRERUNTIME || digestItem.kind == DIGEST_ITEM_CONSENSUS
                || digestItem.kind == DIGEST_ITEM_SEAL
        ) {
            return bytes.concat(
                bytes1(uint8(digestItem.kind)),
                digestItem.consensusEngineID,
                ScaleCodec.checkedEncodeCompactU32(digestItem.data.length),
                digestItem.data
            );
        } else if (digestItem.kind == DIGEST_ITEM_OTHER) {
            return bytes.concat(
                bytes1(uint8(DIGEST_ITEM_OTHER)),
                ScaleCodec.checkedEncodeCompactU32(digestItem.data.length),
                digestItem.data
            );
        } else if (digestItem.kind == DIGEST_ITEM_RUNTIME_ENVIRONMENT_UPDATED) {
            return bytes.concat(bytes1(uint8(DIGEST_ITEM_RUNTIME_ENVIRONMENT_UPDATED)));
        } else {
            revert InvalidParachainHeader();
        }
    }

    // Creates a keccak hash of a SCALE-encoded parachain header
    function createParachainHeaderMerkleLeaf(bytes4 encodedParaID, ParachainHeader calldata header)
        internal
        pure
        returns (bytes32)
    {
        // Hash of encoded parachain header merkle leaf
        return keccak256(createParachainHeader(encodedParaID, header));
    }

    function createParachainHeader(bytes4 encodedParaID, ParachainHeader calldata header)
        internal
        pure
        returns (bytes memory)
    {
        bytes memory encodedHeader = bytes.concat(
            // H256
            header.parentHash,
            // Compact unsigned int
            ScaleCodec.checkedEncodeCompactU32(header.number),
            // H256
            header.stateRoot,
            // H256
            header.extrinsicsRoot,
            // Vec<DigestItem>
            encodeDigestItems(header.digestItems)
        );

        return bytes.concat(
            // u32
            encodedParaID,
            // length of encoded header
            ScaleCodec.checkedEncodeCompactU32(encodedHeader.length),
            encodedHeader
        );
    }

    // SCALE-encode: MMRLeaf
    // Reference: https://github.com/paritytech/substrate/blob/14e0a0b628f9154c5a2c870062c3aac7df8983ed/primitives/consensus/beefy/src/mmr.rs#L52
    function createMMRLeaf(MMRLeafPartial memory leaf, bytes32 parachainHeadsRoot)
        internal
        pure
        returns (bytes32)
    {
        bytes memory encodedLeaf = bytes.concat(
            ScaleCodec.encodeU8(leaf.version),
            ScaleCodec.encodeU32(leaf.parentNumber),
            leaf.parentHash,
            ScaleCodec.encodeU64(leaf.nextAuthoritySetID),
            ScaleCodec.encodeU32(leaf.nextAuthoritySetLen),
            leaf.nextAuthoritySetRoot,
            parachainHeadsRoot
        );
        return keccak256(encodedLeaf);
    }
}
"
    },
    "src/Initializer.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {AgentExecutor} from "./AgentExecutor.sol";
import {Agent} from "./Agent.sol";
import {OperatingMode, ParaID, TokenInfo, Channel, ChannelID} from "./Types.sol";
import {ERC1967} from "./utils/ERC1967.sol";

import {CoreStorage} from "./storage/CoreStorage.sol";
import {PricingStorage} from "./storage/PricingStorage.sol";
import {AssetsStorage} from "./storage/AssetsStorage.sol";

import {Constants} from "./Constants.sol";

import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol";

library Initializer {
    error Unauthorized();

    // Initial configuration for bridge
    struct Config {
        OperatingMode mode;
        /// @dev The fee charged to users for submitting outbound messages (DOT)
        uint128 deliveryCost;
        /// @dev The ETH/DOT exchange rate
        UD60x18 exchangeRate;
        /// @dev The extra fee charged for registering tokens (DOT)
        uint128 assetHubCreateAssetFee;
        /// @dev The extra fee charged for sending tokens (DOT)
        uint128 assetHubReserveTransferFee;
        /// @dev extra fee to discourage spamming
        uint256 registerTokenFee;
        /// @dev Fee multiplier
        UD60x18 multiplier;
        uint8 foreignTokenDecimals;
        uint128 maxDestinationFee;
    }

    function initialize(bytes calldata data) external {
        // Prevent initialization of storage in implementation contract
        if (ERC1967.load() == address(0)) {
            revert Unauthorized();
        }

        CoreStorage.Layout storage core = CoreStorage.layout();

        Config memory config = abi.decode(data, (Config));

        core.mode = config.mode;

        // Initialize agent for BridgeHub
        address bridgeHubAgent = address(new Agent(Constants.BRIDGE_HUB_AGENT_ID));
        core.agents[Constants.BRIDGE_HUB_AGENT_ID] = bridgeHubAgent;

        // Initialize channel for primary governance track
        core.channels[Constants.PRIMARY_GOVERNANCE_CHANNEL_ID] = Channel({
            mode: OperatingMode.Normal,
            agent: bridgeHubAgent,
            inboundNonce: 0,
            outboundNonce: 0
        });

        // Initialize channel for secondary governance track
        core.channels[Constants.SECONDARY_GOVERNANCE_CHANNEL_ID] = Channel({
            mode: OperatingMode.Normal,
            agent: bridgeHubAgent,
            inboundNonce: 0,
            outboundNonce: 0
        });

        // Initialize agent for for AssetHub
        address assetHubAgent = address(new Agent(Constants.ASSET_HUB_AGENT_ID));
        core.agents[Constants.ASSET_HUB_AGENT_ID] = assetHubAgent;

        // Initialize channel for AssetHub
        core.channels[Constants.ASSET_HUB_PARA_ID.into()] = Channel({
            mode: OperatingMode.Normal,
            agent: assetHubAgent,
            inboundNonce: 0,
            outboundNonce: 0
        });

        // Initialize pricing storage
        PricingStorage.Layout storage pricing = PricingStorage.layout();
        pricing.exchangeRate = config.exchangeRate;
        pricing.deliveryCost = config.deliveryCost;
        pricing.multiplier = config.multiplier;

        // Initialize assets storage
        AssetsStorage.Layout storage assets = AssetsStorage.layout();

        assets.assetHubParaID = Constants.ASSET_HUB_PARA_ID;
        assets.assetHubAgent = assetHubAgent;
        assets.registerTokenFee = config.registerTokenFee;
        assets.assetHubCreateAssetFee = config.assetHubCreateAssetFee;
        assets.assetHubReserveTransferFee = config.assetHubReserveTransferFee;
        assets.foreignTokenDecimals = config.foreignTokenDecimals;
        assets.maxDestinationFee = config.maxDestinationFee;

        TokenInfo storage etherTokenInfo = assets.tokenRegistry[address(0)];
        etherTokenInfo.isRegistered = true;
    }
}
"
    },
    "src/AgentExecutor.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {IERC20} from "./interfaces/IERC20.sol";
import {SafeTokenTransfer, SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {Call} from "./utils/Call.sol";

/// @title Code which will run within an `Agent` using `delegatecall`.
/// @dev This is a singleton contract, meaning that all agents will execute the same code.
contract AgentExecutor {
    using SafeTokenTransfer for IERC20;
    using SafeNativeTransfer for address payable;

    // Transfer ether to `recipient`.
    function transferEther(address payable recipient, uint256 amount) external {
        recipient.safeNativeTransfer(amount);
    }

    // Transfer ERC20 to `recipient`.
    function transferToken(address token, address recipient, uint128 amount) external {
        IERC20(token).safeTransfer(recipient, amount);
    }

    // Call contract with Ether value
    function callContract(address target, bytes memory data, uint256 value) external {
        bool success = Call.safeCall(target, data, value);
        if (!success) {
            revert();
        }
    }

    function deposit() external payable {}
}
"
    },
    "src/interfaces/IGatewayBase.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {OperatingMode} from "../types/Common.sol";

/// Base interface for Gateway
interface IGatewayBase {
    error InvalidToken();
    error InvalidAmount();
    error InvalidDestination();
    error TokenNotRegistered();
    error Unsupported();
    error InvalidDestinationFee();
    error AgentDoesNotExist();
    error TokenAlreadyRegistered();
    error TokenMintFailed();
    error TokenTransferFailed();
    error InvalidProof();
    error InvalidNonce();
    error NotEnoughGas();
    error InsufficientEther();
    error Unauthorized();
    error Disabled();
    error AgentExecutionFailed(bytes returndata);
    error InvalidAgentExecutionPayload();
    error InvalidConstructorParams();
    error AlreadyInitialized();

    // Emitted when the operating mode is changed
    event OperatingModeChanged(OperatingMode mode);

    // Emitted when foreign token from polkadot registered
    event ForeignTokenRegistered(bytes32 indexed tokenID, address token);

    /// @dev Emitted when a command is sent to register a new wrapped token on AssetHub
    event TokenRegistrationSent(address token);
}
"
    },
    "src/Types.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {TokenInfo, OperatingMode} from "./types/Common.sol";
import {UD60x18} from "prb/math/src/UD60x18.sol";
import {
    ParaID,
    ChannelID,
    Channel,
    InboundMessage as InboundMessageV1,
    Command as CommandV1,
    MultiAddress
} from "./v1/Types.sol";
import {CallsV1} from "./v1/Calls.sol";
import {HandlersV1} from "./v1/Handlers.sol";
import {IGatewayV1} from "./v1/IGateway.sol";

import {
    InboundMessage as InboundMessageV2, Command as CommandV2, CommandKind
} from "./v2/Types.sol";
import {CallsV2} from "./v2/Calls.sol";
import {HandlersV2} from "./v2/Handlers.sol";
import {IGatewayV2} from "./v2/IGateway.sol";
"
    },
    "src/v2/Types.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {OperatingMode} from "./../types/Common.sol";

// Inbound message from a Polkadot parachain (via BridgeHub)
struct InboundMessage {
    // origin
    bytes32 origin;
    // Message nonce
    uint64 nonce;
    // XCM Topic
    bytes32 topic;
    // Commands
    Command[] commands;
}

struct Command {
    uint8 kind;
    uint64 gas;
    bytes payload;
}

// Command IDs
library CommandKind {
    // Upgrade the Gateway implementation contract
    uint8 constant Upgrade = 0;
    // Set the operating mode for outbound messaging in the Gateway
    uint8 constant SetOperatingMode = 1;
    // Unlock native ERC20 and transfer to recipient
    uint8 constant UnlockNativeToken = 2;
    // Register a foreign token
    uint8 constant RegisterForeignToken = 3;
    // Mint foreign tokens
    uint8 constant MintForeignToken = 4;
    // Call an arbitrary solidity contract
    uint8 constant CallContract = 5;
}

// Payload for outbound messages destined for Polkadot
struct Payload {
    // Sender of the message
    address origin;
    // Asset transfer instructions
    Asset[] assets;
    // XCM Message
    Xcm xcm;
    // SCALE-encoded location of claimer who can claim trapped assets on AssetHub
    bytes claimer;
    // Ether value not reserved for fees
    uint128 value;
    // Ether value reserved for execution fees on AH
    uint128 executionFee;
    // Ether value reserved for relayer incentives
    uint128 relayerFee;
}

struct Xcm {
    // Variant ID
    uint8 kind;
    // ABI-encoded xcm variant
    bytes data;
}

library XcmKind {
    // SCALE-encoded raw bytes for `VersionedXcm`
    uint8 constant Raw = 0;
    // Create a new asset in the ForeignAssets pallet of AH
    uint8 constant CreateAsset = 1;
}

// Format of ABI-encoded Xcm.data when Xcm.kind == XcmKind.CreateAsset
struct AsCreateAsset {
    // Token address
    address token;
    // Polkadot network to create the asset in
    uint8 network;
}

function makeRawXCM(bytes memory xcm) pure returns (Xcm memory) {
    return Xcm({kind: XcmKind.Raw, data: xcm});
}

function makeCreateAssetXCM(address token, Network network) pure returns (Xcm memory) {
    return Xcm({
        kind: XcmKind.CreateAsset,
        data: abi.encode(AsCreateAsset({token: token, network: uint8(network)}))
    });
}

struct Asset {
    uint8 kind;
    bytes data;
}

library AssetKind {
    uint8 constant NativeTokenERC20 = 0;
    uint8 constant ForeignTokenERC20 = 1;
}

// Format of Asset.data when Asset.kind == AssetKind.NativeTokenERC20
struct AsNativeTokenERC20 {
    address token;
    uint128 amount;
}

// Format of Asset.data when Asset.kind == AssetKind.ForeignTokenERC20
struct AsForeignTokenERC20 {
    bytes32 foreignID;
    uint128 amount;
}

function makeNativeAsset(address token, uint128 amount) pure returns (Asset memory) {
    return Asset({
        kind: AssetKind.NativeTokenERC20,
        data: abi.encode(AsNativeTokenERC20({token: token, amount: amount}))
    });
}

function makeForeignAsset(bytes32 foreignID, uint128 amount) pure returns (Asset memory) {
    return Asset({
        kind: AssetKind.ForeignTokenERC20,
        data: abi.encode(AsForeignTokenERC20({foreignID: foreignID, amount: amount}))
    });
}

// V2 Command Params

// Payload for Upgrade
struct UpgradeParams {
    // The address of the implementation contract
    address impl;
    // Codehash of the new implementation contract.
    bytes32 implCodeHash;
    // Parameters used to upgrade storage of the gateway
    bytes initParams;
}

// Payload for SetOperatingMode instruction
struct SetOperatingModeParams {
    /// The new operating mode
    OperatingMode mode;
}

// Payload for NativeTokenUnlock instruction
struct UnlockNativeTokenParams {
    // Token address
    address token;
    // Recipient address
    address recipient;
    // Amount to unlock
    uint128 amount;
}

// Payload for RegisterForeignToken
struct RegisterForeignTokenParams {
    /// @dev The token ID (hash of stable location id of token)
    bytes32 foreignTokenID;
    /// @dev The name of the token
    string name;
    /// @dev The symbol of the token
    string symbol;
    /// @dev The decimal of the token
    uint8 decimals;
}

// Payload for MintForeignTokenParams instruction
struct MintForeignTokenParams {
    // Foreign token ID
    bytes32 foreignTokenID;
    // Recipient address
    address recipient;
    // Amount to mint
    uint128 amount;
}

// Payload for CallContractParams instruction
struct CallContractParams {
    // target contract
    address target;
    // Call data
    bytes data;
    // Ether value
    uint256 value;
}

enum Network {
    Polkadot
}
"
    },
    "src/Upgrade.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

import {ERC1967} from "./utils/ERC1967.sol";
import {Call} from "./utils/Call.sol";
import {Address} from "./utils/Address.sol";
import {IInitializable} from "./interfaces/IInitializable.sol";
import {IUpgradable} from "./interfaces/IUpgradable.sol";

/// @dev Upgrades implementation contract
library Upgrade {
    using Address for address;

    function upgrade(address impl, bytes32 implCodeHash, bytes memory initializerParams)
        external
    {
        // Verify that the implementation is actually a contract
        if (!impl.isContract()) {
            revert IUpgradable.InvalidContract();
        }

        // As a sanity check, ensure that the codehash of implementation contract
        // matches the codehash in the upgrade proposal
        if (impl.codehash != implCodeHash) {
            revert IUpgradable.InvalidCodeHash();
        }

        // Update the proxy with the address of the new implementation
        ERC1967.store(impl);

        // Call the initializer
        (bool success, bytes memory returndata) =
            impl.delegatecall(abi.encodeCall(IInitializable.initialize, initializerParams));
        Call.verifyResult(success, returndata);

        emit IUpgradable.Upgraded(impl);
    }
}
"
    },
    "src/interfaces/IInitializable.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

/**
 * @title Initialization of gateway logic contracts
 */
interface IInitializable {
    function initialize(bytes calldata data) external;
}
"
    },
    "src/interfaces/IUpgradable.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

interface IUpgradable {
    // The new implementation address is a not a contract
    error InvalidContract();
    // The supplied codehash does not match the new implementation codehash
    error InvalidCodeHash();

    // The implementation contract was upgraded
    event Upgraded(address indexed implementation);

    function implementation() external view returns (address);
}
"
    },
    "src/utils/ERC1967.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.28;

/// @title Minimal implementation of ERC1967 storage slot
library ERC1967 {
    // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
    bytes32 public constant _IMPLEMENTATION_SLOT =
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    function load() internal view returns (address implementation) {
        assembly {
            implementation := sload(_IMPLEMENTATION_SLOT)
        }
    }

    function store(address implementation) internal {
        assembly {
            sstore(_IMPLEMENTATION_SLOT, implementation)
        }
    }
}
"
    },
    "src/utils/Address.sol": {
      "content": "// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2023 Axelar Network
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>

pragma solidity 0.8.28;

library Address {
    // Checks whether `account` is a contract
    function isContract(address account) internal view returns (bool) {
        // https://eips.ethereum.org/EIPS/eip-1052
        // keccak256('') == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
        return account.codehash != bytes32(0)
            && account.codehash != 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
    }
}
"
    },
    "src/utils/SafeTransfer.sol": {
      "content": "// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2023 Axelar Network
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>

pragma solidity 0.8.28;

import {IERC20} from "../interfaces/IERC20.sol";

error TokenTransferFailed();
error NativeTransferFailed();

library SafeTokenCall {
    function safeCall(IERC20 token, bytes memory callData) internal {
        (bool success, bytes memory returnData) = address(token).call(callData);
        bool transferred =
            success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));
        if (!transferred || address(token).code.length == 0) {
            revert TokenTransferFailed();
        }
    }
}

library SafeTokenTransfer {
    function safeTransfer(IERC20 token, address receiver, uint256 amount) internal {
        SafeTokenCall.safeCall(token, abi.encodeCall(IERC20.transfer, (receiver, amount)));
    }
}

library SafeTokenTransferFrom {
    function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
        SafeTokenCall.safeCall(token, abi.encodeCall(IERC20.transferFrom, (from, to, amount)));
    }
}

library SafeNativeTransfer {
    function safeNativeTransfer(address payable receiver, uint256 amount) internal {
        bool success;
        assembly {
            success := call(gas(), receiver, amount, 0, 0, 0, 0)
        }
        if (!success) {
            revert NativeTransferFailed();
        }
    }
}
"
    },
    "src/utils/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2023 OpenZeppelin
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
// Code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol
pragma solidity 0.8.28;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero

    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }

    /**
     * @dev Safely adds two unsigned 16-bit integers, preventing overflow by saturating to max uint16.
     */
    function saturatingAdd(uint16 a, uint16 b) internal pure returns (uint16) {
        unchecked {
            uint16 c =

Tags:
ERC20, Proxy, Mintable, Burnable, Swap, Yield, Voting, Upgradeable, Factory|addr:0x8a887783e945233d51881e06835ec78a8b575ece|verified:true|block:23427008|tx:0xd5ce1db69941462b73544ce19f3c3630a1897ae1ae815bc8fe496f85b7227d72|first_check:1758726135

Submitted on: 2025-09-24 17:02:19

Comments

Log in to comment.

No comments yet.