Vault

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/vaultSide/Vault.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import "../interface/IVault.sol";
import "../interface/IVaultCrossChainManager.sol";
import "../library/Utils.sol";
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol";
import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "openzeppelin-contracts/contracts/utils/math/SafeCast.sol";
import "openzeppelin-contracts/contracts/utils/Address.sol";
import "../interface/cctpv2/ITokenMessengerV2.sol";
import "../interface/cctpv2/IMessageTransmitterV2.sol";
import "../interface/IProtocolVault.sol";
import "../library/DelegateSwapSignature.sol";
import "../oz5Revised/ReentrancyGuardRevised.sol";
import "../oz5Revised/AccessControlRevised.sol";
import "../library/Version.sol";
/// @title Vault contract
/// @author Orderly_Rubick, Orderly_Zion
/// @notice Vault is responsible for saving user's erc20 token.
/// EACH CHAIN SHOULD HAVE ONE Vault CONTRACT.
/// User can deposit erc20 (USDC) from Vault.
/// Only crossChainManager can approve withdraw request.

contract Vault is
    IVault,
    PausableUpgradeable,
    OwnableUpgradeable,
    ReentrancyGuardRevised,
    AccessControlRevised,
    Version
{
    using EnumerableSet for EnumerableSet.Bytes32Set;
    using SafeERC20 for IERC20;
    using Address for address payable;
    using SafeCast for uint256;
    // The cross-chain manager address on Vault side

    address public crossChainManagerAddress;
    // An incrasing deposit id / nonce on Vault side
    uint64 public depositId;

    // A set to record the hash value of all allowed brokerIds  // brokerHash = keccak256(abi.encodePacked(brokerId))
    EnumerableSet.Bytes32Set private allowedBrokerSet;
    // A set to record the hash value of all allowed tokens  // tokenHash = keccak256(abi.encodePacked(tokenSymbol))
    EnumerableSet.Bytes32Set private allowedTokenSet;
    // A mapping from tokenHash to token contract address
    mapping(bytes32 => address) public allowedToken;
    // A flag to indicate if deposit fee is enabled
    bool public depositFeeEnabled;

    // https://developers.circle.com/stablecoin/docs/cctp-protocol-contract#tokenmessenger-mainnet
    // TokenMessager for CCTP
    address public tokenMessengerContract;
    // MessageTransmitterContract for CCTP
    address public messageTransmitterContract;

    // A set to record deposit limit for each token. 0 means unlimited
    mapping(address => uint256) public tokenAddress2DepositLimit;

    // Protocol Vault address
    IProtocolVault public protocolVault;

    // EnumerableSet for rebalance enable tokens
    EnumerableSet.Bytes32Set private _rebalanceEnableTokenSet;

    /*=============== Native Token ===============*/

    // Native token hash
    bytes32 public nativeTokenHash;

    // Native token deposit limit
    uint256 public nativeTokenDepositLimit;

    /*=============== Delegate  Swap ===============*/

    // Submitted Swap
    EnumerableSet.Bytes32Set private _submittedSwapSet;
    // Swap Operator Address
    address public swapOperator;
    // Swap Signer Address
    address public swapSigner;


    /*=============== CCTP Config ===============*/
    uint256 public cctpMaxFee;
    uint32 public cctpFinalityThreshold;
    

    /* ================ Role ================ */

    bytes32 public constant SYMBOL_MANAGER_ROLE = keccak256("ORDERLY_MANAGER_SYMBOL_MANAGER_ROLE");

    bytes32 public constant BROKER_MANAGER_ROLE = keccak256("ORDERLY_MANAGER_BROKER_MANAGER_ROLE");

    /*=============== Modifiers ===============*/

    /// @notice onlyRoleOrOwner
    modifier onlyRoleOrOwner(bytes32 role) {
        if (!hasRole(role, msg.sender) && msg.sender != owner()) {
            revert AccessControlUnauthorizedAccount(msg.sender, role);
        }
        _;
    }

    /// @notice Require only swapOperator can call
    modifier onlySwapOperator() {
        require(msg.sender == swapOperator, "Vault: Only swap operator can call");
        _;
    }

    /// @notice Require only cross-chain manager can call
    modifier onlyCrossChainManager() {
        if (msg.sender != crossChainManagerAddress) revert OnlyCrossChainManagerCanCall();
        _;
    }

    /// @notice check non-zero address
    modifier nonZeroAddress(address _address) {
        if (_address == address(0)) revert AddressZero();
        _;
    }

    /*=============== Constructor ===============*/

    constructor() {
        _disableInitializers();
    }

    /*=============== Initializer ===============*/

    function initialize() external override initializer {
        __Ownable_init();
        __Pausable_init();
        __ReentrancyGuard_init();
    }

    /*=============== Setters ===============*/

    /// @notice Sets broker status via cross-chain message from ledger
    /// @dev Only callable by the cross-chain manager, validates chain ID
    /// @param data The SetBrokerData containing broker information and status
    function setBrokerFromLedger(EventTypes.SetBrokerData calldata data) external override onlyCrossChainManager {
        // Chain ID validation (defense in depth) - using Solidity's built-in block.chainid
        require(data.dstChainId == block.chainid, "Vault: dstChainId mismatch");
        
        bool currentStatus = allowedBrokerSet.contains(data.brokerHash);
        
        if (data.allowed) {
            // Add broker operation
            if (currentStatus) {
                // Broker already exists, emit already set event
                emit SetBrokerFromLedgerAlreadySet(data.brokerHash, data.dstChainId, data.allowed);
                return;
            }
            // Add the broker using EnumerableSet
            allowedBrokerSet.add(data.brokerHash);
        } else {
            // Remove broker operation
            if (!currentStatus) {
                // Broker doesn't exist, emit already set event (broker already not present)
                emit SetBrokerFromLedgerAlreadySet(data.brokerHash, data.dstChainId, data.allowed);
                return;
            }
            // Remove the broker using EnumerableSet
            allowedBrokerSet.remove(data.brokerHash);
        }
        
        emit SetBrokerFromLedgerSuccess(data.brokerHash, data.dstChainId, data.allowed);
    }

    /// @notice Change crossChainManager address
    function setCrossChainManager(address _crossChainManagerAddress)
        external
        override
        onlyOwner
        nonZeroAddress(_crossChainManagerAddress)
    {
        emit ChangeCrossChainManager(crossChainManagerAddress, _crossChainManagerAddress);
        crossChainManagerAddress = _crossChainManagerAddress;
    }

    /// @notice Set deposit limit for a token
    function setDepositLimit(address _tokenAddress, uint256 _limit)
        external
        override
        onlyRoleOrOwner(SYMBOL_MANAGER_ROLE)
    {
        tokenAddress2DepositLimit[_tokenAddress] = _limit;
        emit ChangeDepositLimit(_tokenAddress, _limit);
    }

    /// @notice Set protocolVault address
    function setProtocolVaultAddress(address _protocolVaultAddress)
        external
        override
        onlyOwner
        nonZeroAddress(_protocolVaultAddress)
    {
        emit SetProtocolVaultAddress(address(protocolVault), _protocolVaultAddress);
        protocolVault = IProtocolVault(_protocolVaultAddress);
    }

    /// @notice Add contract address for an allowed token given the tokenHash
    /// @dev This function is only called when changing allow status for a token, not for initializing
    function setAllowedToken(bytes32 _tokenHash, bool _allowed) external override onlyOwner {
        bool succ = false;
        if (_allowed) {
            // require tokenAddress exist, except for native token
            if (allowedToken[_tokenHash] == address(0) && _tokenHash != nativeTokenHash) revert AddressZero();
            succ = allowedTokenSet.add(_tokenHash);
        } else {
            succ = allowedTokenSet.remove(_tokenHash);
        }
        if (!succ) revert EnumerableSetError();
        emit SetAllowedToken(_tokenHash, _allowed);
    }

    function setRebalanceEnableToken(bytes32 _tokenHash, bool _allowed) external override onlyOwner {
        bool succ = false;
        if (_allowed) {
            succ = _rebalanceEnableTokenSet.add(_tokenHash);
        } else {
            succ = _rebalanceEnableTokenSet.remove(_tokenHash);
        }
        if (!succ) revert EnumerableSetError();
        emit SetRebalanceEnableToken(_tokenHash, _allowed);
    }

    function getAllRebalanceEnableToken() external view returns (bytes32[] memory) {
        return _rebalanceEnableTokenSet.values();
    }

    /// @notice Add the hash value for an allowed brokerId
    function setAllowedBroker(bytes32 _brokerHash, bool _allowed) external override onlyRoleOrOwner(BROKER_MANAGER_ROLE) {
        bool succ = false;
        if (_allowed) {
            succ = allowedBrokerSet.add(_brokerHash);
        } else {
            succ = allowedBrokerSet.remove(_brokerHash);
        }
        if (!succ) revert EnumerableSetError();
        emit SetAllowedBroker(_brokerHash, _allowed);
    }

    /// @notice Set native token hash
    function setNativeTokenHash(bytes32 _nativeTokenHash) external override onlyOwner {
        nativeTokenHash = _nativeTokenHash;
    }

    /// @notice Set native token deposit limit
    function setNativeTokenDepositLimit(uint256 _nativeTokenDepositLimit) external override onlyRoleOrOwner(SYMBOL_MANAGER_ROLE)
    {
        nativeTokenDepositLimit = _nativeTokenDepositLimit;
    }

    /// @notice Change the token address for an allowed token, used when a new token is added
    /// @dev maybe should called `addTokenAddressAndAllow`, because it's for initializing
    function changeTokenAddressAndAllow(bytes32 _tokenHash, address _tokenAddress)
        external
        override
        onlyOwner
        nonZeroAddress(_tokenAddress)
    {
        allowedToken[_tokenHash] = _tokenAddress;
        allowedTokenSet.add(_tokenHash); // ignore returns here
        emit ChangeTokenAddressAndAllow(_tokenHash, _tokenAddress);
    }

    /// @notice Check if the given tokenHash is allowed on this Vault
    function getAllowedToken(bytes32 _tokenHash) public view override returns (address) {
        if (allowedTokenSet.contains(_tokenHash)) {
            return allowedToken[_tokenHash];
        } else {
            return address(0);
        }
    }

    /// @notice Check if the brokerHash is allowed on this Vault
    function getAllowedBroker(bytes32 _brokerHash) public view override returns (bool) {
        return allowedBrokerSet.contains(_brokerHash);
    }

    /// @notice Get all allowed tokenHash from this Vault
    function getAllAllowedToken() public view override returns (bytes32[] memory) {
        return allowedTokenSet.values();
    }

    /// @notice Get all allowed brokerIds hash from this Vault
    function getAllAllowedBroker() public view override returns (bytes32[] memory) {
        return allowedBrokerSet.values();
    }

    /*=============== Deposit ===============*/

    /// @notice The function to receive user deposit, VaultDepositFE type is defined in VaultTypes.sol
    function deposit(VaultTypes.VaultDepositFE calldata data) public payable override whenNotPaused {
        if (data.tokenHash == nativeTokenHash) {
            _ethDeposit(msg.sender, data);
        } else {
            _deposit(msg.sender, data);
        }
    }

    /// @notice The function to allow users to deposit on behalf of another user, the receiver is the user who will receive the deposit
    function depositTo(address receiver, VaultTypes.VaultDepositFE calldata data)
        public
        payable
        override
        whenNotPaused
    {
        if (data.tokenHash == nativeTokenHash) {
            _ethDeposit(receiver, data);
        } else {
            _deposit(receiver, data);
        }
    }

    /// @notice The function to query layerzero fee from CrossChainManager contract
    function getDepositFee(address receiver, VaultTypes.VaultDepositFE calldata data)
        public
        view
        override
        whenNotPaused
        returns (uint256)
    {
        _validateDeposit(receiver, data);
        VaultTypes.VaultDeposit memory depositData = VaultTypes.VaultDeposit(
            data.accountId, receiver, data.brokerHash, data.tokenHash, data.tokenAmount, depositId + 1
        );
        return (IVaultCrossChainManager(crossChainManagerAddress).getDepositFee(depositData));
    }

    /// @notice The function to enable/disable deposit fee
    function enableDepositFee(bool _enabled) public override onlyOwner whenNotPaused {
        depositFeeEnabled = _enabled;
    }

    /// @notice The function to call deposit of CCManager contract
    function _deposit(address receiver, VaultTypes.VaultDepositFE calldata data) internal {
        _validateDeposit(receiver, data);
        // avoid reentrancy, so `transferFrom` token at the beginning
        IERC20 tokenAddress = IERC20(allowedToken[data.tokenHash]);
        // check deposit limit
        /// @notice Be aware that we track the balance of the token in the contract, should be better track internal token deposit
        /// @notice Be aware that becuase of the async process of deposit & withdraw, the limit may be broken. So, it's a soft limit, not a hard limit
        if (
            tokenAddress2DepositLimit[address(tokenAddress)] != 0
                && data.tokenAmount + tokenAddress.balanceOf(address(this))
                    > tokenAddress2DepositLimit[address(tokenAddress)]
        ) {
            revert DepositExceedLimit();
        }
        // avoid non-standard ERC20 tranferFrom bug
        tokenAddress.safeTransferFrom(msg.sender, address(this), data.tokenAmount);
        // cross-chain tx to ledger
        VaultTypes.VaultDeposit memory depositData = VaultTypes.VaultDeposit(
            data.accountId, receiver, data.brokerHash, data.tokenHash, data.tokenAmount, _newDepositId()
        );
        // if deposit fee is enabled, user should pay fee in native token and the msg.value will be forwarded to CrossChainManager to pay for the layerzero cross-chain fee
        if (depositFeeEnabled) {
            if (msg.value == 0) revert ZeroDepositFee();
            IVaultCrossChainManager(crossChainManagerAddress).depositWithFeeRefund{value: msg.value}(
                msg.sender, depositData
            );
        } else {
            IVaultCrossChainManager(crossChainManagerAddress).deposit(depositData);
        }
        emit AccountDepositTo(data.accountId, receiver, depositId, data.tokenHash, data.tokenAmount);
    }

    function _ethDeposit(address receiver, VaultTypes.VaultDepositFE calldata data) internal {
        _validateDeposit(receiver, data);

        uint128 nativeDepositAmount = msg.value.toUint128();

        if (nativeDepositAmount < data.tokenAmount) revert NativeTokenDepositAmountMismatch();
        // check native token deposit limit
        if (
            nativeTokenDepositLimit != 0 && (data.tokenAmount + address(this).balance - nativeDepositAmount) > nativeTokenDepositLimit
        ) {
            revert DepositExceedLimit();
        }
        // cross-chain tx to ledger
        VaultTypes.VaultDeposit memory depositData = VaultTypes.VaultDeposit(
            data.accountId, receiver, data.brokerHash, data.tokenHash, data.tokenAmount, _newDepositId()
        );

        // cross-chain fee
        uint256 crossChainFee = nativeDepositAmount - data.tokenAmount;

        // if deposit fee is enabled, user should pay fee in native token and the msg.value will be forwarded to CrossChainManager to pay for the layerzero cross-chain fee
        if (depositFeeEnabled) {
            if (crossChainFee == 0) revert ZeroDepositFee();
            IVaultCrossChainManager(crossChainManagerAddress).depositWithFeeRefund{value: crossChainFee}(
                msg.sender, depositData
            );
        } else {
            IVaultCrossChainManager(crossChainManagerAddress).deposit(depositData);
        }
        emit AccountDepositTo(data.accountId, receiver, depositId, data.tokenHash, data.tokenAmount);
    }

    /// @notice The function to validate deposit data
    function _validateDeposit(address receiver, VaultTypes.VaultDepositFE calldata data) internal view
    {
        // check if tokenHash and brokerHash are allowed
        if (!allowedTokenSet.contains(data.tokenHash)) revert TokenNotAllowed();
        if (!allowedBrokerSet.contains(data.brokerHash)) revert BrokerNotAllowed();
        // check if accountId = keccak256(abi.encodePacked(brokerHash, receiver))
        if (!Utils.validateExtendedAccountId(address(protocolVault), data.accountId, data.brokerHash, receiver)) {
            revert AccountIdInvalid();
        }

        // check if tokenAmount > 0
        if (data.tokenAmount == 0) revert ZeroDeposit();
    }

    function _ethWithdraw(address receiver, uint128 amount) internal {
        payable(receiver).sendValue(amount);
    }

    /*=============== Withdraw ===============*/

    /// @notice user withdraw
    function withdraw(VaultTypes.VaultWithdraw calldata data) public override onlyCrossChainManager whenNotPaused {
        // send cross-chain tx to ledger
        IVaultCrossChainManager(crossChainManagerAddress).withdraw(data);

        require(data.tokenAmount > data.fee, "withdraw: fee is greater than token amount");

        uint128 amount = data.tokenAmount - data.fee;

        if (data.tokenHash == nativeTokenHash) {
            _ethWithdraw(data.receiver, amount);
        } else {
            // avoid reentrancy, so `transfer` token at the end
            IERC20 tokenAddress = IERC20(allowedToken[data.tokenHash]);
            require(tokenAddress.balanceOf(address(this)) >= amount, "withdraw: insufficient balance");
            // avoid revert if transfer to zero address or blacklist.
            /// @notice This check condition should always be true because cc promise that
            if (!_validReceiver(data.receiver, address(tokenAddress))) {
                emit WithdrawFailed(address(tokenAddress), data.receiver, amount);
            } else {
                tokenAddress.safeTransfer(data.receiver, amount);
            }
        }
        // emit withdraw event
        emit AccountWithdraw(
            data.accountId,
            data.withdrawNonce,
            data.brokerHash,
            data.sender,
            data.receiver,
            data.tokenHash,
            data.tokenAmount,
            data.fee
        );
    }

    /*=============== Withdraw2Contract ===============*/

    /// @notice withdraw to another contract by calling the contract's deposit function
    function withdraw2Contract(VaultTypes.VaultWithdraw2Contract calldata data)
        external
        onlyCrossChainManager
        whenNotPaused
    {
        VaultTypes.VaultWithdraw memory vaultWithdrawData = VaultTypes.VaultWithdraw({
            accountId: data.accountId,
            brokerHash: data.brokerHash,
            tokenHash: data.tokenHash,
            tokenAmount: data.tokenAmount,
            fee: data.fee,
            sender: data.sender,
            receiver: data.receiver,
            withdrawNonce: data.withdrawNonce
        });
        // send cross-chain tx to ledger
        IVaultCrossChainManager(crossChainManagerAddress).withdraw(vaultWithdrawData);

        require(data.tokenAmount > data.fee, "withdraw2Contract: fee is greater than token amount");

        uint128 amount = data.tokenAmount - data.fee;

        if (data.tokenHash == nativeTokenHash) {
            _ethWithdraw(data.receiver, amount);
        } else {
            // avoid reentrancy, so `transfer` token at the end
            IERC20 tokenAddress = IERC20(allowedToken[data.tokenHash]);
            require(tokenAddress.balanceOf(address(this)) >= amount, "Vault: insufficient balance");
            // avoid revert if transfer to zero address or blacklist.
            /// @notice This check condition should always be true because cc promise that
            /// @notice But in some extreme cases (e.g. usdc contract pause) it will revert, devs should mannual fix it
            if (!_validReceiver(data.receiver, address(tokenAddress))) {
                emit WithdrawFailed(address(tokenAddress), data.receiver, amount);
            } else {
                // because we check type at the beginning, so we can safely check the type here
                if (data.vaultType == VaultTypes.VaultEnum.ProtocolVault) {
                    tokenAddress.safeApprove(data.receiver, amount);
                    protocolVault.depositFromStrategy(data.clientId, address(tokenAddress), amount);
                } else if (data.vaultType == VaultTypes.VaultEnum.Ceffu) {
                    tokenAddress.safeTransfer(data.receiver, amount);
                }
            }
        }
        // emit withdraw event
        emit AccountWithdraw(
            data.accountId,
            data.withdrawNonce,
            data.brokerHash,
            data.sender,
            data.receiver,
            data.tokenHash,
            data.tokenAmount,
            data.fee
        );
    }

    /// @notice validate if the receiver address is zero or in the blacklist
    function _validReceiver(address _receiver, address _token) internal view returns (bool) {
        if (_receiver == address(0)) {
            return false;
        } else if (_isBlacklisted(_receiver, _token)) {
            return false;
        } else {
            return true;
        }
    }

    /// @notice check if the receiver is in the blacklist in case the token contract has the blacklist function
    function _isBlacklisted(address _receiver, address _token) internal view returns (bool) {
        bytes memory data = abi.encodeWithSignature("isBlacklisted(address)", _receiver);
        (bool success, bytes memory result) = _token.staticcall(data);
        if (success) {
            return abi.decode(result, (bool));
        } else {
            return false;
        }
    }

    function delegateSigner(VaultTypes.VaultDelegate calldata data) public override {
        if ((msg.sender).code.length == 0) revert ZeroCodeLength();
        if ((data.delegateSigner).code.length != 0) revert NotZeroCodeLength();
        if (!allowedBrokerSet.contains(data.brokerHash)) revert BrokerNotAllowed();

        // emit delegate event
        emit AccountDelegate(msg.sender, data.brokerHash, data.delegateSigner, block.chainid, block.number);
    }

    /// @notice Update the depositId
    function _newDepositId() internal returns (uint64) {
        return ++depositId;
    }

    function emergencyPause() public whenNotPaused onlyOwner {
        _pause();
    }

    function emergencyUnpause() public whenPaused onlyOwner {
        _unpause();
    }

    function setTokenMessengerContract(address _tokenMessengerContract)
        public
        override
        onlyOwner
        nonZeroAddress(_tokenMessengerContract)
    {
        tokenMessengerContract = _tokenMessengerContract;
    }

    function setRebalanceMessengerContract(address _rebalanceMessengerContract)
        public
        override
        onlyOwner
        nonZeroAddress(_rebalanceMessengerContract)
    {
        messageTransmitterContract = _rebalanceMessengerContract;
    }

    function setCCTPConfig(uint256 _maxFee, uint32 _finalityThreshold) public onlyOwner {
        
        cctpMaxFee = _maxFee;
        cctpFinalityThreshold = _finalityThreshold;
    }

    function rebalanceBurn(RebalanceTypes.RebalanceBurnCCData calldata data) external override onlyCrossChainManager {
        /// Check if the token is allowed to be burned
        address burnToken = allowedToken[data.tokenHash];
        if (burnToken == address(0)) revert AddressZero();
        if (!_rebalanceEnableTokenSet.contains(data.tokenHash)) revert NotRebalanceEnableToken();

        /// Approve the token to be burned
        IERC20(burnToken).approve(tokenMessengerContract, data.amount);
        try ITokenMessengerV2(tokenMessengerContract).depositForBurn(
            data.amount, data.dstDomain, Utils.toBytes32(data.dstVaultAddress), burnToken, Utils.toBytes32(data.dstVaultAddress), cctpMaxFee,cctpFinalityThreshold
        ) {
            // send succ cross-chain tx to ledger
            // rebalanceId, amount, tokenHash, burnChainId, mintChainId | true
            IVaultCrossChainManager(crossChainManagerAddress).burnFinish(
                RebalanceTypes.RebalanceBurnCCFinishData({
                    rebalanceId: data.rebalanceId,
                    amount: data.amount,
                    tokenHash: data.tokenHash,
                    burnChainId: data.burnChainId,
                    mintChainId: data.mintChainId,
                    success: true
                })
            );
        } catch {
            // send fail cross-chain tx to ledger
            // rebalanceId, amount, tokenHash, burnChainId, mintChainId | false
            IVaultCrossChainManager(crossChainManagerAddress).burnFinish(
                RebalanceTypes.RebalanceBurnCCFinishData({
                    rebalanceId: data.rebalanceId,
                    amount: data.amount,
                    tokenHash: data.tokenHash,
                    burnChainId: data.burnChainId,
                    mintChainId: data.mintChainId,
                    success: false
                })
            );
        }
    }

    function rebalanceMint(RebalanceTypes.RebalanceMintCCData calldata data) external override onlyCrossChainManager {
        try IMessageTransmitterV2(messageTransmitterContract).receiveMessage(data.messageBytes, data.messageSignature) {
            // send succ cross-chain tx to ledger
            // rebalanceId, amount, tokenHash, burnChainId, mintChainId | true
            IVaultCrossChainManager(crossChainManagerAddress).mintFinish(
                RebalanceTypes.RebalanceMintCCFinishData({
                    rebalanceId: data.rebalanceId,
                    amount: data.amount,
                    tokenHash: data.tokenHash,
                    burnChainId: data.burnChainId,
                    mintChainId: data.mintChainId,
                    success: true
                })
            );
        } catch Error(string memory reason) {
            // The method `receiveMessage` is permissionless, so it may fail due to others call it first
            // So if the reason is "Nonce already used", we treat it as success
            /// @notice This is still a bad practice, because maybe more errors will be treated as success (e.g. cctp contract pause & call it & unpause)
            /// But those corner cases are rare, and we can finally fix it
            string memory expectedReason = "Nonce already used";
            bool success = keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked(expectedReason));
            IVaultCrossChainManager(crossChainManagerAddress).mintFinish(
                RebalanceTypes.RebalanceMintCCFinishData({
                    rebalanceId: data.rebalanceId,
                    amount: data.amount,
                    tokenHash: data.tokenHash,
                    burnChainId: data.burnChainId,
                    mintChainId: data.mintChainId,
                    success: success
                })
            );
        }
    }

    /*=================================================
     =============== Delegate Swap ===============
     =================================================*/

    /// @notice Set the operator for the Swap
    function setSwapOperator(address _swapOperator) public override onlyOwner {
        swapOperator = _swapOperator;
    }

    /// @notice Set the signer for the Swap
    function setSwapSigner(address _swapSigner) public override onlyOwner {
        swapSigner = _swapSigner;
    }


    /// @notice Get all submitted swaps
    function getSubmittedSwaps() public view returns (bytes32[] memory) {
        return _submittedSwapSet.values();
    }

    /// @notice If submittedSwapSet contains the tradeId, return true
    function isSwapSubmitted(bytes32 tradeId) public view returns (bool) {
        return _submittedSwapSet.contains(tradeId);
    }

    function _verifySwapSignature(VaultTypes.DelegateSwap calldata data) internal view {
        // Verify Signature
        if (!DelegateSwapSignature.validateDelegateSwapSignature(swapSigner, data)) revert InvalidSwapSignature();
    }

    function _validateSwap(VaultTypes.DelegateSwap calldata data) internal view {
        // require nonce == swapNonce
        if (_submittedSwapSet.contains(data.tradeId)) revert SwapAlreadySubmitted();

        // Verify that the token is allowed
        bytes32 inTokenHash = data.inTokenHash;
        if (!allowedTokenSet.contains(inTokenHash)) revert TokenNotAllowed();

        // Verify that the owner has enough tokens
        if (inTokenHash != nativeTokenHash) {
            // ERC20 token case
            address tokenAddress = allowedToken[inTokenHash];
            if (tokenAddress == address(0)) revert InvalidTokenAddress();
        }

        // Verify Signature
        _verifySwapSignature(data);
    }

    /*=============== Delegate Swap ===============*/

    function delegateSwap(VaultTypes.DelegateSwap calldata data)
        external
        override
        whenNotPaused
        onlySwapOperator
        nonReentrant
    {
        _validateSwap(data);
        _submittedSwapSet.add(data.tradeId);

        // Execute the transaction
        // Verify that the owner has enough tokens
        if (data.inTokenHash != nativeTokenHash) {
            // Approve the token to be spent
            address tokenAddress = allowedToken[data.inTokenHash];
            IERC20 token = IERC20(tokenAddress);
            token.safeApprove(data.to, data.inTokenAmount);
        }

        uint256 value = 0;
        if (data.inTokenHash == nativeTokenHash) {
            value = data.value;
        }

        // Execute the transaction
        (bool success, bytes memory result) = data.to.call{value: value}(data.swapCalldata);
        if (!success) {
            assembly {
                revert(add(result, 0x20), mload(result))
            }
        }

        // Revoke the approval
        if (data.inTokenHash != nativeTokenHash) {
            address tokenAddress = allowedToken[data.inTokenHash];
            IERC20 token = IERC20(tokenAddress);
            token.safeApprove(data.to, 0);
        }

        emit DelegateSwapExecuted(data.tradeId, data.inTokenHash, data.inTokenAmount, data.to, data.value);
    }

    /* ================ Override AccessControlRevised To Simplify Access Control ================ */

    /// @notice Override grantRole
    function grantRole(bytes32 role, address account) public override onlyOwner {
        _grantRole(role, account);
    }

    /// @notice Override revokeRole
    function revokeRole(bytes32 role, address account) public override onlyOwner {
        _revokeRole(role, account);
    }
}
"
    },
    "src/interface/IVault.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.18;

