ETH Price: $2,525.33 (+0.39%)
Gas: 0.38 Gwei

Transaction Decoder

Block:
20614625 at Aug-26-2024 06:41:35 PM +UTC
Transaction Fee:
0.000328693344438084 ETH $0.83
Gas Used:
119,436 Gas / 2.752045819 Gwei

Emitted Events:

190 PriceRegistry.UsdPerUnitGasUpdated( destChain=7264351850409363825, value=520207845799274239367403183286949792935721434, timestamp=1724697695 )
191 CommitStore.Transmitted( configDigest=00012DA522C4EB868A6396E5A3F26B12E29D3A68935341A418F613954FB6B34E, epoch=1793 )

Account State Difference:

  Address   Before After State Difference Code
2.627749251080206201 Eth2.627861628412606201 Eth0.0001123773324
0x76264869...67c7F190e
0x8c9b2Efb...763b958Ad
0xfc038715...B21434157
4.566943104748398273 Eth
Nonce: 23111
4.566614411403960189 Eth
Nonce: 23112
0.000328693344438084

Execution Trace

CommitStore.transmit( )
  • ARMProxy.STATICCALL( )
    • ARM.CALL( )
    • PriceRegistry.updatePrices( priceUpdates=[{name:tokenPriceUpdates, type:tuple[], order:1, indexed:false}, {name:gasPriceUpdates, type:tuple[], order:2, indexed:false}] )
    • Null: 0x000...001.44d0bd53( )
    • Null: 0x000...001.44d0bd53( )
    • Null: 0x000...001.44d0bd53( )
    • Null: 0x000...001.44d0bd53( )
    • Null: 0x000...001.44d0bd53( )
    • Null: 0x000...001.44d0bd53( )
      File 1 of 4: CommitStore
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity 0.8.19;
      import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
      import {ICommitStore} from "./interfaces/ICommitStore.sol";
      import {IARM} from "./interfaces/IARM.sol";
      import {IPriceRegistry} from "./interfaces/IPriceRegistry.sol";
      import {OCR2Base} from "./ocr/OCR2Base.sol";
      import {Internal} from "./libraries/Internal.sol";
      import {MerkleMultiProof} from "./libraries/MerkleMultiProof.sol";
      contract CommitStore is ICommitStore, ITypeAndVersion, OCR2Base {
        error StaleReport();
        error PausedError();
        error InvalidInterval(Interval interval);
        error InvalidRoot();
        error InvalidCommitStoreConfig();
        error BadARMSignal();
        error RootAlreadyCommitted();
        event Paused(address account);
        event Unpaused(address account);
        /// @dev RMN depends on this event, if changing, please notify the RMN maintainers.
        event ReportAccepted(CommitReport report);
        event ConfigSet(StaticConfig staticConfig, DynamicConfig dynamicConfig);
        event RootRemoved(bytes32 root);
        /// @notice Static commit store config
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct StaticConfig {
          uint64 chainSelector; // ───────╮  Destination chainSelector
          uint64 sourceChainSelector; // ─╯  Source chainSelector
          address onRamp; // OnRamp address on the source chain
          address armProxy; // ARM proxy address
        }
        /// @notice Dynamic commit store config
        struct DynamicConfig {
          address priceRegistry; // Price registry address on the destination chain
        }
        /// @notice a sequenceNumber interval
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct Interval {
          uint64 min; // ───╮ Minimum sequence number, inclusive
          uint64 max; // ───╯ Maximum sequence number, inclusive
        }
        /// @notice Report that is committed by the observing DON at the committing phase
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct CommitReport {
          Internal.PriceUpdates priceUpdates;
          Interval interval;
          bytes32 merkleRoot;
        }
        // STATIC CONFIG
        // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
        string public constant override typeAndVersion = "CommitStore 1.2.0";
        // Chain ID of this chain
        uint64 internal immutable i_chainSelector;
        // Chain ID of the source chain
        uint64 internal immutable i_sourceChainSelector;
        // The onRamp address on the source chain
        address internal immutable i_onRamp;
        // The address of the arm proxy
        address internal immutable i_armProxy;
        // DYNAMIC CONFIG
        // The dynamic commitStore config
        DynamicConfig internal s_dynamicConfig;
        // STATE
        // The min sequence number expected for future messages
        uint64 private s_minSeqNr = 1;
        /// @dev The epoch and round of the last report
        uint40 private s_latestPriceEpochAndRound;
        /// @dev Whether this OnRamp is paused or not
        bool private s_paused = false;
        // merkleRoot => timestamp when received
        mapping(bytes32 merkleRoot => uint256 timestamp) private s_roots;
        /// @param staticConfig Containing the static part of the commitStore config
        /// @dev When instantiating OCR2Base we set UNIQUE_REPORTS to false, which means
        /// that we do not require 2f+1 signatures on a report, only f+1 to save gas. 2f+1 is required
        /// only if one must strictly ensure that for a given round there is only one valid report ever generated by
        /// the DON. In our case additional valid reports (i.e. approved by >= f+1 oracles) are not a problem, as they will
        /// will either be ignored (reverted as an invalid interval) or will be accepted as an additional valid price update.
        constructor(StaticConfig memory staticConfig) OCR2Base(false) {
          if (
            staticConfig.onRamp == address(0) ||
            staticConfig.chainSelector == 0 ||
            staticConfig.sourceChainSelector == 0 ||
            staticConfig.armProxy == address(0)
          ) revert InvalidCommitStoreConfig();
          i_chainSelector = staticConfig.chainSelector;
          i_sourceChainSelector = staticConfig.sourceChainSelector;
          i_onRamp = staticConfig.onRamp;
          i_armProxy = staticConfig.armProxy;
        }
        // ================================================================
        // │                        Verification                          │
        // ================================================================
        /// @notice Returns the next expected sequence number.
        /// @return the next expected sequenceNumber.
        function getExpectedNextSequenceNumber() external view returns (uint64) {
          return s_minSeqNr;
        }
        /// @notice Sets the minimum sequence number.
        /// @param minSeqNr The new minimum sequence number.
        function setMinSeqNr(uint64 minSeqNr) external onlyOwner {
          s_minSeqNr = minSeqNr;
        }
        /// @notice Returns the epoch and round of the last price update.
        /// @return the latest price epoch and round.
        function getLatestPriceEpochAndRound() public view returns (uint64) {
          return s_latestPriceEpochAndRound;
        }
        /// @notice Sets the latest epoch and round for price update.
        /// @param latestPriceEpochAndRound The new epoch and round for prices.
        function setLatestPriceEpochAndRound(uint40 latestPriceEpochAndRound) external onlyOwner {
          s_latestPriceEpochAndRound = latestPriceEpochAndRound;
        }
        /// @notice Returns the timestamp of a potentially previously committed merkle root.
        /// If the root was never committed 0 will be returned.
        /// @param root The merkle root to check the commit status for.
        /// @return the timestamp of the committed root or zero in the case that it was never
        /// committed.
        function getMerkleRoot(bytes32 root) external view returns (uint256) {
          return s_roots[root];
        }
        /// @notice Returns if a root is blessed or not.
        /// @param root The merkle root to check the blessing status for.
        /// @return whether the root is blessed or not.
        function isBlessed(bytes32 root) public view returns (bool) {
          return IARM(i_armProxy).isBlessed(IARM.TaggedRoot({commitStore: address(this), root: root}));
        }
        /// @notice Used by the owner in case an invalid sequence of roots has been
        /// posted and needs to be removed. The interval in the report is trusted.
        /// @param rootToReset The roots that will be reset. This function will only
        /// reset roots that are not blessed.
        function resetUnblessedRoots(bytes32[] calldata rootToReset) external onlyOwner {
          for (uint256 i = 0; i < rootToReset.length; ++i) {
            bytes32 root = rootToReset[i];
            if (!isBlessed(root)) {
              delete s_roots[root];
              emit RootRemoved(root);
            }
          }
        }
        /// @inheritdoc ICommitStore
        function verify(
          bytes32[] calldata hashedLeaves,
          bytes32[] calldata proofs,
          uint256 proofFlagBits
        ) external view override whenNotPaused returns (uint256 timestamp) {
          bytes32 root = MerkleMultiProof.merkleRoot(hashedLeaves, proofs, proofFlagBits);
          // Only return non-zero if present and blessed.
          if (!isBlessed(root)) {
            return 0;
          }
          return s_roots[root];
        }
        /// @inheritdoc OCR2Base
        /// @dev A commitReport can have two distinct parts (batched together to amortize the cost of checking sigs):
        /// 1. Price updates
        /// 2. A merkle root and sequence number interval
        /// Both have their own, separate, staleness checks, with price updates using the epoch and round
        /// number of the latest price update. The merkle root checks for staleness based on the seqNums.
        /// They need to be separate because a price report for round t+2 might be included before a report
        /// containing a merkle root for round t+1. This merkle root report for round t+1 is still valid
        /// and should not be rejected. When a report with a stale root but valid price updates is submitted,
        /// we are OK to revert to preserve the invariant that we always revert on invalid sequence number ranges.
        /// If that happens, prices will be updates in later rounds.
        function _report(bytes calldata encodedReport, uint40 epochAndRound) internal override whenNotPaused whenHealthy {
          CommitReport memory report = abi.decode(encodedReport, (CommitReport));
          // Check if the report contains price updates
          if (report.priceUpdates.tokenPriceUpdates.length > 0 || report.priceUpdates.gasPriceUpdates.length > 0) {
            // Check for price staleness based on the epoch and round
            if (s_latestPriceEpochAndRound < epochAndRound) {
              // If prices are not stale, update the latest epoch and round
              s_latestPriceEpochAndRound = epochAndRound;
              // And update the prices in the price registry
              IPriceRegistry(s_dynamicConfig.priceRegistry).updatePrices(report.priceUpdates);
              // If there is no root, the report only contained fee updated and
              // we return to not revert on the empty root check below.
              if (report.merkleRoot == bytes32(0)) return;
            } else {
              // If prices are stale and the report doesn't contain a root, this report
              // does not have any valid information and we revert.
              // If it does contain a merkle root, continue to the root checking section.
              if (report.merkleRoot == bytes32(0)) revert StaleReport();
            }
          }
          // If we reached this section, the report should contain a valid root
          if (s_minSeqNr != report.interval.min || report.interval.min > report.interval.max)
            revert InvalidInterval(report.interval);
          if (report.merkleRoot == bytes32(0)) revert InvalidRoot();
          // Disallow duplicate roots as that would reset the timestamp and
          // delay potential manual execution.
          if (s_roots[report.merkleRoot] != 0) revert RootAlreadyCommitted();
          s_minSeqNr = report.interval.max + 1;
          s_roots[report.merkleRoot] = block.timestamp;
          emit ReportAccepted(report);
        }
        // ================================================================
        // │                           Config                             │
        // ================================================================
        /// @notice Returns the static commit store config.
        /// @dev RMN depends on this function, if changing, please notify the RMN maintainers.
        /// @return the configuration.
        function getStaticConfig() external view returns (StaticConfig memory) {
          return
            StaticConfig({
              chainSelector: i_chainSelector,
              sourceChainSelector: i_sourceChainSelector,
              onRamp: i_onRamp,
              armProxy: i_armProxy
            });
        }
        /// @notice Returns the dynamic commit store config.
        /// @return the configuration.
        function getDynamicConfig() external view returns (DynamicConfig memory) {
          return s_dynamicConfig;
        }
        /// @notice Sets the dynamic config. This function is called during `setOCR2Config` flow
        function _beforeSetConfig(bytes memory onchainConfig) internal override {
          DynamicConfig memory dynamicConfig = abi.decode(onchainConfig, (DynamicConfig));
          if (dynamicConfig.priceRegistry == address(0)) revert InvalidCommitStoreConfig();
          s_dynamicConfig = dynamicConfig;
          // When the OCR config changes, we reset the price epoch and round
          // since epoch and rounds are scoped per config digest.
          // Note that s_minSeqNr/roots do not need to be reset as the roots persist
          // across reconfigurations and are de-duplicated separately.
          s_latestPriceEpochAndRound = 0;
          emit ConfigSet(
            StaticConfig({
              chainSelector: i_chainSelector,
              sourceChainSelector: i_sourceChainSelector,
              onRamp: i_onRamp,
              armProxy: i_armProxy
            }),
            dynamicConfig
          );
        }
        // ================================================================
        // │                        Access and ARM                        │
        // ================================================================
        /// @notice Single function to check the status of the commitStore.
        function isUnpausedAndARMHealthy() external view returns (bool) {
          return !IARM(i_armProxy).isCursed() && !s_paused;
        }
        /// @notice Support querying whether health checker is healthy.
        function isARMHealthy() external view returns (bool) {
          return !IARM(i_armProxy).isCursed();
        }
        /// @notice Ensure that the ARM has not emitted a bad signal, and that the latest heartbeat is not stale.
        modifier whenHealthy() {
          if (IARM(i_armProxy).isCursed()) revert BadARMSignal();
          _;
        }
        /// @notice Modifier to make a function callable only when the contract is not paused.
        modifier whenNotPaused() {
          if (paused()) revert PausedError();
          _;
        }
        /// @notice Returns true if the contract is paused, and false otherwise.
        function paused() public view returns (bool) {
          return s_paused;
        }
        /// @notice Pause the contract
        /// @dev only callable by the owner
        function pause() external onlyOwner {
          s_paused = true;
          emit Paused(msg.sender);
        }
        /// @notice Unpause the contract
        /// @dev only callable by the owner
        function unpause() external onlyOwner {
          s_paused = false;
          emit Unpaused(msg.sender);
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface ITypeAndVersion {
        function typeAndVersion() external pure returns (string memory);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface ICommitStore {
        /// @notice Returns timestamp of when root was accepted or 0 if verification fails.
        /// @dev This method uses a merkle tree within a merkle tree, with the hashedLeaves,
        /// proofs and proofFlagBits being used to get the root of the inner tree.
        /// This root is then used as the singular leaf of the outer tree.
        function verify(
          bytes32[] calldata hashedLeaves,
          bytes32[] calldata proofs,
          uint256 proofFlagBits
        ) external view returns (uint256 timestamp);
        /// @notice Returns the expected next sequence number
        function getExpectedNextSequenceNumber() external view returns (uint64 sequenceNumber);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      /// @notice This interface contains the only ARM-related functions that might be used on-chain by other CCIP contracts.
      interface IARM {
        /// @notice A Merkle root tagged with the address of the commit store contract it is destined for.
        struct TaggedRoot {
          address commitStore;
          bytes32 root;
        }
        /// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.
        function isBlessed(TaggedRoot calldata taggedRoot) external view returns (bool);
        /// @notice When the ARM is "cursed", CCIP pauses until the curse is lifted.
        function isCursed() external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {Internal} from "../libraries/Internal.sol";
      interface IPriceRegistry {
        /// @notice Update the price for given tokens and gas prices for given chains.
        /// @param priceUpdates The price updates to apply.
        function updatePrices(Internal.PriceUpdates memory priceUpdates) external;
        /// @notice Get the `tokenPrice` for a given token.
        /// @param token The token to get the price for.
        /// @return tokenPrice The tokenPrice for the given token.
        function getTokenPrice(address token) external view returns (Internal.TimestampedPackedUint224 memory);
        /// @notice Get the `tokenPrice` for a given token, checks if the price is valid.
        /// @param token The token to get the price for.
        /// @return tokenPrice The tokenPrice for the given token if it exists and is valid.
        function getValidatedTokenPrice(address token) external view returns (uint224);
        /// @notice Get the `tokenPrice` for an array of tokens.
        /// @param tokens The tokens to get prices for.
        /// @return tokenPrices The tokenPrices for the given tokens.
        function getTokenPrices(address[] calldata tokens) external view returns (Internal.TimestampedPackedUint224[] memory);
        /// @notice Get an encoded `gasPrice` for a given destination chain ID.
        /// The 224-bit result encodes necessary gas price components.
        /// On L1 chains like Ethereum or Avax, the only component is the gas price.
        /// On Optimistic Rollups, there are two components - the L2 gas price, and L1 base fee for data availability.
        /// On future chains, there could be more or differing price components.
        /// PriceRegistry does not contain chain-specific logic to parse destination chain price components.
        /// @param destChainSelector The destination chain to get the price for.
        /// @return gasPrice The encoded gasPrice for the given destination chain ID.
        function getDestinationChainGasPrice(
          uint64 destChainSelector
        ) external view returns (Internal.TimestampedPackedUint224 memory);
        /// @notice Gets the fee token price and the gas price, both denominated in dollars.
        /// @param token The source token to get the price for.
        /// @param destChainSelector The destination chain to get the gas price for.
        /// @return tokenPrice The price of the feeToken in 1e18 dollars per base unit.
        /// @return gasPrice The price of gas in 1e18 dollars per base unit.
        function getTokenAndGasPrices(
          address token,
          uint64 destChainSelector
        ) external view returns (uint224 tokenPrice, uint224 gasPrice);
        /// @notice Convert a given token amount to target token amount.
        /// @param fromToken The given token address.
        /// @param fromTokenAmount The given token amount.
        /// @param toToken The target token address.
        /// @return toTokenAmount The target token amount.
        function convertTokenAmount(
          address fromToken,
          uint256 fromTokenAmount,
          address toToken
        ) external view returns (uint256 toTokenAmount);
      }
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity ^0.8.0;
      import {OwnerIsCreator} from "../../shared/access/OwnerIsCreator.sol";
      import {OCR2Abstract} from "./OCR2Abstract.sol";
      /// @notice Onchain verification of reports from the offchain reporting protocol
      /// @dev For details on its operation, see the offchain reporting protocol design
      /// doc, which refers to this contract as simply the "contract".
      abstract contract OCR2Base is OwnerIsCreator, OCR2Abstract {
        error InvalidConfig(string message);
        error WrongMessageLength(uint256 expected, uint256 actual);
        error ConfigDigestMismatch(bytes32 expected, bytes32 actual);
        error ForkedChain(uint256 expected, uint256 actual);
        error WrongNumberOfSignatures();
        error SignaturesOutOfRegistration();
        error UnauthorizedTransmitter();
        error UnauthorizedSigner();
        error NonUniqueSignatures();
        error OracleCannotBeZeroAddress();
        // Packing these fields used on the hot path in a ConfigInfo variable reduces the
        // retrieval of all of them to a minimum number of SLOADs.
        struct ConfigInfo {
          bytes32 latestConfigDigest;
          uint8 f;
          uint8 n;
        }
        // Used for s_oracles[a].role, where a is an address, to track the purpose
        // of the address, or to indicate that the address is unset.
        enum Role {
          // No oracle role has been set for address a
          Unset,
          // Signing address for the s_oracles[a].index'th oracle. I.e., report
          // signatures from this oracle should ecrecover back to address a.
          Signer,
          // Transmission address for the s_oracles[a].index'th oracle. I.e., if a
          // report is received by OCR2Aggregator.transmit in which msg.sender is
          // a, it is attributed to the s_oracles[a].index'th oracle.
          Transmitter
        }
        struct Oracle {
          uint8 index; // Index of oracle in s_signers/s_transmitters
          Role role; // Role of the address which mapped to this struct
        }
        // The current config
        ConfigInfo internal s_configInfo;
        // incremented each time a new config is posted. This count is incorporated
        // into the config digest, to prevent replay attacks.
        uint32 internal s_configCount;
        // makes it easier for offchain systems to extract config from logs.
        uint32 internal s_latestConfigBlockNumber;
        // signer OR transmitter address
        mapping(address signerOrTransmitter => Oracle oracle) internal s_oracles;
        // s_signers contains the signing address of each oracle
        address[] internal s_signers;
        // s_transmitters contains the transmission address of each oracle,
        // i.e. the address the oracle actually sends transactions to the contract from
        address[] internal s_transmitters;
        // The constant-length components of the msg.data sent to transmit.
        // See the "If we wanted to call sam" example on for example reasoning
        // https://solidity.readthedocs.io/en/v0.7.2/abi-spec.html
        uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT =
          4 + // function selector
            32 *
            3 + // 3 words containing reportContext
            32 + // word containing start location of abiencoded report value
            32 + // word containing location start of abiencoded rs value
            32 + // word containing start location of abiencoded ss value
            32 + // rawVs value
            32 + // word containing length of report
            32 + // word containing length rs
            32; // word containing length of ss
        bool internal immutable i_uniqueReports;
        uint256 internal immutable i_chainID;
        constructor(bool uniqueReports) {
          i_uniqueReports = uniqueReports;
          i_chainID = block.chainid;
        }
        // Reverts transaction if config args are invalid
        modifier checkConfigValid(
          uint256 numSigners,
          uint256 numTransmitters,
          uint256 f
        ) {
          if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers");
          if (f == 0) revert InvalidConfig("f must be positive");
          if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration");
          if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high");
          _;
        }
        /// @notice sets offchain reporting protocol configuration incl. participating oracles
        /// @param signers addresses with which oracles sign the reports
        /// @param transmitters addresses oracles use to transmit the reports
        /// @param f number of faulty oracles the system can tolerate
        /// @param onchainConfig encoded on-chain contract configuration
        /// @param offchainConfigVersion version number for offchainEncoding schema
        /// @param offchainConfig encoded off-chain oracle configuration
        function setOCR2Config(
          address[] memory signers,
          address[] memory transmitters,
          uint8 f,
          bytes memory onchainConfig,
          uint64 offchainConfigVersion,
          bytes memory offchainConfig
        ) external override checkConfigValid(signers.length, transmitters.length, f) onlyOwner {
          _beforeSetConfig(onchainConfig);
          uint256 oldSignerLength = s_signers.length;
          for (uint256 i = 0; i < oldSignerLength; ++i) {
            delete s_oracles[s_signers[i]];
            delete s_oracles[s_transmitters[i]];
          }
          uint256 newSignersLength = signers.length;
          for (uint256 i = 0; i < newSignersLength; ++i) {
            // add new signer/transmitter addresses
            address signer = signers[i];
            if (s_oracles[signer].role != Role.Unset) revert InvalidConfig("repeated signer address");
            if (signer == address(0)) revert OracleCannotBeZeroAddress();
            s_oracles[signer] = Oracle(uint8(i), Role.Signer);
            address transmitter = transmitters[i];
            if (s_oracles[transmitter].role != Role.Unset) revert InvalidConfig("repeated transmitter address");
            if (transmitter == address(0)) revert OracleCannotBeZeroAddress();
            s_oracles[transmitter] = Oracle(uint8(i), Role.Transmitter);
          }
          s_signers = signers;
          s_transmitters = transmitters;
          s_configInfo.f = f;
          s_configInfo.n = uint8(newSignersLength);
          s_configInfo.latestConfigDigest = _configDigestFromConfigData(
            block.chainid,
            address(this),
            ++s_configCount,
            signers,
            transmitters,
            f,
            onchainConfig,
            offchainConfigVersion,
            offchainConfig
          );
          uint32 previousConfigBlockNumber = s_latestConfigBlockNumber;
          s_latestConfigBlockNumber = uint32(block.number);
          emit ConfigSet(
            previousConfigBlockNumber,
            s_configInfo.latestConfigDigest,
            s_configCount,
            signers,
            transmitters,
            f,
            onchainConfig,
            offchainConfigVersion,
            offchainConfig
          );
        }
        /// @dev Hook that is run from setOCR2Config() right after validating configuration.
        /// Empty by default, please provide an implementation in a child contract if you need additional configuration processing
        function _beforeSetConfig(bytes memory _onchainConfig) internal virtual {}
        /// @return list of addresses permitted to transmit reports to this contract
        /// @dev The list will match the order used to specify the transmitter during setConfig
        function getTransmitters() external view returns (address[] memory) {
          return s_transmitters;
        }
        /// @notice transmit is called to post a new report to the contract
        /// @param report serialized report, which the signatures are signing.
        /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
        /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
        /// @param rawVs ith element is the the V component of the ith signature
        function transmit(
          // NOTE: If these parameters are changed, expectedMsgDataLength and/or
          // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
          bytes32[3] calldata reportContext,
          bytes calldata report,
          bytes32[] calldata rs,
          bytes32[] calldata ss,
          bytes32 rawVs // signatures
        ) external override {
          // Scoping this reduces stack pressure and gas usage
          {
            // report and epochAndRound
            _report(report, uint40(uint256(reportContext[1])));
          }
          // reportContext consists of:
          // reportContext[0]: ConfigDigest
          // reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round
          // reportContext[2]: ExtraHash
          bytes32 configDigest = reportContext[0];
          ConfigInfo memory configInfo = s_configInfo;
          if (configInfo.latestConfigDigest != configDigest)
            revert ConfigDigestMismatch(configInfo.latestConfigDigest, configDigest);
          // If the cached chainID at time of deployment doesn't match the current chainID, we reject all signed reports.
          // This avoids a (rare) scenario where chain A forks into chain A and A', A' still has configDigest
          // calculated from chain A and so OCR reports will be valid on both forks.
          if (i_chainID != block.chainid) revert ForkedChain(i_chainID, block.chainid);
          emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
          uint256 expectedNumSignatures;
          if (i_uniqueReports) {
            expectedNumSignatures = (configInfo.n + configInfo.f) / 2 + 1;
          } else {
            expectedNumSignatures = configInfo.f + 1;
          }
          if (rs.length != expectedNumSignatures) revert WrongNumberOfSignatures();
          if (rs.length != ss.length) revert SignaturesOutOfRegistration();
          // Scoping this reduces stack pressure and gas usage
          {
            Oracle memory transmitter = s_oracles[msg.sender];
            // Check that sender is authorized to report
            if (!(transmitter.role == Role.Transmitter && msg.sender == s_transmitters[transmitter.index]))
              revert UnauthorizedTransmitter();
          }
          // Scoping this reduces stack pressure and gas usage
          {
            uint256 expectedDataLength = uint256(TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT) +
              report.length + // one byte pure entry in _report
              rs.length *
              32 + // 32 bytes per entry in _rs
              ss.length *
              32; // 32 bytes per entry in _ss)
            if (msg.data.length != expectedDataLength) revert WrongMessageLength(expectedDataLength, msg.data.length);
          }
          // Verify signatures attached to report
          bytes32 h = keccak256(abi.encodePacked(keccak256(report), reportContext));
          bool[MAX_NUM_ORACLES] memory signed;
          uint256 numberOfSignatures = rs.length;
          for (uint256 i = 0; i < numberOfSignatures; ++i) {
            // Safe from ECDSA malleability here since we check for duplicate signers.
            address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]);
            // Since we disallow address(0) as a valid signer address, it can
            // never have a signer role.
            Oracle memory oracle = s_oracles[signer];
            if (oracle.role != Role.Signer) revert UnauthorizedSigner();
            if (signed[oracle.index]) revert NonUniqueSignatures();
            signed[oracle.index] = true;
          }
        }
        /// @notice information about current offchain reporting protocol configuration
        /// @return configCount ordinal number of current config, out of all configs applied to this contract so far
        /// @return blockNumber block at which this config was set
        /// @return configDigest domain-separation tag for current config (see _configDigestFromConfigData)
        function latestConfigDetails()
          external
          view
          override
          returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest)
        {
          return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest);
        }
        /// @inheritdoc OCR2Abstract
        function latestConfigDigestAndEpoch()
          external
          view
          virtual
          override
          returns (bool scanLogs, bytes32 configDigest, uint32 epoch)
        {
          return (true, bytes32(0), uint32(0));
        }
        function _report(bytes calldata report, uint40 epochAndRound) internal virtual;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {Client} from "./Client.sol";
      import {MerkleMultiProof} from "../libraries/MerkleMultiProof.sol";
      // Library for CCIP internal definitions common to multiple contracts.
      library Internal {
        /// @dev The minimum amount of gas to perform the call with exact gas.
        /// We include this in the offramp so that we can redeploy to adjust it
        /// should a hardfork change the gas costs of relevant opcodes in callWithExactGas.
        uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
        // @dev We limit return data to a selector plus 4 words. This is to avoid
        // malicious contracts from returning large amounts of data and causing
        // repeated out-of-gas scenarios.
        uint16 internal constant MAX_RET_BYTES = 4 + 4 * 32;
        /// @notice A collection of token price and gas price updates.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct PriceUpdates {
          TokenPriceUpdate[] tokenPriceUpdates;
          GasPriceUpdate[] gasPriceUpdates;
        }
        /// @notice Token price in USD.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct TokenPriceUpdate {
          address sourceToken; // Source token
          uint224 usdPerToken; // 1e18 USD per smallest unit of token
        }
        /// @notice Gas price for a given chain in USD, its value may contain tightly packed fields.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct GasPriceUpdate {
          uint64 destChainSelector; // Destination chain selector
          uint224 usdPerUnitGas; // 1e18 USD per smallest unit (e.g. wei) of destination chain gas
        }
        /// @notice A timestamped uint224 value that can contain several tightly packed fields.
        struct TimestampedPackedUint224 {
          uint224 value; // ───────╮ Value in uint224, packed.
          uint32 timestamp; // ────╯ Timestamp of the most recent price update.
        }
        /// @dev Gas price is stored in 112-bit unsigned int. uint224 can pack 2 prices.
        /// When packing L1 and L2 gas prices, L1 gas price is left-shifted to the higher-order bits.
        /// Using uint8 type, which cannot be higher than other bit shift operands, to avoid shift operand type warning.
        uint8 public constant GAS_PRICE_BITS = 112;
        struct PoolUpdate {
          address token; // The IERC20 token address
          address pool; // The token pool address
        }
        /// @notice Report that is submitted by the execution DON at the execution phase.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct ExecutionReport {
          EVM2EVMMessage[] messages;
          // Contains a bytes array for each message, each inner bytes array contains bytes per transferred token
          bytes[][] offchainTokenData;
          bytes32[] proofs;
          uint256 proofFlagBits;
        }
        /// @notice The cross chain message that gets committed to EVM chains.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct EVM2EVMMessage {
          uint64 sourceChainSelector; // ─────────╮ the chain selector of the source chain, note: not chainId
          address sender; // ─────────────────────╯ sender address on the source chain
          address receiver; // ───────────────────╮ receiver address on the destination chain
          uint64 sequenceNumber; // ──────────────╯ sequence number, not unique across lanes
          uint256 gasLimit; //                      user supplied maximum gas amount available for dest chain execution
          bool strict; // ────────────────────────╮ DEPRECATED
          uint64 nonce; //                        │ nonce for this lane for this sender, not unique across senders/lanes
          address feeToken; // ───────────────────╯ fee token
          uint256 feeTokenAmount; //                fee token amount
          bytes data; //                            arbitrary data payload supplied by the message sender
          Client.EVMTokenAmount[] tokenAmounts; //  array of tokens and amounts to transfer
          bytes[] sourceTokenData; //               array of token pool return values, one per token
          bytes32 messageId; //                     a hash of the message data
        }
        /// @dev EVM2EVMMessage struct has 13 fields, including 3 variable arrays.
        /// Each variable array takes 1 more slot to store its length.
        /// When abi encoded, excluding array contents,
        /// EVM2EVMMessage takes up a fixed number of 16 lots, 32 bytes each.
        /// For structs that contain arrays, 1 more slot is added to the front, reaching a total of 17.
        uint256 public constant MESSAGE_FIXED_BYTES = 32 * 17;
        /// @dev Each token transfer adds 1 EVMTokenAmount and 1 bytes.
        /// When abiEncoded, each EVMTokenAmount takes 2 slots, each bytes takes 2 slots, excl bytes contents
        uint256 public constant MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * 4;
        function _toAny2EVMMessage(
          EVM2EVMMessage memory original,
          Client.EVMTokenAmount[] memory destTokenAmounts
        ) internal pure returns (Client.Any2EVMMessage memory message) {
          message = Client.Any2EVMMessage({
            messageId: original.messageId,
            sourceChainSelector: original.sourceChainSelector,
            sender: abi.encode(original.sender),
            data: original.data,
            destTokenAmounts: destTokenAmounts
          });
        }
        bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256("EVM2EVMMessageHashV2");
        function _hash(EVM2EVMMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
          // Fixed-size message fields are included in nested hash to reduce stack pressure.
          // This hashing scheme is also used by RMN. If changing it, please notify the RMN maintainers.
          return
            keccak256(
              abi.encode(
                MerkleMultiProof.LEAF_DOMAIN_SEPARATOR,
                metadataHash,
                keccak256(
                  abi.encode(
                    original.sender,
                    original.receiver,
                    original.sequenceNumber,
                    original.gasLimit,
                    original.strict,
                    original.nonce,
                    original.feeToken,
                    original.feeTokenAmount
                  )
                ),
                keccak256(original.data),
                keccak256(abi.encode(original.tokenAmounts)),
                keccak256(abi.encode(original.sourceTokenData))
              )
            );
        }
        /// @notice Enum listing the possible message execution states within
        /// the offRamp contract.
        /// UNTOUCHED never executed
        /// IN_PROGRESS currently being executed, used a replay protection
        /// SUCCESS successfully executed. End state
        /// FAILURE unsuccessfully executed, manual execution is now enabled.
        /// @dev RMN depends on this enum, if changing, please notify the RMN maintainers.
        enum MessageExecutionState {
          UNTOUCHED,
          IN_PROGRESS,
          SUCCESS,
          FAILURE
        }
      }
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity ^0.8.0;
      library MerkleMultiProof {
        /// @notice Leaf domain separator, should be used as the first 32 bytes of a leaf's preimage.
        bytes32 internal constant LEAF_DOMAIN_SEPARATOR = 0x0000000000000000000000000000000000000000000000000000000000000000;
        /// @notice Internal domain separator, should be used as the first 32 bytes of an internal node's preiimage.
        bytes32 internal constant INTERNAL_DOMAIN_SEPARATOR =
          0x0000000000000000000000000000000000000000000000000000000000000001;
        uint256 internal constant MAX_NUM_HASHES = 256;
        error InvalidProof();
        error LeavesCannotBeEmpty();
        /// @notice Computes the root based on provided pre-hashed leaf nodes in
        /// leaves, internal nodes in proofs, and using proofFlagBits' i-th bit to
        /// determine if an element of proofs or one of the previously computed leafs
        /// or internal nodes will be used for the i-th hash.
        /// @param leaves Should be pre-hashed and the first 32 bytes of a leaf's
        /// preimage should match LEAF_DOMAIN_SEPARATOR.
        /// @param proofs The hashes to be used instead of a leaf hash when the proofFlagBits
        ///  indicates a proof should be used.
        /// @param proofFlagBits A single uint256 of which each bit indicates whether a leaf or
        ///  a proof needs to be used in a hash operation.
        /// @dev the maximum number of hash operations it set to 256. Any input that would require
        ///  more than 256 hashes to get to a root will revert.
        /// @dev For given input `leaves` = [a,b,c] `proofs` = [D] and `proofFlagBits` = 5
        ///     totalHashes = 3 + 1 - 1 = 3
        ///  ** round 1 **
        ///    proofFlagBits = (5 >> 0) & 1 = true
        ///    hashes[0] = hashPair(a, b)
        ///    (leafPos, hashPos, proofPos) = (2, 0, 0);
        ///
        ///  ** round 2 **
        ///    proofFlagBits = (5 >> 1) & 1 = false
        ///    hashes[1] = hashPair(D, c)
        ///    (leafPos, hashPos, proofPos) = (3, 0, 1);
        ///
        ///  ** round 3 **
        ///    proofFlagBits = (5 >> 2) & 1 = true
        ///    hashes[2] = hashPair(hashes[0], hashes[1])
        ///    (leafPos, hashPos, proofPos) = (3, 2, 1);
        ///
        ///    i = 3 and no longer < totalHashes. The algorithm is done
        ///    return hashes[totalHashes - 1] = hashes[2]; the last hash we computed.
        // We mark this function as internal to force it to be inlined in contracts
        // that use it, but semantically it is public.
        // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
        function merkleRoot(
          bytes32[] memory leaves,
          bytes32[] memory proofs,
          uint256 proofFlagBits
        ) internal pure returns (bytes32) {
          unchecked {
            uint256 leavesLen = leaves.length;
            uint256 proofsLen = proofs.length;
            if (leavesLen == 0) revert LeavesCannotBeEmpty();
            if (!(leavesLen <= MAX_NUM_HASHES + 1 && proofsLen <= MAX_NUM_HASHES + 1)) revert InvalidProof();
            uint256 totalHashes = leavesLen + proofsLen - 1;
            if (!(totalHashes <= MAX_NUM_HASHES)) revert InvalidProof();
            if (totalHashes == 0) {
              return leaves[0];
            }
            bytes32[] memory hashes = new bytes32[](totalHashes);
            (uint256 leafPos, uint256 hashPos, uint256 proofPos) = (0, 0, 0);
            for (uint256 i = 0; i < totalHashes; ++i) {
              // Checks if the bit flag signals the use of a supplied proof or a leaf/previous hash.
              bytes32 a;
              if (proofFlagBits & (1 << i) == (1 << i)) {
                // Use a leaf or a previously computed hash.
                if (leafPos < leavesLen) {
                  a = leaves[leafPos++];
                } else {
                  a = hashes[hashPos++];
                }
              } else {
                // Use a supplied proof.
                a = proofs[proofPos++];
              }
              // The second part of the hashed pair is never a proof as hashing two proofs would result in a
              // hash that can already be computed offchain.
              bytes32 b;
              if (leafPos < leavesLen) {
                b = leaves[leafPos++];
              } else {
                b = hashes[hashPos++];
              }
              if (!(hashPos <= i)) revert InvalidProof();
              hashes[i] = _hashPair(a, b);
            }
            if (!(hashPos == totalHashes - 1 && leafPos == leavesLen && proofPos == proofsLen)) revert InvalidProof();
            // Return the last hash.
            return hashes[totalHashes - 1];
          }
        }
        /// @notice Hashes two bytes32 objects in their given order, prepended by the
        /// INTERNAL_DOMAIN_SEPARATOR.
        function _hashInternalNode(bytes32 left, bytes32 right) private pure returns (bytes32 hash) {
          return keccak256(abi.encode(INTERNAL_DOMAIN_SEPARATOR, left, right));
        }
        /// @notice Hashes two bytes32 objects. The order is taken into account,
        /// using the lower value first.
        function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
          return a < b ? _hashInternalNode(a, b) : _hashInternalNode(b, a);
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwner} from "./ConfirmedOwner.sol";
      /// @title The OwnerIsCreator contract
      /// @notice A contract with helpers for basic contract ownership.
      contract OwnerIsCreator is ConfirmedOwner {
        constructor() ConfirmedOwner(msg.sender) {}
      }
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity ^0.8.0;
      import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
      abstract contract OCR2Abstract is ITypeAndVersion {
        // Maximum number of oracles the offchain reporting protocol is designed for
        uint256 internal constant MAX_NUM_ORACLES = 31;
        /// @notice triggers a new run of the offchain reporting protocol
        /// @param previousConfigBlockNumber block in which the previous config was set, to simplify historic analysis
        /// @param configDigest configDigest of this configuration
        /// @param configCount ordinal number of this config setting among all config settings over the life of this contract
        /// @param signers ith element is address ith oracle uses to sign a report
        /// @param transmitters ith element is address ith oracle uses to transmit a report via the transmit method
        /// @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly
        /// @param onchainConfig serialized configuration used by the contract (and possibly oracles)
        /// @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter
        /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
        event ConfigSet(
          uint32 previousConfigBlockNumber,
          bytes32 configDigest,
          uint64 configCount,
          address[] signers,
          address[] transmitters,
          uint8 f,
          bytes onchainConfig,
          uint64 offchainConfigVersion,
          bytes offchainConfig
        );
        /// @notice sets offchain reporting protocol configuration incl. participating oracles
        /// @param signers addresses with which oracles sign the reports
        /// @param transmitters addresses oracles use to transmit the reports
        /// @param f number of faulty oracles the system can tolerate
        /// @param onchainConfig serialized configuration used by the contract (and possibly oracles)
        /// @param offchainConfigVersion version number for offchainEncoding schema
        /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
        function setOCR2Config(
          address[] memory signers,
          address[] memory transmitters,
          uint8 f,
          bytes memory onchainConfig,
          uint64 offchainConfigVersion,
          bytes memory offchainConfig
        ) external virtual;
        /// @notice information about current offchain reporting protocol configuration
        /// @return configCount ordinal number of current config, out of all configs applied to this contract so far
        /// @return blockNumber block at which this config was set
        /// @return configDigest domain-separation tag for current config (see _configDigestFromConfigData)
        function latestConfigDetails()
          external
          view
          virtual
          returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest);
        function _configDigestFromConfigData(
          uint256 chainId,
          address contractAddress,
          uint64 configCount,
          address[] memory signers,
          address[] memory transmitters,
          uint8 f,
          bytes memory onchainConfig,
          uint64 offchainConfigVersion,
          bytes memory offchainConfig
        ) internal pure returns (bytes32) {
          uint256 h = uint256(
            keccak256(
              abi.encode(
                chainId,
                contractAddress,
                configCount,
                signers,
                transmitters,
                f,
                onchainConfig,
                offchainConfigVersion,
                offchainConfig
              )
            )
          );
          uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00
          uint256 prefix = 0x0001 << (256 - 16); // 0x000100..00
          return bytes32((prefix & prefixMask) | (h & ~prefixMask));
        }
        /// @notice optionally emitted to indicate the latest configDigest and epoch for
        /// which a report was successfully transmitted. Alternatively, the contract may
        /// use latestConfigDigestAndEpoch with scanLogs set to false.
        event Transmitted(bytes32 configDigest, uint32 epoch);
        /// @notice optionally returns the latest configDigest and epoch for which a
        /// report was successfully transmitted. Alternatively, the contract may return
        /// scanLogs set to true and use Transmitted events to provide this information
        /// to offchain watchers.
        /// @return scanLogs indicates whether to rely on the configDigest and epoch
        /// returned or whether to scan logs for the Transmitted event instead.
        /// @return configDigest
        /// @return epoch
        function latestConfigDigestAndEpoch()
          external
          view
          virtual
          returns (bool scanLogs, bytes32 configDigest, uint32 epoch);
        /// @notice transmit is called to post a new report to the contract
        /// @param report serialized report, which the signatures are signing.
        /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
        /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
        /// @param rawVs ith element is the the V component of the ith signature
        function transmit(
          // NOTE: If these parameters are changed, expectedMsgDataLength and/or
          // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
          bytes32[3] calldata reportContext,
          bytes calldata report,
          bytes32[] calldata rs,
          bytes32[] calldata ss,
          bytes32 rawVs // signatures
        ) external virtual;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      // End consumer library.
      library Client {
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct EVMTokenAmount {
          address token; // token address on the local chain.
          uint256 amount; // Amount of tokens.
        }
        struct Any2EVMMessage {
          bytes32 messageId; // MessageId corresponding to ccipSend on source.
          uint64 sourceChainSelector; // Source chain selector.
          bytes sender; // abi.decode(sender) if coming from an EVM chain.
          bytes data; // payload sent in original message.
          EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.
        }
        // If extraArgs is empty bytes, the default is 200k gas limit.
        struct EVM2AnyMessage {
          bytes receiver; // abi.encode(receiver address) for dest EVM chains
          bytes data; // Data payload
          EVMTokenAmount[] tokenAmounts; // Token transfers
          address feeToken; // Address of feeToken. address(0) means you will send msg.value.
          bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1)
        }
        // bytes4(keccak256("CCIP EVMExtraArgsV1"));
        bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
        struct EVMExtraArgsV1 {
          uint256 gasLimit;
        }
        function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {
          return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwner is ConfirmedOwnerWithProposal {
        constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {IOwnable} from "../interfaces/IOwnable.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwnerWithProposal is IOwnable {
        address private s_owner;
        address private s_pendingOwner;
        event OwnershipTransferRequested(address indexed from, address indexed to);
        event OwnershipTransferred(address indexed from, address indexed to);
        constructor(address newOwner, address pendingOwner) {
          // solhint-disable-next-line custom-errors
          require(newOwner != address(0), "Cannot set owner to zero");
          s_owner = newOwner;
          if (pendingOwner != address(0)) {
            _transferOwnership(pendingOwner);
          }
        }
        /// @notice Allows an owner to begin transferring ownership to a new address.
        function transferOwnership(address to) public override onlyOwner {
          _transferOwnership(to);
        }
        /// @notice Allows an ownership transfer to be completed by the recipient.
        function acceptOwnership() external override {
          // solhint-disable-next-line custom-errors
          require(msg.sender == s_pendingOwner, "Must be proposed owner");
          address oldOwner = s_owner;
          s_owner = msg.sender;
          s_pendingOwner = address(0);
          emit OwnershipTransferred(oldOwner, msg.sender);
        }
        /// @notice Get the current owner
        function owner() public view override returns (address) {
          return s_owner;
        }
        /// @notice validate, transfer ownership, and emit relevant events
        function _transferOwnership(address to) private {
          // solhint-disable-next-line custom-errors
          require(to != msg.sender, "Cannot transfer to self");
          s_pendingOwner = to;
          emit OwnershipTransferRequested(s_owner, to);
        }
        /// @notice validate access
        function _validateOwnership() internal view {
          // solhint-disable-next-line custom-errors
          require(msg.sender == s_owner, "Only callable by owner");
        }
        /// @notice Reverts if called by anyone other than the contract owner.
        modifier onlyOwner() {
          _validateOwnership();
          _;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IOwnable {
        function owner() external returns (address);
        function transferOwnership(address recipient) external;
        function acceptOwnership() external;
      }
      

      File 2 of 4: PriceRegistry
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity 0.8.19;
      import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
      import {IPriceRegistry} from "./interfaces/IPriceRegistry.sol";
      import {OwnerIsCreator} from "./../shared/access/OwnerIsCreator.sol";
      import {Internal} from "./libraries/Internal.sol";
      import {USDPriceWith18Decimals} from "./libraries/USDPriceWith18Decimals.sol";
      import {EnumerableSet} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol";
      /// @notice The PriceRegistry contract responsibility is to store the current gas price in USD for a given destination chain,
      /// and the price of a token in USD allowing the owner or priceUpdater to update this value.
      contract PriceRegistry is IPriceRegistry, OwnerIsCreator, ITypeAndVersion {
        using EnumerableSet for EnumerableSet.AddressSet;
        using USDPriceWith18Decimals for uint224;
        error TokenNotSupported(address token);
        error ChainNotSupported(uint64 chain);
        error OnlyCallableByUpdaterOrOwner();
        error StaleGasPrice(uint64 destChainSelector, uint256 threshold, uint256 timePassed);
        error StaleTokenPrice(address token, uint256 threshold, uint256 timePassed);
        error InvalidStalenessThreshold();
        event PriceUpdaterSet(address indexed priceUpdater);
        event PriceUpdaterRemoved(address indexed priceUpdater);
        event FeeTokenAdded(address indexed feeToken);
        event FeeTokenRemoved(address indexed feeToken);
        event UsdPerUnitGasUpdated(uint64 indexed destChain, uint256 value, uint256 timestamp);
        event UsdPerTokenUpdated(address indexed token, uint256 value, uint256 timestamp);
        // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
        string public constant override typeAndVersion = "PriceRegistry 1.2.0";
        /// @dev The gas price per unit of gas for a given destination chain, in USD with 18 decimals.
        /// Multiple gas prices can be encoded into the same value. Each price takes {Internal.GAS_PRICE_BITS} bits.
        /// For example, if Optimism is the destination chain, gas price can include L1 base fee and L2 gas price.
        /// Logic to parse the price components is chain-specific, and should live in OnRamp.
        /// @dev Price of 1e18 is 1 USD. Examples:
        ///     Very Expensive:   1 unit of gas costs 1 USD                  -> 1e18
        ///     Expensive:        1 unit of gas costs 0.1 USD                -> 1e17
        ///     Cheap:            1 unit of gas costs 0.000001 USD           -> 1e12
        mapping(uint64 destChainSelector => Internal.TimestampedPackedUint224 price)
          private s_usdPerUnitGasByDestChainSelector;
        /// @dev The price, in USD with 18 decimals, per 1e18 of the smallest token denomination.
        /// @dev Price of 1e18 represents 1 USD per 1e18 token amount.
        ///     1 USDC = 1.00 USD per full token, each full token is 1e6 units -> 1 * 1e18 * 1e18 / 1e6 = 1e30
        ///     1 ETH = 2,000 USD per full token, each full token is 1e18 units -> 2000 * 1e18 * 1e18 / 1e18 = 2_000e18
        ///     1 LINK = 5.00 USD per full token, each full token is 1e18 units -> 5 * 1e18 * 1e18 / 1e18 = 5e18
        mapping(address token => Internal.TimestampedPackedUint224 price) private s_usdPerToken;
        // Price updaters are allowed to update the prices.
        EnumerableSet.AddressSet private s_priceUpdaters;
        // Subset of tokens which prices tracked by this registry which are fee tokens.
        EnumerableSet.AddressSet private s_feeTokens;
        // The amount of time a price can be stale before it is considered invalid.
        uint32 private immutable i_stalenessThreshold;
        constructor(address[] memory priceUpdaters, address[] memory feeTokens, uint32 stalenessThreshold) {
          _applyPriceUpdatersUpdates(priceUpdaters, new address[](0));
          _applyFeeTokensUpdates(feeTokens, new address[](0));
          if (stalenessThreshold == 0) revert InvalidStalenessThreshold();
          i_stalenessThreshold = stalenessThreshold;
        }
        // ================================================================
        // │                     Price calculations                       │
        // ================================================================
        // @inheritdoc IPriceRegistry
        function getTokenPrice(address token) public view override returns (Internal.TimestampedPackedUint224 memory) {
          return s_usdPerToken[token];
        }
        // @inheritdoc IPriceRegistry
        function getValidatedTokenPrice(address token) external view override returns (uint224) {
          return _getValidatedTokenPrice(token);
        }
        // @inheritdoc IPriceRegistry
        function getTokenPrices(
          address[] calldata tokens
        ) external view override returns (Internal.TimestampedPackedUint224[] memory) {
          uint256 length = tokens.length;
          Internal.TimestampedPackedUint224[] memory tokenPrices = new Internal.TimestampedPackedUint224[](length);
          for (uint256 i = 0; i < length; ++i) {
            tokenPrices[i] = getTokenPrice(tokens[i]);
          }
          return tokenPrices;
        }
        /// @notice Get the staleness threshold.
        /// @return stalenessThreshold The staleness threshold.
        function getStalenessThreshold() external view returns (uint128) {
          return i_stalenessThreshold;
        }
        // @inheritdoc IPriceRegistry
        function getDestinationChainGasPrice(
          uint64 destChainSelector
        ) external view override returns (Internal.TimestampedPackedUint224 memory) {
          return s_usdPerUnitGasByDestChainSelector[destChainSelector];
        }
        function getTokenAndGasPrices(
          address token,
          uint64 destChainSelector
        ) external view override returns (uint224 tokenPrice, uint224 gasPriceValue) {
          Internal.TimestampedPackedUint224 memory gasPrice = s_usdPerUnitGasByDestChainSelector[destChainSelector];
          // We do allow a gas price of 0, but no stale or unset gas prices
          if (gasPrice.timestamp == 0) revert ChainNotSupported(destChainSelector);
          uint256 timePassed = block.timestamp - gasPrice.timestamp;
          if (timePassed > i_stalenessThreshold) revert StaleGasPrice(destChainSelector, i_stalenessThreshold, timePassed);
          return (_getValidatedTokenPrice(token), gasPrice.value);
        }
        /// @inheritdoc IPriceRegistry
        /// @dev this function assumes that no more than 1e59 dollars are sent as payment.
        /// If more is sent, the multiplication of feeTokenAmount and feeTokenValue will overflow.
        /// Since there isn't even close to 1e59 dollars in the world economy this is safe.
        function convertTokenAmount(
          address fromToken,
          uint256 fromTokenAmount,
          address toToken
        ) external view override returns (uint256) {
          /// Example:
          /// fromTokenAmount:   1e18      // 1 ETH
          /// ETH:               2_000e18
          /// LINK:              5e18
          /// return:            1e18 * 2_000e18 / 5e18 = 400e18 (400 LINK)
          return (fromTokenAmount * _getValidatedTokenPrice(fromToken)) / _getValidatedTokenPrice(toToken);
        }
        /// @notice Gets the token price for a given token and revert if the token is either
        /// not supported or the price is stale.
        /// @param token The address of the token to get the price for
        /// @return the token price
        function _getValidatedTokenPrice(address token) internal view returns (uint224) {
          Internal.TimestampedPackedUint224 memory tokenPrice = s_usdPerToken[token];
          if (tokenPrice.timestamp == 0 || tokenPrice.value == 0) revert TokenNotSupported(token);
          uint256 timePassed = block.timestamp - tokenPrice.timestamp;
          if (timePassed > i_stalenessThreshold) revert StaleTokenPrice(token, i_stalenessThreshold, timePassed);
          return tokenPrice.value;
        }
        // ================================================================
        // │                         Fee tokens                           │
        // ================================================================
        /// @notice Get the list of fee tokens.
        /// @return The tokens set as fee tokens.
        function getFeeTokens() external view returns (address[] memory) {
          return s_feeTokens.values();
        }
        /// @notice Add and remove tokens from feeTokens set.
        /// @param feeTokensToAdd The addresses of the tokens which are now considered fee tokens
        /// and can be used to calculate fees.
        /// @param feeTokensToRemove The addresses of the tokens which are no longer considered feeTokens.
        function applyFeeTokensUpdates(
          address[] memory feeTokensToAdd,
          address[] memory feeTokensToRemove
        ) external onlyOwner {
          _applyFeeTokensUpdates(feeTokensToAdd, feeTokensToRemove);
        }
        /// @notice Add and remove tokens from feeTokens set.
        /// @param feeTokensToAdd The addresses of the tokens which are now considered fee tokens
        /// and can be used to calculate fees.
        /// @param feeTokensToRemove The addresses of the tokens which are no longer considered feeTokens.
        function _applyFeeTokensUpdates(address[] memory feeTokensToAdd, address[] memory feeTokensToRemove) private {
          for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
            if (s_feeTokens.add(feeTokensToAdd[i])) {
              emit FeeTokenAdded(feeTokensToAdd[i]);
            }
          }
          for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
            if (s_feeTokens.remove(feeTokensToRemove[i])) {
              emit FeeTokenRemoved(feeTokensToRemove[i]);
            }
          }
        }
        // ================================================================
        // │                       Price updates                          │
        // ================================================================
        // @inheritdoc IPriceRegistry
        function updatePrices(Internal.PriceUpdates calldata priceUpdates) external override requireUpdaterOrOwner {
          uint256 tokenUpdatesLength = priceUpdates.tokenPriceUpdates.length;
          for (uint256 i = 0; i < tokenUpdatesLength; ++i) {
            Internal.TokenPriceUpdate memory update = priceUpdates.tokenPriceUpdates[i];
            s_usdPerToken[update.sourceToken] = Internal.TimestampedPackedUint224({
              value: update.usdPerToken,
              timestamp: uint32(block.timestamp)
            });
            emit UsdPerTokenUpdated(update.sourceToken, update.usdPerToken, block.timestamp);
          }
          uint256 gasUpdatesLength = priceUpdates.gasPriceUpdates.length;
          for (uint256 i = 0; i < gasUpdatesLength; ++i) {
            Internal.GasPriceUpdate memory update = priceUpdates.gasPriceUpdates[i];
            s_usdPerUnitGasByDestChainSelector[update.destChainSelector] = Internal.TimestampedPackedUint224({
              value: update.usdPerUnitGas,
              timestamp: uint32(block.timestamp)
            });
            emit UsdPerUnitGasUpdated(update.destChainSelector, update.usdPerUnitGas, block.timestamp);
          }
        }
        // ================================================================
        // │                           Access                             │
        // ================================================================
        /// @notice Get the list of price updaters.
        /// @return The price updaters.
        function getPriceUpdaters() external view returns (address[] memory) {
          return s_priceUpdaters.values();
        }
        /// @notice Adds new priceUpdaters and remove existing ones.
        /// @param priceUpdatersToAdd The addresses of the priceUpdaters that are now allowed
        /// to send fee updates.
        /// @param priceUpdatersToRemove The addresses of the priceUpdaters that are no longer allowed
        /// to send fee updates.
        function applyPriceUpdatersUpdates(
          address[] memory priceUpdatersToAdd,
          address[] memory priceUpdatersToRemove
        ) external onlyOwner {
          _applyPriceUpdatersUpdates(priceUpdatersToAdd, priceUpdatersToRemove);
        }
        /// @notice Adds new priceUpdaters and remove existing ones.
        /// @param priceUpdatersToAdd The addresses of the priceUpdaters that are now allowed
        /// to send fee updates.
        /// @param priceUpdatersToRemove The addresses of the priceUpdaters that are no longer allowed
        /// to send fee updates.
        function _applyPriceUpdatersUpdates(
          address[] memory priceUpdatersToAdd,
          address[] memory priceUpdatersToRemove
        ) private {
          for (uint256 i = 0; i < priceUpdatersToAdd.length; ++i) {
            if (s_priceUpdaters.add(priceUpdatersToAdd[i])) {
              emit PriceUpdaterSet(priceUpdatersToAdd[i]);
            }
          }
          for (uint256 i = 0; i < priceUpdatersToRemove.length; ++i) {
            if (s_priceUpdaters.remove(priceUpdatersToRemove[i])) {
              emit PriceUpdaterRemoved(priceUpdatersToRemove[i]);
            }
          }
        }
        /// @notice Require that the caller is the owner or a fee updater.
        modifier requireUpdaterOrOwner() {
          if (msg.sender != owner() && !s_priceUpdaters.contains(msg.sender)) revert OnlyCallableByUpdaterOrOwner();
          _;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface ITypeAndVersion {
        function typeAndVersion() external pure returns (string memory);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {Internal} from "../libraries/Internal.sol";
      interface IPriceRegistry {
        /// @notice Update the price for given tokens and gas prices for given chains.
        /// @param priceUpdates The price updates to apply.
        function updatePrices(Internal.PriceUpdates memory priceUpdates) external;
        /// @notice Get the `tokenPrice` for a given token.
        /// @param token The token to get the price for.
        /// @return tokenPrice The tokenPrice for the given token.
        function getTokenPrice(address token) external view returns (Internal.TimestampedPackedUint224 memory);
        /// @notice Get the `tokenPrice` for a given token, checks if the price is valid.
        /// @param token The token to get the price for.
        /// @return tokenPrice The tokenPrice for the given token if it exists and is valid.
        function getValidatedTokenPrice(address token) external view returns (uint224);
        /// @notice Get the `tokenPrice` for an array of tokens.
        /// @param tokens The tokens to get prices for.
        /// @return tokenPrices The tokenPrices for the given tokens.
        function getTokenPrices(address[] calldata tokens) external view returns (Internal.TimestampedPackedUint224[] memory);
        /// @notice Get an encoded `gasPrice` for a given destination chain ID.
        /// The 224-bit result encodes necessary gas price components.
        /// On L1 chains like Ethereum or Avax, the only component is the gas price.
        /// On Optimistic Rollups, there are two components - the L2 gas price, and L1 base fee for data availability.
        /// On future chains, there could be more or differing price components.
        /// PriceRegistry does not contain chain-specific logic to parse destination chain price components.
        /// @param destChainSelector The destination chain to get the price for.
        /// @return gasPrice The encoded gasPrice for the given destination chain ID.
        function getDestinationChainGasPrice(
          uint64 destChainSelector
        ) external view returns (Internal.TimestampedPackedUint224 memory);
        /// @notice Gets the fee token price and the gas price, both denominated in dollars.
        /// @param token The source token to get the price for.
        /// @param destChainSelector The destination chain to get the gas price for.
        /// @return tokenPrice The price of the feeToken in 1e18 dollars per base unit.
        /// @return gasPrice The price of gas in 1e18 dollars per base unit.
        function getTokenAndGasPrices(
          address token,
          uint64 destChainSelector
        ) external view returns (uint224 tokenPrice, uint224 gasPrice);
        /// @notice Convert a given token amount to target token amount.
        /// @param fromToken The given token address.
        /// @param fromTokenAmount The given token amount.
        /// @param toToken The target token address.
        /// @return toTokenAmount The target token amount.
        function convertTokenAmount(
          address fromToken,
          uint256 fromTokenAmount,
          address toToken
        ) external view returns (uint256 toTokenAmount);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwner} from "./ConfirmedOwner.sol";
      /// @title The OwnerIsCreator contract
      /// @notice A contract with helpers for basic contract ownership.
      contract OwnerIsCreator is ConfirmedOwner {
        constructor() ConfirmedOwner(msg.sender) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {Client} from "./Client.sol";
      import {MerkleMultiProof} from "../libraries/MerkleMultiProof.sol";
      // Library for CCIP internal definitions common to multiple contracts.
      library Internal {
        /// @dev The minimum amount of gas to perform the call with exact gas.
        /// We include this in the offramp so that we can redeploy to adjust it
        /// should a hardfork change the gas costs of relevant opcodes in callWithExactGas.
        uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
        // @dev We limit return data to a selector plus 4 words. This is to avoid
        // malicious contracts from returning large amounts of data and causing
        // repeated out-of-gas scenarios.
        uint16 internal constant MAX_RET_BYTES = 4 + 4 * 32;
        /// @notice A collection of token price and gas price updates.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct PriceUpdates {
          TokenPriceUpdate[] tokenPriceUpdates;
          GasPriceUpdate[] gasPriceUpdates;
        }
        /// @notice Token price in USD.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct TokenPriceUpdate {
          address sourceToken; // Source token
          uint224 usdPerToken; // 1e18 USD per smallest unit of token
        }
        /// @notice Gas price for a given chain in USD, its value may contain tightly packed fields.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct GasPriceUpdate {
          uint64 destChainSelector; // Destination chain selector
          uint224 usdPerUnitGas; // 1e18 USD per smallest unit (e.g. wei) of destination chain gas
        }
        /// @notice A timestamped uint224 value that can contain several tightly packed fields.
        struct TimestampedPackedUint224 {
          uint224 value; // ───────╮ Value in uint224, packed.
          uint32 timestamp; // ────╯ Timestamp of the most recent price update.
        }
        /// @dev Gas price is stored in 112-bit unsigned int. uint224 can pack 2 prices.
        /// When packing L1 and L2 gas prices, L1 gas price is left-shifted to the higher-order bits.
        /// Using uint8 type, which cannot be higher than other bit shift operands, to avoid shift operand type warning.
        uint8 public constant GAS_PRICE_BITS = 112;
        struct PoolUpdate {
          address token; // The IERC20 token address
          address pool; // The token pool address
        }
        /// @notice Report that is submitted by the execution DON at the execution phase.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct ExecutionReport {
          EVM2EVMMessage[] messages;
          // Contains a bytes array for each message, each inner bytes array contains bytes per transferred token
          bytes[][] offchainTokenData;
          bytes32[] proofs;
          uint256 proofFlagBits;
        }
        /// @notice The cross chain message that gets committed to EVM chains.
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct EVM2EVMMessage {
          uint64 sourceChainSelector; // ─────────╮ the chain selector of the source chain, note: not chainId
          address sender; // ─────────────────────╯ sender address on the source chain
          address receiver; // ───────────────────╮ receiver address on the destination chain
          uint64 sequenceNumber; // ──────────────╯ sequence number, not unique across lanes
          uint256 gasLimit; //                      user supplied maximum gas amount available for dest chain execution
          bool strict; // ────────────────────────╮ DEPRECATED
          uint64 nonce; //                        │ nonce for this lane for this sender, not unique across senders/lanes
          address feeToken; // ───────────────────╯ fee token
          uint256 feeTokenAmount; //                fee token amount
          bytes data; //                            arbitrary data payload supplied by the message sender
          Client.EVMTokenAmount[] tokenAmounts; //  array of tokens and amounts to transfer
          bytes[] sourceTokenData; //               array of token pool return values, one per token
          bytes32 messageId; //                     a hash of the message data
        }
        /// @dev EVM2EVMMessage struct has 13 fields, including 3 variable arrays.
        /// Each variable array takes 1 more slot to store its length.
        /// When abi encoded, excluding array contents,
        /// EVM2EVMMessage takes up a fixed number of 16 lots, 32 bytes each.
        /// For structs that contain arrays, 1 more slot is added to the front, reaching a total of 17.
        uint256 public constant MESSAGE_FIXED_BYTES = 32 * 17;
        /// @dev Each token transfer adds 1 EVMTokenAmount and 1 bytes.
        /// When abiEncoded, each EVMTokenAmount takes 2 slots, each bytes takes 2 slots, excl bytes contents
        uint256 public constant MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * 4;
        function _toAny2EVMMessage(
          EVM2EVMMessage memory original,
          Client.EVMTokenAmount[] memory destTokenAmounts
        ) internal pure returns (Client.Any2EVMMessage memory message) {
          message = Client.Any2EVMMessage({
            messageId: original.messageId,
            sourceChainSelector: original.sourceChainSelector,
            sender: abi.encode(original.sender),
            data: original.data,
            destTokenAmounts: destTokenAmounts
          });
        }
        bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256("EVM2EVMMessageHashV2");
        function _hash(EVM2EVMMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
          // Fixed-size message fields are included in nested hash to reduce stack pressure.
          // This hashing scheme is also used by RMN. If changing it, please notify the RMN maintainers.
          return
            keccak256(
              abi.encode(
                MerkleMultiProof.LEAF_DOMAIN_SEPARATOR,
                metadataHash,
                keccak256(
                  abi.encode(
                    original.sender,
                    original.receiver,
                    original.sequenceNumber,
                    original.gasLimit,
                    original.strict,
                    original.nonce,
                    original.feeToken,
                    original.feeTokenAmount
                  )
                ),
                keccak256(original.data),
                keccak256(abi.encode(original.tokenAmounts)),
                keccak256(abi.encode(original.sourceTokenData))
              )
            );
        }
        /// @notice Enum listing the possible message execution states within
        /// the offRamp contract.
        /// UNTOUCHED never executed
        /// IN_PROGRESS currently being executed, used a replay protection
        /// SUCCESS successfully executed. End state
        /// FAILURE unsuccessfully executed, manual execution is now enabled.
        /// @dev RMN depends on this enum, if changing, please notify the RMN maintainers.
        enum MessageExecutionState {
          UNTOUCHED,
          IN_PROGRESS,
          SUCCESS,
          FAILURE
        }
      }
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity ^0.8.0;
      library USDPriceWith18Decimals {
        /// @notice Takes a price in USD, with 18 decimals per 1e18 token amount,
        /// and amount of the smallest token denomination,
        /// calculates the value in USD with 18 decimals.
        /// @param tokenPrice The USD price of the token.
        /// @param tokenAmount Amount of the smallest token denomination.
        /// @return USD value with 18 decimals.
        /// @dev this function assumes that no more than 1e59 US dollar worth of token is passed in.
        /// If more is sent, this function will overflow and revert.
        /// Since there isn't even close to 1e59 dollars, this is ok for all legit tokens.
        function _calcUSDValueFromTokenAmount(uint224 tokenPrice, uint256 tokenAmount) internal pure returns (uint256) {
          /// LINK Example:
          /// tokenPrice:         8e18 -> $8/LINK, as 1e18 token amount is 1 LINK, worth 8 USD, or 8e18 with 18 decimals
          /// tokenAmount:        2e18 -> 2 LINK
          /// result:             8e18 * 2e18 / 1e18 -> 16e18 with 18 decimals = $16
          /// USDC Example:
          /// tokenPrice:         1e30 -> $1/USDC, as 1e18 token amount is 1e12 USDC, worth 1e12 USD, or 1e30 with 18 decimals
          /// tokenAmount:        5e6  -> 5 USDC
          /// result:             1e30 * 5e6 / 1e18 -> 5e18 with 18 decimals = $5
          return (tokenPrice * tokenAmount) / 1e18;
        }
        /// @notice Takes a price in USD, with 18 decimals per 1e18 token amount,
        /// and USD value with 18 decimals,
        /// calculates amount of the smallest token denomination.
        /// @param tokenPrice The USD price of the token.
        /// @param usdValue USD value with 18 decimals.
        /// @return Amount of the smallest token denomination.
        function _calcTokenAmountFromUSDValue(uint224 tokenPrice, uint256 usdValue) internal pure returns (uint256) {
          /// LINK Example:
          /// tokenPrice:          8e18 -> $8/LINK, as 1e18 token amount is 1 LINK, worth 8 USD, or 8e18 with 18 decimals
          /// usdValue:           16e18 -> $16
          /// result:             16e18 * 1e18 / 8e18 -> 2e18 = 2 LINK
          /// USDC Example:
          /// tokenPrice:         1e30 -> $1/USDC, as 1e18 token amount is 1e12 USDC, worth 1e12 USD, or 1e30 with 18 decimals
          /// usdValue:           5e18 -> $5
          /// result:             5e18 * 1e18 / 1e30 -> 5e6 = 5 USDC
          return (usdValue * 1e18) / tokenPrice;
        }
      }
      // 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 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(UintSet storage set) internal view returns (uint256[] memory) {
          bytes32[] memory store = _values(set._inner);
          uint256[] memory result;
          /// @solidity memory-safe-assembly
          assembly {
            result := store
          }
          return result;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwner is ConfirmedOwnerWithProposal {
        constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      // End consumer library.
      library Client {
        /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
        struct EVMTokenAmount {
          address token; // token address on the local chain.
          uint256 amount; // Amount of tokens.
        }
        struct Any2EVMMessage {
          bytes32 messageId; // MessageId corresponding to ccipSend on source.
          uint64 sourceChainSelector; // Source chain selector.
          bytes sender; // abi.decode(sender) if coming from an EVM chain.
          bytes data; // payload sent in original message.
          EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.
        }
        // If extraArgs is empty bytes, the default is 200k gas limit.
        struct EVM2AnyMessage {
          bytes receiver; // abi.encode(receiver address) for dest EVM chains
          bytes data; // Data payload
          EVMTokenAmount[] tokenAmounts; // Token transfers
          address feeToken; // Address of feeToken. address(0) means you will send msg.value.
          bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1)
        }
        // bytes4(keccak256("CCIP EVMExtraArgsV1"));
        bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
        struct EVMExtraArgsV1 {
          uint256 gasLimit;
        }
        function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {
          return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
        }
      }
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity ^0.8.0;
      library MerkleMultiProof {
        /// @notice Leaf domain separator, should be used as the first 32 bytes of a leaf's preimage.
        bytes32 internal constant LEAF_DOMAIN_SEPARATOR = 0x0000000000000000000000000000000000000000000000000000000000000000;
        /// @notice Internal domain separator, should be used as the first 32 bytes of an internal node's preiimage.
        bytes32 internal constant INTERNAL_DOMAIN_SEPARATOR =
          0x0000000000000000000000000000000000000000000000000000000000000001;
        uint256 internal constant MAX_NUM_HASHES = 256;
        error InvalidProof();
        error LeavesCannotBeEmpty();
        /// @notice Computes the root based on provided pre-hashed leaf nodes in
        /// leaves, internal nodes in proofs, and using proofFlagBits' i-th bit to
        /// determine if an element of proofs or one of the previously computed leafs
        /// or internal nodes will be used for the i-th hash.
        /// @param leaves Should be pre-hashed and the first 32 bytes of a leaf's
        /// preimage should match LEAF_DOMAIN_SEPARATOR.
        /// @param proofs The hashes to be used instead of a leaf hash when the proofFlagBits
        ///  indicates a proof should be used.
        /// @param proofFlagBits A single uint256 of which each bit indicates whether a leaf or
        ///  a proof needs to be used in a hash operation.
        /// @dev the maximum number of hash operations it set to 256. Any input that would require
        ///  more than 256 hashes to get to a root will revert.
        /// @dev For given input `leaves` = [a,b,c] `proofs` = [D] and `proofFlagBits` = 5
        ///     totalHashes = 3 + 1 - 1 = 3
        ///  ** round 1 **
        ///    proofFlagBits = (5 >> 0) & 1 = true
        ///    hashes[0] = hashPair(a, b)
        ///    (leafPos, hashPos, proofPos) = (2, 0, 0);
        ///
        ///  ** round 2 **
        ///    proofFlagBits = (5 >> 1) & 1 = false
        ///    hashes[1] = hashPair(D, c)
        ///    (leafPos, hashPos, proofPos) = (3, 0, 1);
        ///
        ///  ** round 3 **
        ///    proofFlagBits = (5 >> 2) & 1 = true
        ///    hashes[2] = hashPair(hashes[0], hashes[1])
        ///    (leafPos, hashPos, proofPos) = (3, 2, 1);
        ///
        ///    i = 3 and no longer < totalHashes. The algorithm is done
        ///    return hashes[totalHashes - 1] = hashes[2]; the last hash we computed.
        // We mark this function as internal to force it to be inlined in contracts
        // that use it, but semantically it is public.
        // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
        function merkleRoot(
          bytes32[] memory leaves,
          bytes32[] memory proofs,
          uint256 proofFlagBits
        ) internal pure returns (bytes32) {
          unchecked {
            uint256 leavesLen = leaves.length;
            uint256 proofsLen = proofs.length;
            if (leavesLen == 0) revert LeavesCannotBeEmpty();
            if (!(leavesLen <= MAX_NUM_HASHES + 1 && proofsLen <= MAX_NUM_HASHES + 1)) revert InvalidProof();
            uint256 totalHashes = leavesLen + proofsLen - 1;
            if (!(totalHashes <= MAX_NUM_HASHES)) revert InvalidProof();
            if (totalHashes == 0) {
              return leaves[0];
            }
            bytes32[] memory hashes = new bytes32[](totalHashes);
            (uint256 leafPos, uint256 hashPos, uint256 proofPos) = (0, 0, 0);
            for (uint256 i = 0; i < totalHashes; ++i) {
              // Checks if the bit flag signals the use of a supplied proof or a leaf/previous hash.
              bytes32 a;
              if (proofFlagBits & (1 << i) == (1 << i)) {
                // Use a leaf or a previously computed hash.
                if (leafPos < leavesLen) {
                  a = leaves[leafPos++];
                } else {
                  a = hashes[hashPos++];
                }
              } else {
                // Use a supplied proof.
                a = proofs[proofPos++];
              }
              // The second part of the hashed pair is never a proof as hashing two proofs would result in a
              // hash that can already be computed offchain.
              bytes32 b;
              if (leafPos < leavesLen) {
                b = leaves[leafPos++];
              } else {
                b = hashes[hashPos++];
              }
              if (!(hashPos <= i)) revert InvalidProof();
              hashes[i] = _hashPair(a, b);
            }
            if (!(hashPos == totalHashes - 1 && leafPos == leavesLen && proofPos == proofsLen)) revert InvalidProof();
            // Return the last hash.
            return hashes[totalHashes - 1];
          }
        }
        /// @notice Hashes two bytes32 objects in their given order, prepended by the
        /// INTERNAL_DOMAIN_SEPARATOR.
        function _hashInternalNode(bytes32 left, bytes32 right) private pure returns (bytes32 hash) {
          return keccak256(abi.encode(INTERNAL_DOMAIN_SEPARATOR, left, right));
        }
        /// @notice Hashes two bytes32 objects. The order is taken into account,
        /// using the lower value first.
        function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
          return a < b ? _hashInternalNode(a, b) : _hashInternalNode(b, a);
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {IOwnable} from "../interfaces/IOwnable.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwnerWithProposal is IOwnable {
        address private s_owner;
        address private s_pendingOwner;
        event OwnershipTransferRequested(address indexed from, address indexed to);
        event OwnershipTransferred(address indexed from, address indexed to);
        constructor(address newOwner, address pendingOwner) {
          // solhint-disable-next-line custom-errors
          require(newOwner != address(0), "Cannot set owner to zero");
          s_owner = newOwner;
          if (pendingOwner != address(0)) {
            _transferOwnership(pendingOwner);
          }
        }
        /// @notice Allows an owner to begin transferring ownership to a new address.
        function transferOwnership(address to) public override onlyOwner {
          _transferOwnership(to);
        }
        /// @notice Allows an ownership transfer to be completed by the recipient.
        function acceptOwnership() external override {
          // solhint-disable-next-line custom-errors
          require(msg.sender == s_pendingOwner, "Must be proposed owner");
          address oldOwner = s_owner;
          s_owner = msg.sender;
          s_pendingOwner = address(0);
          emit OwnershipTransferred(oldOwner, msg.sender);
        }
        /// @notice Get the current owner
        function owner() public view override returns (address) {
          return s_owner;
        }
        /// @notice validate, transfer ownership, and emit relevant events
        function _transferOwnership(address to) private {
          // solhint-disable-next-line custom-errors
          require(to != msg.sender, "Cannot transfer to self");
          s_pendingOwner = to;
          emit OwnershipTransferRequested(s_owner, to);
        }
        /// @notice validate access
        function _validateOwnership() internal view {
          // solhint-disable-next-line custom-errors
          require(msg.sender == s_owner, "Only callable by owner");
        }
        /// @notice Reverts if called by anyone other than the contract owner.
        modifier onlyOwner() {
          _validateOwnership();
          _;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IOwnable {
        function owner() external returns (address);
        function transferOwnership(address recipient) external;
        function acceptOwnership() external;
      }
      

      File 3 of 4: ARMProxy
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity 0.8.19;
      import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
      import {IARM} from "./interfaces/IARM.sol";
      import {OwnerIsCreator} from "./../shared/access/OwnerIsCreator.sol";
      contract ARMProxy is OwnerIsCreator, TypeAndVersionInterface {
        error ZeroAddressNotAllowed();
        event ARMSet(address arm);
        // STATIC CONFIG
        // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
        string public constant override typeAndVersion = "ARMProxy 1.0.0";
        // DYNAMIC CONFIG
        address private s_arm;
        constructor(address arm) {
          setARM(arm);
        }
        /// @notice SetARM sets the ARM implementation contract address.
        /// @param arm The address of the arm implementation contract.
        function setARM(address arm) public onlyOwner {
          if (arm == address(0)) revert ZeroAddressNotAllowed();
          s_arm = arm;
          emit ARMSet(arm);
        }
        /// @notice getARM gets the ARM implementation contract address.
        /// @return arm The address of the arm implementation contract.
        function getARM() external view returns (address) {
          return s_arm;
        }
        // We use a fallback function instead of explicit implementations of the functions
        // defined in IARM.sol to preserve compatibility with future additions to the IARM
        // interface. Calling IARM interface methods in ARMProxy should be transparent, i.e.
        // their input/output behaviour should be identical to calling the proxied s_arm
        // contract directly. (If s_arm doesn't point to a contract, we always revert.)
        fallback() external {
          address arm = s_arm;
          assembly {
            // Revert if no contract present at destination address, otherwise call
            // might succeed unintentionally.
            if iszero(extcodesize(arm)) {
              revert(0, 0)
            }
            // We use memory starting at zero, overwriting anything that might already
            // be stored there. This messes with Solidity's expectations around memory
            // layout, but it's fine because we always exit execution of this contract
            // inside this assembly block, i.e. we don't cede control to code generated
            // by the Solidity compiler that might have expectations around memory
            // layout.
            // Copy calldatasize() bytes from calldata offset 0 to memory offset 0.
            calldatacopy(0, 0, calldatasize())
            // Call the underlying ARM implementation. out and outsize are 0 because
            // we don't know the size yet. We hardcode value to zero.
            let success := call(gas(), arm, 0, 0, calldatasize(), 0, 0)
            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())
            // Pass through successful return or revert and associated data.
            if success {
              return(0, returndatasize())
            }
            revert(0, returndatasize())
          }
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      abstract contract TypeAndVersionInterface {
        function typeAndVersion() external pure virtual returns (string memory);
      }
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity ^0.8.0;
      /// @notice This interface contains the only ARM-related functions that might be used on-chain by other CCIP contracts.
      interface IARM {
        /// @notice A Merkle root tagged with the address of the commit store contract it is destined for.
        struct TaggedRoot {
          address commitStore;
          bytes32 root;
        }
        /// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.
        function isBlessed(TaggedRoot calldata taggedRoot) external view returns (bool);
        /// @notice When the ARM is "cursed", CCIP pauses until the curse is lifted.
        function isCursed() external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwner} from "../../ConfirmedOwner.sol";
      /// @title The OwnerIsCreator contract
      /// @notice A contract with helpers for basic contract ownership.
      contract OwnerIsCreator is ConfirmedOwner {
        constructor() ConfirmedOwner(msg.sender) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./ConfirmedOwnerWithProposal.sol";
      /**
       * @title The ConfirmedOwner contract
       * @notice A contract with helpers for basic contract ownership.
       */
      contract ConfirmedOwner is ConfirmedOwnerWithProposal {
        constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./interfaces/OwnableInterface.sol";
      /**
       * @title The ConfirmedOwner contract
       * @notice A contract with helpers for basic contract ownership.
       */
      contract ConfirmedOwnerWithProposal is OwnableInterface {
        address private s_owner;
        address private s_pendingOwner;
        event OwnershipTransferRequested(address indexed from, address indexed to);
        event OwnershipTransferred(address indexed from, address indexed to);
        constructor(address newOwner, address pendingOwner) {
          require(newOwner != address(0), "Cannot set owner to zero");
          s_owner = newOwner;
          if (pendingOwner != address(0)) {
            _transferOwnership(pendingOwner);
          }
        }
        /**
         * @notice Allows an owner to begin transferring ownership to a new address,
         * pending.
         */
        function transferOwnership(address to) public override onlyOwner {
          _transferOwnership(to);
        }
        /**
         * @notice Allows an ownership transfer to be completed by the recipient.
         */
        function acceptOwnership() external override {
          require(msg.sender == s_pendingOwner, "Must be proposed owner");
          address oldOwner = s_owner;
          s_owner = msg.sender;
          s_pendingOwner = address(0);
          emit OwnershipTransferred(oldOwner, msg.sender);
        }
        /**
         * @notice Get the current owner
         */
        function owner() public view override returns (address) {
          return s_owner;
        }
        /**
         * @notice validate, transfer ownership, and emit relevant events
         */
        function _transferOwnership(address to) private {
          require(to != msg.sender, "Cannot transfer to self");
          s_pendingOwner = to;
          emit OwnershipTransferRequested(s_owner, to);
        }
        /**
         * @notice validate access
         */
        function _validateOwnership() internal view {
          require(msg.sender == s_owner, "Only callable by owner");
        }
        /**
         * @notice Reverts if called by anyone other than the contract owner.
         */
        modifier onlyOwner() {
          _validateOwnership();
          _;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface OwnableInterface {
        function owner() external returns (address);
        function transferOwnership(address recipient) external;
        function acceptOwnership() external;
      }
      

      File 4 of 4: ARM
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity 0.8.19;
      import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
      import {IARM} from "./interfaces/IARM.sol";
      import {OwnerIsCreator} from "./../shared/access/OwnerIsCreator.sol";
      contract ARM is IARM, OwnerIsCreator, TypeAndVersionInterface {
        // STATIC CONFIG
        // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
        string public constant override typeAndVersion = "ARM 1.0.0";
        uint256 private constant MAX_NUM_VOTERS = 128;
        // DYNAMIC CONFIG
        struct Voter {
          // This is the address the voter should use to call voteToBless.
          address blessVoteAddr;
          // This is the address the voter should use to call voteToCurse.
          address curseVoteAddr;
          // This is the address the voter should use to call unvoteToCurse.
          address curseUnvoteAddr;
          // The weight of this voter's vote for blessing.
          uint8 blessWeight;
          // The weight of this voter's vote for cursing.
          uint8 curseWeight;
        }
        struct Config {
          Voter[] voters;
          // When the total weight of voters that have voted to bless a tagged root reaches
          // or exceeds blessWeightThreshold, the tagged root becomes blessed.
          uint16 blessWeightThreshold;
          // When the total weight of voters that have voted to curse reaches or
          // exceeds curseWeightThreshold, the ARM enters the cursed state.
          uint16 curseWeightThreshold;
        }
        struct VersionedConfig {
          Config config;
          // The version is incremented every time the config changes.
          // The initial configuration on the contract will have configVersion == 1.
          uint32 configVersion;
          // The block number at which the config was last set. Helps the offchain
          // code check that the config was set in a stable block or double-check
          // that it has the correct config by querying logs at that block number.
          uint32 blockNumber;
        }
        VersionedConfig private s_versionedConfig;
        // STATE
        struct BlesserRecord {
          // The config version at which this BlesserRecord was last set. A blesser
          // is considered active iff this configVersion equals
          // s_versionedConfig.configVersion.
          uint32 configVersion;
          uint8 weight;
          uint8 index;
        }
        mapping(address blessVoteAddr => BlesserRecord blesserRecord) private s_blesserRecords;
        struct BlessVoteProgress {
          // A BlessVoteProgress is considered invalid if weightThresholdMet is false when
          // s_versionedConfig.configVersion changes. we don't want old in-progress
          // votes to continue when we set a new config!
          // The config version at which the bless vote for a tagged root was initiated.
          uint32 configVersion;
          uint16 accumulatedWeight;
          // Care must be taken that the bitmap has as many bits as MAX_NUM_VOTERS.
          uint128 voterBitmap;
          bool weightThresholdMet;
        }
        mapping(bytes32 taggedRootHash => BlessVoteProgress blessVoteProgress) private s_blessVoteProgressByTaggedRootHash;
        // voteCount and cursesHash can be reset through unvoteToCurse, and ownerUnvoteToCurse, and may be reset through
        // setConfig if the curser is not part of the new config.
        struct CurserRecord {
          bool active;
          uint8 weight;
          uint32 voteCount;
          address curseUnvoteAddr;
          bytes32 cursesHash;
        }
        mapping(address curseVoteAddr => CurserRecord curserRecord) private s_curserRecords;
        // Maintains a per-curser set of curseIds. Entries from this mapping are
        // never cleared. Once a curseId is used it can never be reused, even after
        // an unvoteToCurse or ownerUnvoteToCurse. This is to prevent accidental
        // re-votes to curse, e.g. caused by TOCTOU issues.
        mapping(address curseVoteAddr => mapping(bytes32 curseId => bool voted)) private s_curseVotes;
        struct CurseVoteProgress {
          uint16 curseWeightThreshold;
          uint16 accumulatedWeight;
          // A curse becomes active after:
          // - accumulatedWeight becomes greater or equal than curseWeightThreshold; or
          // - the owner curses.
          // Once a curse is active, only the owner can lift it.
          bool curseActive;
        }
        CurseVoteProgress private s_curseVoteProgress;
        // AUXILLARY STRUCTS
        struct UnvoteToCurseRecord {
          address curseVoteAddr;
          bytes32 cursesHash;
          bool forceUnvote;
        }
        // EVENTS, ERRORS
        event ConfigSet(uint32 indexed configVersion, Config config);
        error InvalidConfig();
        event TaggedRootBlessed(uint32 indexed configVersion, IARM.TaggedRoot taggedRoot, uint16 accumulatedWeight);
        event TaggedRootBlessVotesReset(uint32 indexed configVersion, IARM.TaggedRoot taggedRoot, bool wasBlessed);
        event VotedToBless(uint32 indexed configVersion, address indexed voter, IARM.TaggedRoot taggedRoot, uint8 weight);
        event VotedToCurse(
          uint32 indexed configVersion,
          address indexed voter,
          uint8 weight,
          uint32 voteCount,
          bytes32 curseId,
          bytes32 cursesHash,
          uint16 accumulatedWeight
        );
        event ReusedVotesToCurse(
          uint32 indexed configVersion,
          address indexed voter,
          uint8 weight,
          uint32 voteCount,
          bytes32 cursesHash,
          uint16 accumulatedWeight
        );
        event UnvotedToCurse(
          uint32 indexed configVersion,
          address indexed voter,
          uint8 weight,
          uint32 voteCount,
          bytes32 cursesHash
        );
        event SkippedUnvoteToCurse(address indexed voter, bytes32 expectedCursesHash, bytes32 actualCursesHash);
        event OwnerCursed(uint256 timestamp);
        event Cursed(uint32 indexed configVersion, uint256 timestamp);
        // These events make it easier for offchain logic to discover that it performs
        // the same actions multiple times.
        event AlreadyVotedToBless(uint32 indexed configVersion, address indexed voter, IARM.TaggedRoot taggedRoot);
        event AlreadyBlessed(uint32 indexed configVersion, address indexed voter, IARM.TaggedRoot taggedRoot);
        event RecoveredFromCurse();
        error AlreadyVotedToCurse(address voter, bytes32 curseId);
        error InvalidVoter(address voter);
        error InvalidCurseState();
        error InvalidCursesHash(bytes32 expectedCursesHash, bytes32 actualCursesHash);
        error MustRecoverFromCurse();
        constructor(Config memory config) {
          {
            // Ensure that the bitmap is large enough to hold MAX_NUM_VOTERS.
            // We do this in the constructor because MAX_NUM_VOTERS is constant.
            BlessVoteProgress memory vp;
            vp.voterBitmap = ~uint128(0);
            assert(vp.voterBitmap >> (MAX_NUM_VOTERS - 1) >= 1);
          }
          _setConfig(config);
        }
        function _bitmapGet(uint128 bitmap, uint8 index) internal pure returns (bool) {
          assert(index < MAX_NUM_VOTERS);
          return bitmap & (uint128(1) << index) != 0;
        }
        function _bitmapSet(uint128 bitmap, uint8 index) internal pure returns (uint128) {
          assert(index < MAX_NUM_VOTERS);
          return bitmap | (uint128(1) << index);
        }
        function _bitmapCount(uint128 bitmap) internal pure returns (uint8 oneBits) {
          // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
          for (; bitmap != 0; ++oneBits) {
            bitmap &= bitmap - 1;
          }
        }
        function _taggedRootHash(IARM.TaggedRoot memory taggedRoot) internal pure returns (bytes32) {
          return keccak256(abi.encode(taggedRoot.commitStore, taggedRoot.root));
        }
        /// @param taggedRoots A tagged root is hashed as `keccak256(abi.encode(taggedRoot.commitStore
        /// /* address */, taggedRoot.root /* bytes32 */))`.
        function voteToBless(IARM.TaggedRoot[] calldata taggedRoots) external {
          // If we have an active curse, something is really wrong. Let's err on the
          // side of caution and not accept further blessings during this time of
          // uncertainty.
          if (isCursed()) revert MustRecoverFromCurse();
          uint32 configVersion = s_versionedConfig.configVersion;
          BlesserRecord memory blesserRecord = s_blesserRecords[msg.sender];
          if (blesserRecord.configVersion != configVersion) revert InvalidVoter(msg.sender);
          for (uint256 i = 0; i < taggedRoots.length; ++i) {
            IARM.TaggedRoot memory taggedRoot = taggedRoots[i];
            bytes32 taggedRootHash = _taggedRootHash(taggedRoot);
            BlessVoteProgress memory voteProgress = s_blessVoteProgressByTaggedRootHash[taggedRootHash];
            if (voteProgress.weightThresholdMet) {
              // We don't revert here because it's unreasonable to expect from the
              // voter to know exactly when to stop voting. Most likely when they
              // voted they didn't realize the threshold would be reached by the time
              // their vote was counted.
              // Additionally, there might be other tagged roots for which votes might
              // count, and we want to allow that to happen.
              emit AlreadyBlessed(configVersion, msg.sender, taggedRoot);
              continue;
            }
            if (voteProgress.configVersion != configVersion) {
              // Note that voteProgress.weightThresholdMet must be false at this point
              // If votes were received while an older config was in effect,
              // invalidate them and start from scratch.
              // If votes were never received, set the current config version.
              voteProgress = BlessVoteProgress({
                configVersion: configVersion,
                voterBitmap: 0,
                accumulatedWeight: 0,
                weightThresholdMet: false
              });
            }
            if (_bitmapGet(voteProgress.voterBitmap, blesserRecord.index)) {
              // We don't revert here because there might be other tagged roots for
              // which votes might count, and we want to allow that to happen.
              emit AlreadyVotedToBless(configVersion, msg.sender, taggedRoot);
              continue;
            }
            voteProgress.voterBitmap = _bitmapSet(voteProgress.voterBitmap, blesserRecord.index);
            voteProgress.accumulatedWeight += blesserRecord.weight;
            emit VotedToBless(configVersion, msg.sender, taggedRoot, blesserRecord.weight);
            if (voteProgress.accumulatedWeight >= s_versionedConfig.config.blessWeightThreshold) {
              voteProgress.weightThresholdMet = true;
              emit TaggedRootBlessed(configVersion, taggedRoot, voteProgress.accumulatedWeight);
            }
            s_blessVoteProgressByTaggedRootHash[taggedRootHash] = voteProgress;
          }
        }
        /// @notice Can be called by the owner to remove unintentionally voted or even blessed tagged roots in a recovery
        /// scenario. The owner must ensure that there are no in-flight transactions by ARM nodes voting for any of the
        /// taggedRoots before calling this function, as such in-flight transactions could lead to the roots becoming
        /// re-blessed shortly after the call to this function, contrary to the original intention.
        function ownerResetBlessVotes(IARM.TaggedRoot[] calldata taggedRoots) external onlyOwner {
          uint32 configVersion = s_versionedConfig.configVersion;
          for (uint256 i = 0; i < taggedRoots.length; ++i) {
            IARM.TaggedRoot memory taggedRoot = taggedRoots[i];
            bytes32 taggedRootHash = _taggedRootHash(taggedRoot);
            BlessVoteProgress memory voteProgress = s_blessVoteProgressByTaggedRootHash[taggedRootHash];
            delete s_blessVoteProgressByTaggedRootHash[taggedRootHash];
            bool wasBlessed = voteProgress.weightThresholdMet;
            if (voteProgress.configVersion == configVersion || wasBlessed) {
              emit TaggedRootBlessVotesReset(configVersion, taggedRoot, wasBlessed);
            }
          }
        }
        /// @notice Can be called by a curser to remove unintentional votes to curse.
        /// We expect this to be called very rarely, e.g. in case of a bug in the
        /// offchain code causing false voteToCurse calls.
        /// @notice Should be called from curser's corresponding curseUnvoteAddr.
        function unvoteToCurse(address curseVoteAddr, bytes32 cursesHash) external {
          CurserRecord memory curserRecord = s_curserRecords[curseVoteAddr];
          // If a curse is active, only the owner is allowed to lift it.
          if (isCursed()) revert MustRecoverFromCurse();
          if (msg.sender != curserRecord.curseUnvoteAddr) revert InvalidVoter(msg.sender);
          if (!curserRecord.active || curserRecord.voteCount == 0) revert InvalidCurseState();
          if (curserRecord.cursesHash != cursesHash) revert InvalidCursesHash(curserRecord.cursesHash, cursesHash);
          emit UnvotedToCurse(
            s_versionedConfig.configVersion,
            curseVoteAddr,
            curserRecord.weight,
            curserRecord.voteCount,
            cursesHash
          );
          curserRecord.voteCount = 0;
          curserRecord.cursesHash = 0;
          s_curserRecords[curseVoteAddr] = curserRecord;
          s_curseVoteProgress.accumulatedWeight -= curserRecord.weight;
        }
        /// @notice A vote to curse is appropriate during unhealthy blockchain conditions
        /// (eg. finality violations).
        function voteToCurse(bytes32 curseId) external {
          CurserRecord memory curserRecord = s_curserRecords[msg.sender];
          if (!curserRecord.active) revert InvalidVoter(msg.sender);
          if (s_curseVotes[msg.sender][curseId]) revert AlreadyVotedToCurse(msg.sender, curseId);
          s_curseVotes[msg.sender][curseId] = true;
          ++curserRecord.voteCount;
          curserRecord.cursesHash = keccak256(abi.encode(curserRecord.cursesHash, curseId));
          s_curserRecords[msg.sender] = curserRecord;
          CurseVoteProgress memory curseVoteProgress = s_curseVoteProgress;
          if (curserRecord.voteCount == 1) {
            curseVoteProgress.accumulatedWeight += curserRecord.weight;
          }
          // NOTE: We could pack configVersion into CurserRecord that we already load in the beginning of this function to
          // avoid the following extra storage read for it, but since voteToCurse is not on the hot path we'd rather keep
          // things simple.
          uint32 configVersion = s_versionedConfig.configVersion;
          emit VotedToCurse(
            configVersion,
            msg.sender,
            curserRecord.weight,
            curserRecord.voteCount,
            curseId,
            curserRecord.cursesHash,
            curseVoteProgress.accumulatedWeight
          );
          if (
            !curseVoteProgress.curseActive && curseVoteProgress.accumulatedWeight >= curseVoteProgress.curseWeightThreshold
          ) {
            curseVoteProgress.curseActive = true;
            emit Cursed(configVersion, block.timestamp);
          }
          s_curseVoteProgress = curseVoteProgress;
        }
        /// @notice Enables the owner to immediately have the system enter the cursed state.
        function ownerCurse() external onlyOwner {
          emit OwnerCursed(block.timestamp);
          if (!s_curseVoteProgress.curseActive) {
            s_curseVoteProgress.curseActive = true;
            emit Cursed(s_versionedConfig.configVersion, block.timestamp);
          }
        }
        /// @notice Enables the owner to remove curse votes. After the curse votes are removed,
        /// this function will check whether the curse is still valid and restore the uncursed state if possible.
        /// This function also enables the owner to lift a curse created through ownerCurse.
        function ownerUnvoteToCurse(UnvoteToCurseRecord[] calldata unvoteRecords) external onlyOwner {
          for (uint256 i = 0; i < unvoteRecords.length; ++i) {
            UnvoteToCurseRecord memory unvoteRecord = unvoteRecords[i];
            CurserRecord memory curserRecord = s_curserRecords[unvoteRecord.curseVoteAddr];
            // Owner can avoid the curses hash check by setting forceUnvote to true, in case
            // a malicious curser is flooding the system with votes to curse with the
            // intention to disallow the owner to clear their curse.
            if (!unvoteRecord.forceUnvote && curserRecord.cursesHash != unvoteRecord.cursesHash) {
              emit SkippedUnvoteToCurse(unvoteRecord.curseVoteAddr, curserRecord.cursesHash, unvoteRecord.cursesHash);
              continue;
            }
            if (!curserRecord.active || curserRecord.voteCount == 0) continue;
            emit UnvotedToCurse(
              s_versionedConfig.configVersion,
              unvoteRecord.curseVoteAddr,
              curserRecord.weight,
              curserRecord.voteCount,
              curserRecord.cursesHash
            );
            curserRecord.voteCount = 0;
            curserRecord.cursesHash = 0;
            s_curserRecords[unvoteRecord.curseVoteAddr] = curserRecord;
            s_curseVoteProgress.accumulatedWeight -= curserRecord.weight;
          }
          if (
            s_curseVoteProgress.curseActive &&
            s_curseVoteProgress.accumulatedWeight < s_curseVoteProgress.curseWeightThreshold
          ) {
            s_curseVoteProgress.curseActive = false;
            emit RecoveredFromCurse();
            // Invalidate all in-progress votes to bless by bumping the config version.
            // They might have been based on false information about the source chain
            // (e.g. in case of a finality violation).
            _setConfig(s_versionedConfig.config);
          }
        }
        /// @notice Will revert in case a curse is active. To avoid accidentally invalidating an in-progress curse vote, it
        /// may be advisable to remove voters one-by-one over time, rather than many at once.
        /// @dev The gas use of this function varies depending on the number of curse votes that are active. When calling this
        /// function, be sure to include a gas cushion to account for curse votes that may occur between your transaction
        /// being sent and mined.
        function setConfig(Config memory config) external onlyOwner {
          _setConfig(config);
        }
        /// @inheritdoc IARM
        function isBlessed(IARM.TaggedRoot calldata taggedRoot) external view override returns (bool) {
          return s_blessVoteProgressByTaggedRootHash[_taggedRootHash(taggedRoot)].weightThresholdMet;
        }
        /// @inheritdoc IARM
        function isCursed() public view override returns (bool) {
          return s_curseVoteProgress.curseActive;
        }
        /// @notice Config version might be incremented for many reasons, including
        /// recovery from a curse and a regular config change.
        function getConfigDetails() external view returns (uint32 version, uint32 blockNumber, Config memory config) {
          version = s_versionedConfig.configVersion;
          blockNumber = s_versionedConfig.blockNumber;
          config = s_versionedConfig.config;
        }
        /// @return blessVoteAddrs addresses of voters, will be empty if voting took place with an older config version
        /// @return accumulatedWeight sum of weights of voters, will be zero if voting took place with an older config version
        /// @return blessed will be accurate regardless of when voting took place
        /// @dev This is a helper method for offchain code so efficiency is not really a concern.
        function getBlessProgress(
          IARM.TaggedRoot calldata taggedRoot
        ) external view returns (address[] memory blessVoteAddrs, uint16 accumulatedWeight, bool blessed) {
          bytes32 taggedRootHash = _taggedRootHash(taggedRoot);
          BlessVoteProgress memory progress = s_blessVoteProgressByTaggedRootHash[taggedRootHash];
          blessed = progress.weightThresholdMet;
          if (progress.configVersion == s_versionedConfig.configVersion) {
            accumulatedWeight = progress.accumulatedWeight;
            uint128 bitmap = progress.voterBitmap;
            blessVoteAddrs = new address[](_bitmapCount(bitmap));
            Voter[] memory voters = s_versionedConfig.config.voters;
            uint256 j = 0;
            for (uint256 i = 0; i < voters.length; ++i) {
              if (_bitmapGet(bitmap, s_blesserRecords[voters[i].blessVoteAddr].index)) {
                blessVoteAddrs[j] = voters[i].blessVoteAddr;
                ++j;
              }
            }
          }
        }
        /// @dev This is a helper method for offchain code so efficiency is not really a concern.
        function getCurseProgress()
          external
          view
          returns (
            address[] memory curseVoteAddrs,
            uint32[] memory voteCounts,
            bytes32[] memory cursesHashes,
            uint16 accumulatedWeight,
            bool cursed
          )
        {
          accumulatedWeight = s_curseVoteProgress.accumulatedWeight;
          cursed = s_curseVoteProgress.curseActive;
          uint256 numCursers;
          Voter[] memory voters = s_versionedConfig.config.voters;
          for (uint256 i = 0; i < voters.length; ++i) {
            CurserRecord memory curserRecord = s_curserRecords[voters[i].curseVoteAddr];
            if (curserRecord.voteCount > 0) {
              ++numCursers;
            }
          }
          curseVoteAddrs = new address[](numCursers);
          voteCounts = new uint32[](numCursers);
          cursesHashes = new bytes32[](numCursers);
          uint256 j = 0;
          for (uint256 i = 0; i < voters.length; ++i) {
            address curseVoteAddr = voters[i].curseVoteAddr;
            CurserRecord memory curserRecord = s_curserRecords[curseVoteAddr];
            if (curserRecord.voteCount > 0) {
              curseVoteAddrs[j] = curseVoteAddr;
              voteCounts[j] = curserRecord.voteCount;
              cursesHashes[j] = curserRecord.cursesHash;
              ++j;
            }
          }
        }
        function _validateConfig(Config memory config) internal pure returns (bool) {
          if (
            config.voters.length == 0 ||
            config.voters.length > MAX_NUM_VOTERS ||
            config.blessWeightThreshold == 0 ||
            config.curseWeightThreshold == 0
          ) {
            return false;
          }
          uint256 totalBlessWeight = 0;
          uint256 totalCurseWeight = 0;
          address[] memory allAddrs = new address[](3 * config.voters.length);
          for (uint256 i = 0; i < config.voters.length; ++i) {
            Voter memory voter = config.voters[i];
            if (
              voter.blessVoteAddr == address(0) ||
              voter.curseVoteAddr == address(0) ||
              voter.curseUnvoteAddr == address(0) ||
              (voter.blessWeight == 0 && voter.curseWeight == 0)
            ) {
              return false;
            }
            allAddrs[3 * i + 0] = voter.blessVoteAddr;
            allAddrs[3 * i + 1] = voter.curseVoteAddr;
            allAddrs[3 * i + 2] = voter.curseUnvoteAddr;
            totalBlessWeight += voter.blessWeight;
            totalCurseWeight += voter.curseWeight;
          }
          for (uint256 i = 0; i < allAddrs.length; ++i) {
            address allAddrs_i = allAddrs[i];
            for (uint256 j = i + 1; j < allAddrs.length; ++j) {
              if (allAddrs_i == allAddrs[j]) {
                return false;
              }
            }
          }
          return totalBlessWeight >= config.blessWeightThreshold && totalCurseWeight >= config.curseWeightThreshold;
        }
        function _setConfig(Config memory config) private {
          if (isCursed()) revert MustRecoverFromCurse();
          if (!_validateConfig(config)) revert InvalidConfig();
          Config memory oldConfig = s_versionedConfig.config;
          // We can't directly assign s_versionedConfig.config to config
          // because copying a memory array into storage is not supported.
          {
            s_versionedConfig.config.blessWeightThreshold = config.blessWeightThreshold;
            s_versionedConfig.config.curseWeightThreshold = config.curseWeightThreshold;
            while (s_versionedConfig.config.voters.length != 0) {
              Voter memory voter = s_versionedConfig.config.voters[s_versionedConfig.config.voters.length - 1];
              delete s_blesserRecords[voter.blessVoteAddr];
              s_curserRecords[voter.curseVoteAddr].active = false;
              s_versionedConfig.config.voters.pop();
            }
            for (uint256 i = 0; i < config.voters.length; ++i) {
              s_versionedConfig.config.voters.push(config.voters[i]);
            }
          }
          ++s_versionedConfig.configVersion;
          uint32 configVersion = s_versionedConfig.configVersion;
          for (uint8 i = 0; i < config.voters.length; ++i) {
            Voter memory voter = config.voters[i];
            s_blesserRecords[voter.blessVoteAddr] = BlesserRecord({
              configVersion: configVersion,
              index: i,
              weight: voter.blessWeight
            });
            s_curserRecords[voter.curseVoteAddr] = CurserRecord({
              active: true,
              weight: voter.curseWeight,
              curseUnvoteAddr: voter.curseUnvoteAddr,
              voteCount: s_curserRecords[voter.curseVoteAddr].voteCount,
              cursesHash: s_curserRecords[voter.curseVoteAddr].cursesHash
            });
          }
          s_versionedConfig.blockNumber = uint32(block.number);
          emit ConfigSet(configVersion, config);
          CurseVoteProgress memory newCurseVoteProgress = CurseVoteProgress({
            curseWeightThreshold: config.curseWeightThreshold,
            accumulatedWeight: 0,
            curseActive: false
          });
          // Retain votes for the cursers who are still part of the new config and delete records for the cursers who are not.
          for (uint8 i = 0; i < oldConfig.voters.length; ++i) {
            // We could be more efficient with this but since this is only for
            // setConfig it will do for now.
            address curseVoteAddr = oldConfig.voters[i].curseVoteAddr;
            CurserRecord memory curserRecord = s_curserRecords[curseVoteAddr];
            if (!curserRecord.active) {
              delete s_curserRecords[curseVoteAddr];
            } else if (curserRecord.active && curserRecord.voteCount > 0) {
              newCurseVoteProgress.accumulatedWeight += curserRecord.weight;
              emit ReusedVotesToCurse(
                configVersion,
                curseVoteAddr,
                curserRecord.weight,
                curserRecord.voteCount,
                curserRecord.cursesHash,
                newCurseVoteProgress.accumulatedWeight
              );
            }
          }
          newCurseVoteProgress.curseActive =
            newCurseVoteProgress.accumulatedWeight >= newCurseVoteProgress.curseWeightThreshold;
          if (newCurseVoteProgress.curseActive) {
            emit Cursed(configVersion, block.timestamp);
          }
          s_curseVoteProgress = newCurseVoteProgress;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      abstract contract TypeAndVersionInterface {
        function typeAndVersion() external pure virtual returns (string memory);
      }
      // SPDX-License-Identifier: BUSL-1.1
      pragma solidity ^0.8.0;
      /// @notice This interface contains the only ARM-related functions that might be used on-chain by other CCIP contracts.
      interface IARM {
        /// @notice A Merkle root tagged with the address of the commit store contract it is destined for.
        struct TaggedRoot {
          address commitStore;
          bytes32 root;
        }
        /// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.
        function isBlessed(TaggedRoot calldata taggedRoot) external view returns (bool);
        /// @notice When the ARM is "cursed", CCIP pauses until the curse is lifted.
        function isCursed() external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwner} from "../../ConfirmedOwner.sol";
      /// @title The OwnerIsCreator contract
      /// @notice A contract with helpers for basic contract ownership.
      contract OwnerIsCreator is ConfirmedOwner {
        constructor() ConfirmedOwner(msg.sender) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./ConfirmedOwnerWithProposal.sol";
      /**
       * @title The ConfirmedOwner contract
       * @notice A contract with helpers for basic contract ownership.
       */
      contract ConfirmedOwner is ConfirmedOwnerWithProposal {
        constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./interfaces/OwnableInterface.sol";
      /**
       * @title The ConfirmedOwner contract
       * @notice A contract with helpers for basic contract ownership.
       */
      contract ConfirmedOwnerWithProposal is OwnableInterface {
        address private s_owner;
        address private s_pendingOwner;
        event OwnershipTransferRequested(address indexed from, address indexed to);
        event OwnershipTransferred(address indexed from, address indexed to);
        constructor(address newOwner, address pendingOwner) {
          require(newOwner != address(0), "Cannot set owner to zero");
          s_owner = newOwner;
          if (pendingOwner != address(0)) {
            _transferOwnership(pendingOwner);
          }
        }
        /**
         * @notice Allows an owner to begin transferring ownership to a new address,
         * pending.
         */
        function transferOwnership(address to) public override onlyOwner {
          _transferOwnership(to);
        }
        /**
         * @notice Allows an ownership transfer to be completed by the recipient.
         */
        function acceptOwnership() external override {
          require(msg.sender == s_pendingOwner, "Must be proposed owner");
          address oldOwner = s_owner;
          s_owner = msg.sender;
          s_pendingOwner = address(0);
          emit OwnershipTransferred(oldOwner, msg.sender);
        }
        /**
         * @notice Get the current owner
         */
        function owner() public view override returns (address) {
          return s_owner;
        }
        /**
         * @notice validate, transfer ownership, and emit relevant events
         */
        function _transferOwnership(address to) private {
          require(to != msg.sender, "Cannot transfer to self");
          s_pendingOwner = to;
          emit OwnershipTransferRequested(s_owner, to);
        }
        /**
         * @notice validate access
         */
        function _validateOwnership() internal view {
          require(msg.sender == s_owner, "Only callable by owner");
        }
        /**
         * @notice Reverts if called by anyone other than the contract owner.
         */
        modifier onlyOwner() {
          _validateOwnership();
          _;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface OwnableInterface {
        function owner() external returns (address);
        function transferOwnership(address recipient) external;
        function acceptOwnership() external;
      }