import "./../library/types/VaultTypes.sol";
import "./../library/types/RebalanceTypes.sol";
import "./../library/types/EventTypes.sol";

interface IVault {
    error OnlyCrossChainManagerCanCall();
    error AccountIdInvalid();
    error TokenNotAllowed();
    error InvalidTokenAddress();
    error BrokerNotAllowed();
    error BalanceNotEnough(uint256 balance, uint128 amount);
    error AddressZero();
    error EnumerableSetError();
    error ZeroDepositFee();
    error ZeroDeposit();
    error ZeroCodeLength();
    error NotZeroCodeLength();
    error DepositExceedLimit();
    error NativeTokenDepositAmountMismatch();
    error NotRebalanceEnableToken();
    error SwapAlreadySubmitted();
    error InvalidSwapSignature();
    error CeffuAddressMismatch(address want, address got);

    // @deprecated
    event AccountDeposit(
        bytes32 indexed accountId,
        address indexed userAddress,
        uint64 indexed depositNonce,
        bytes32 tokenHash,
        uint128 tokenAmount
    );

    event AccountDepositTo(
        bytes32 indexed accountId,
        address indexed userAddress,
        uint64 indexed depositNonce,
        bytes32 tokenHash,
        uint128 tokenAmount
    );

    event AccountWithdraw(
        bytes32 indexed accountId,
        uint64 indexed withdrawNonce,
        bytes32 brokerHash,
        address sender,
        address receiver,
        bytes32 tokenHash,
        uint128 tokenAmount,
        uint128 fee
    );

    event AccountDelegate(
        address indexed delegateContract,
        bytes32 indexed brokerHash,
        address indexed delegateSigner,
        uint256 chainId,
        uint256 blockNumber
    );

    event SetAllowedToken(bytes32 indexed _tokenHash, bool _allowed);
    event SetAllowedBroker(bytes32 indexed _brokerHash, bool _allowed);
    event ChangeTokenAddressAndAllow(bytes32 indexed _tokenHash, address _tokenAddress);
    event ChangeCrossChainManager(address oldAddress, address newAddress);
    event ChangeDepositLimit(address indexed _tokenAddress, uint256 _limit);
    event WithdrawFailed(address indexed token, address indexed receiver, uint256 amount);
    event SetRebalanceEnableToken(bytes32 indexed _tokenHash, bool _allowed);
    event DelegateSwapExecuted(bytes32 indexed tradeId, bytes32 inTokenHash, uint256 inTokenAmount, address to, uint256 value);

    event SetProtocolVaultAddress(address _oldProtocolVaultAddress, address _newProtocolVaultAddress);
    event SetCeffuAddress(address _oldCeffuAddress, address _newCeffuAddress);

    // SetBroker from ledger events
    event SetBrokerFromLedgerAlreadySet(bytes32 indexed brokerHash, uint256 dstChainId, bool allowed);
    event SetBrokerFromLedgerSuccess(bytes32 indexed brokerHash, uint256 dstChainId, bool allowed);

    function initialize() external;

    function deposit(VaultTypes.VaultDepositFE calldata data) external payable;
    function depositTo(address receiver, VaultTypes.VaultDepositFE calldata data) external payable;
    function getDepositFee(address recevier, VaultTypes.VaultDepositFE calldata data) external view returns (uint256);
    function enableDepositFee(bool _enabled) external;
    function withdraw(VaultTypes.VaultWithdraw calldata data) external;
    function delegateSigner(VaultTypes.VaultDelegate calldata data) external;
    function withdraw2Contract(VaultTypes.VaultWithdraw2Contract calldata data) external;

    // CCTP: functions for receive rebalance msg
    function rebalanceMint(RebalanceTypes.RebalanceMintCCData calldata data) external;
    function rebalanceBurn(RebalanceTypes.RebalanceBurnCCData calldata data) external;
    function setTokenMessengerContract(address _tokenMessengerContract) external;
    function setRebalanceMessengerContract(address _rebalanceMessengerContract) external;

    // admin call
    function setCrossChainManager(address _crossChainManagerAddress) external;
    function setDepositLimit(address _tokenAddress, uint256 _limit) external;
    function setProtocolVaultAddress(address _protocolVaultAddress) external;
    function emergencyPause() external;
    function emergencyUnpause() external;

    // whitelist
    function setAllowedToken(bytes32 _tokenHash, bool _allowed) external;
    function setAllowedBroker(bytes32 _brokerHash, bool _allowed) external;
    function setNativeTokenHash(bytes32 _nativeTokenHash) external;
    function setNativeTokenDepositLimit(uint256 _nativeTokenDepositLimit) external;
    function setRebalanceEnableToken(bytes32 _tokenHash, bool _allowed) external;
    function changeTokenAddressAndAllow(bytes32 _tokenHash, address _tokenAddress) external;
    function getAllowedToken(bytes32 _tokenHash) external view returns (address);
    function getAllowedBroker(bytes32 _brokerHash) external view returns (bool);
    function getAllAllowedToken() external view returns (bytes32[] memory);
    function getAllAllowedBroker() external view returns (bytes32[] memory);
    function getAllRebalanceEnableToken() external view returns (bytes32[] memory);

    // cross-chain broker management
    function setBrokerFromLedger(EventTypes.SetBrokerData calldata data) external;

    // Delegate swap function
    function setSwapOperator(address _swapOperator) external;
    function setSwapSigner(address _swapSigner) external;
    function isSwapSubmitted(bytes32 tradeId) external view returns (bool);
    function delegateSwap(VaultTypes.DelegateSwap calldata data) external;
}
"
    },
    "src/interface/IVaultCrossChainManager.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

// Importing necessary utility libraries and types
import "../library/types/AccountTypes.sol";
import "../library/types/VaultTypes.sol";
import "../library/types/RebalanceTypes.sol";

/// @title IVaultCrossChainManager Interface
/// @notice Interface for managing cross-chain activities related to the vault.
interface IVaultCrossChainManager {
    /// @notice Triggers a withdrawal from the ledger.
    /// @param withdraw Struct containing withdrawal data.
    function withdraw(VaultTypes.VaultWithdraw memory withdraw) external;

    /// @notice Triggers a finish msg from vault to ledger to inform the status of burn
    /// @param data Struct containing burn data.
    function burnFinish(RebalanceTypes.RebalanceBurnCCFinishData memory data) external;

    /// @notice Triggers a finish msg from vault to ledger to inform the status of mint
    /// @param data Struct containing mint data.
    function mintFinish(RebalanceTypes.RebalanceMintCCFinishData memory data) external;

    /// @notice Initiates a deposit to the vault.
    /// @param data Struct containing deposit data.
    function deposit(VaultTypes.VaultDeposit memory data) external;

    /// @notice Initiates a deposit to the vault along with native fees.
    /// @param data Struct containing deposit data.
    function depositWithFee(VaultTypes.VaultDeposit memory data) external payable;

    /// @notice Initiates a deposit to the vault along with native fees.
    /// @param refundReceiver Address of the receiver for the deposit fee refund.
    /// @param data Struct containing deposit data.
    function depositWithFeeRefund(address refundReceiver, VaultTypes.VaultDeposit memory data) external payable;

    /// @notice Fetches the deposit fee based on deposit data.
    /// @param data Struct containing deposit data.
    /// @return fee The calculated deposit fee.
    function getDepositFee(VaultTypes.VaultDeposit memory data) external view returns (uint256);

    /// @notice Sets the vault address.
    /// @param vault Address of the new vault.
    function setVault(address vault) external;

    /// @notice Sets the cross-chain relay address.
    /// @param crossChainRelay Address of the new cross-chain relay.
    function setCrossChainRelay(address crossChainRelay) external;
}
"
    },
    "src/library/Utils.sol": {
      "content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.18;

/// @title Utils library
/// @author Orderly_Rubick Orderly_Zion
library Utils {
    // legacy account id
    function getAccountId(address _userAddr, string memory _brokerId) internal pure returns (bytes32) {
        return keccak256(abi.encode(_userAddr, calculateStringHash(_brokerId)));
    }

    // legacy account id
    function calculateAccountId(address _userAddr, bytes32 _brokerHash) internal pure returns (bytes32) {
        return keccak256(abi.encode(_userAddr, _brokerHash));
    }

    // pv account id
    function calculateStrategyVaultAccountId(address _vault, address _userAddr, bytes32 _brokerHash)
        internal
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(_vault, _userAddr, _brokerHash));
    }

    function calculateStringHash(string memory _str) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(_str));
    }

    // legacy account id for evm
    function validateAccountId(bytes32 _accountId, bytes32 _brokerHash, address _userAddress)
        internal
        pure
        returns (bool)
    {
        return keccak256(abi.encode(_userAddress, _brokerHash)) == _accountId;
    }

    // legacy account id for solana
    function validateAccountId(bytes32 _accountId, bytes32 _brokerHash, bytes32 _userAddress)
        internal
        pure
        returns (bool)
    {
        return keccak256(abi.encode(_userAddress, _brokerHash)) == _accountId;
    }

    function validateStrategyVaultAccountId(
        address _vault,
        bytes32 _accountId,
        bytes32 _brokerHash,
        address _userAddress
    ) internal pure returns (bool) {
        return calculateStrategyVaultAccountId(_vault, _userAddress, _brokerHash) == _accountId;
    }

    // both legacy accountId and pv accountId are valid
    function validateExtendedAccountId(address _vault, bytes32 _accountId, bytes32 _brokerHash, address _userAddress)
        internal
        pure
        returns (bool)
    {
        return validateAccountId(_accountId, _brokerHash, _userAddress)
            || validateStrategyVaultAccountId(_vault, _accountId, _brokerHash, _userAddress);
    }

    function toBytes32(address addr) internal pure returns (bytes32) {
        return bytes32(abi.encode(addr));
    }

    function bytes32ToBytes(bytes32 _bytes32) internal pure returns (bytes memory) {
        return abi.encodePacked(_bytes32);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    function __Pausable_init() internal onlyInitializing {
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal onlyInitializing {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../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.
 *
 * By default, the owner account will be the one that deploys the contract. 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 {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function __Ownable_init() internal onlyInitializing {
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @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 {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should

Tags:
ERC20, Multisig, Swap, Upgradeable, Multi-Signature, Factory|addr:0xf80150582bf41339882740355c554cfba8790f69|verified:true|block:23590014|tx:0x853e0a44b9217dc6bfacc1718ab27c3ddb920d4bcc6350b87b8b904b40d66ca2|first_check:1760618781

Submitted on: 2025-10-16 14:46:25

Comments

Log in to comment.

No comments yet.