Farseen0/scar-pairs huggingface dataset:
LOW severity: `swEXIT::setWithdrawRequestMaximum` and `setWithdrawRequestMinimum` lacking validation can lead to a state where `withdrawRequestMinimum > withdrawRequestMaximum`. **Description:** Invariant `withdrawRequestMinimum <= withdrawRequestMaximum` must always hold, however this is not checked when new min/max withdraw values are set. Hence it is possible to enter a non-sensical state where `withdrawRequestMinimum > withdrawRequestMaximum`.

**Recommended...

Vulnerable code:
function setWithdrawRequestMaximum(
    uint256 _withdrawRequestMaximum
  ) external override checkRole(SwellLib.PLATFORM_ADMIN) {
+   require(withdrawRequestMinimum <= _withdrawRequestMaximum);

    emit WithdrawalRequestMaximumUpdated(
      withdrawRequestMaximum,
      _withdrawRequestMaximum
    );
    withdrawRequestMaximum = _withdrawRequestMaximum;
  }

  function setWithdrawRequestMinimum(
    uint256 _withdrawRequestMinimum
  ) external override checkRole(SwellLib.PLATFORM_ADMIN) {
+   require(_withdrawRequestMinimum <= withdrawRequestMaximum);

    emit WithdrawalRequestMinimumUpdated(
      withdrawRequestMinimum,
      _withdrawRequestMinimum
    );
    withdrawRequestMinimum = _withdrawRequestMinimum;
  }
LOW severity: `swExit::getProcessedRateForTokenId` returns `true` with valid `processedRate` for non-existent `tokenId` input. **Description:** `swExit::getProcessedRateForTokenId` returns `true` with valid `processedRate` for non-existent `tokenId` input.

**Impact:** This `public` function can return valid output for invalid input. Currently it only appears to be used by `finalizeWithdrawal` where this behavior does not.....

Vulnerable code:
it("Should return false for isProcessed when tokens have been processed but this token doesn't exist", async () => {
    await createWithdrawRequests(Deployer, 5);

    await swEXIT_Deployer.processWithdrawals(4, parseEther("1"));

    // @audit this test fails
    expect(await getProcessedRateForTokenId(0)).eql({
      isProcessed: false,               // @audit returns true
      processedRate: BigNumber.from(0), // @audit returns > 0
    });
  });
HIGH severity: Check for staleness of data when fetching Proof of Reserves via Chainlink `Swell ETH PoR` Oracle. **Description:** `RepricingOracle::_assertRepricingSnapshotValidity` [uses](https://github.com/SwellNetwork/v3-contracts-lst/tree/a95ea7942ba895ae84845ab7fec1163d667bee38/contracts/implementations/RepricingOracle.sol#L329-L331) the `Swell ETH PoR` Chainlink Proof Of Reserves Oracle to fetch an...

Vulnerable code:
// @audit no staleness check
(, int256 externallyReportedV3Balance, , , ) = AggregatorV3Interface(
  ExternalV3ReservesPoROracle
).latestRoundData();
HIGH severity: `swETH::reprice` may run out of gas or become exorbitantly expensive when scaling to large number of validator operators due to iterating over them all. **Description:** `swETH::reprice` [loops](https://github.com/SwellNetwork/v3-contracts-lst/tree/a95ea7942ba895ae84845ab7fec1163d667bee38/contracts/implementations/swETH.sol#L303-L321) through all validator operators to pay out their share of rewards:

If Swell scales to a large number of...

Vulnerable code:
// @audit may run out of gas for larger number of validator operators
// or make repricing exorbitantly expensive
for (uint128 i = 1; i <= totalOperators; ) {
  (
    address rewardAddress,
    uint256 operatorActiveValidators
  ) = nodeOperatorRegistry.getRewardDetailsForOperatorId(i);

  if (operatorActiveValidators != 0) {
    uint256 operatorsRewardShare = wrap(operatorActiveValidators)
      .div(totalActiveValidators)
      .mul(wrap(nodeOperatorRewards))
      .unwrap();

    _transfer(address(this), rewardAddress, operatorsRewardShare);
  }

  // Will never overflow as the total operators are capped at uint128
  unchecked {
    ++i;
  }
}
LOW severity: `NodeOperatorRegistry::updateOperatorControllingAddress` allows to override `_newOperatorAddress` if its address is already assigned to an operator ID. **Description:** Current implementation does not check if the new assigned address has already been assigned to an operator ID. As a consequence, its current value can be over written in mapping `getOperatorIdForAddress`, and `getOperatorForOperatorId` will have 2 operator IDs pointing to the same.....

Vulnerable code:
it("Should revert updating operator controlling address to existing address", async () => {
      // create another operator
      await NodeOperatorRegistry_Deployer.addOperator(
        "OPERATOR_2",
        NewOperator.address,
        NewOperator.address
      );

      // attempt to update first operator's controlling address to be
      // the same as the newly created operator - should revert but doesn't
      await NodeOperatorRegistry_Deployer.updateOperatorControllingAddress(
        Operator.address,
        NewOperator.address
      );
    });

function updateOperatorControllingAddress(
    address _operatorAddress,
    address _newOperatorAddress
  )
    external
    override
    checkRole(SwellLib.PLATFORM_ADMIN)
    checkZeroAddress(_newOperatorAddress)
  {

    if (_operatorAddress == _newOperatorAddress) {
        revert CannotSetOperatorControllingAddressToSameAddress();
    }
+   if(getOperatorIdForAddress[_newOperatorAddress] != 0){
+       revert CannotUpdateOperatorControllingAddressToAlreadyAssignedAddress();
+   }

    uint128 operatorId = _getOperatorIdSafe(_operatorAddress);

    getOperatorIdForAddress[_newOperatorAddress] = operatorId;
    getOperatorForOperatorId[operatorId]
      .controllingAddress = _newOperatorAddress;

    delete getOperatorIdForAddress[_operatorAddress];
  }
LOW severity: Allowing anyone to finalize any withdrawal can lead to integration problems for smart contract allowed to receive ETH. **Description:** Current implementation of `swEXIT::finalizeWithdrawal` allows anyone to finalize any withdrawal request which is already processed. However this design decision make the strong assumption that an NFT owner always wants to finalize a withdrawal, which might not be always the...

Vulnerable code:
function finalizeWithdrawal(uint256 tokenId) external override {
        if (AccessControlManager.withdrawalsPaused()) {
        revert WithdrawalsPaused();
        }

        address owner = _ownerOf(tokenId);

-       if (owner == address(0)) {
-           revert WithdrawalRequestDoesNotExist();
+       if (owner == msg.sender) {
+           revert WithdrawalRequestFinalizationOnlyAllowedForNFTOwner();
        }
HIGH severity: Repricing with small rewards results in an invalid state where `ETH` reserves increase, `swETH to ETH` exchange rate increases, but no rewards are paid out to operators or treasury. **Description:** Invariant fuzzing used repricings with small rewards to reach an invalid state where `ETH` reserves increase, `swETH : ETH` exchange rate increases, but no rewards are paid out to operators or treasury.

**Proof of Concept:** During repricing:
1) there is no minimum value enforced.....

Vulnerable code:
it("audit small rewards not distributed while reserves and exchange rate increasing", async () => {
      const swellTreasuryRewardPercentage = parseEther("0.1");

      await swETH_Deployer.setSwellTreasuryRewardPercentage(
        swellTreasuryRewardPercentage
      );

      await swETH_Deployer.deposit({
        value: parseEther("1000"),
      });
      const preRewardETHReserves = parseEther("1100");

      const swETHSupply = parseEther("1000");

      const ethRewards = parseUnits("1", "wei");

      const swellTreasuryPre = await swETH_Deployer.balanceOf(SwellTreasury.address);
      const ethReservesPre = await swETH_Deployer.lastRepriceETHReserves();
      const rateBefore = await swETH_Deployer.swETHToETHRate();

      swETH_Bot.reprice(
          preRewardETHReserves,
          ethRewards,
          swETH_Deployer.totalSupply());

      const swellTreasuryPost = await swETH_Deployer.balanceOf(SwellTreasury.address);
      const ethReservesPost = await swETH_Deployer.lastRepriceETHReserves();
      const rateAfter = await swETH_Deployer.swETHToETHRate();

      // no rewards distributed to treasury
      expect(swellTreasuryPre).eq(swellTreasuryPost);

      // exchange rate increases
      expect(rateBefore).lt(rateAfter);

      // reserves increase
      expect(ethReservesPre).lt(ethReservesPost);

      // repricing using small `_newETHRewards` can lead to increasing reserves
      // and increasing exchange rate without reward payouts
    });
HIGH severity: Precision loss in `swETH::_deposit` from unnecessary hidden division before multiplication. **Description:** `swETH::_deposit` [L170](https://github.com/SwellNetwork/v3-contracts-lst/blob/a95ea7942ba895ae84845ab7fec1163d667bee38/contracts/implementations/swETH.sol#L170) contains a hidden unnecessary [division before...

Vulnerable code:
uint256 swETHAmount = wrap(msg.value).mul(_ethToSwETHRate()).unwrap();
// @audit expanding this out
// wrap(msg.value).mul(_ethToSwETHRate()).unwrap();
// wrap(msg.value).mul(wrap(1 ether).div(_swETHToETHRate())).unwrap();

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {UD60x18, wrap} from "@prb/math/src/UD60x18.sol";

import "forge-std/Test.sol";

// run from base project directory with:
// (fuzz test) forge test --match-test FuzzMint -vvv
// (hardcoded) forge test --match-test HardcodedMint -vvv
contract MintTest is Test {

    uint256 private constant SWETH_ETH_RATE = 1050754209601187151; //as of 2024-02-15

    function _mintOriginal(uint256 inputAmount) private pure returns(uint256) {
        // hidden division before multiplication
        // wrap(inputAmount).mul(_ethToSwETHRate()).unwrap();
        // wrap(inputAmount).mul(wrap(1 ether).div(_swETHToETHRate())).unwrap()

        return wrap(inputAmount).mul(wrap(1 ether).div(wrap(SWETH_ETH_RATE))).unwrap();
    }

    function _mintFixed(uint256 inputAmount) private pure returns(uint256) {
        // refactor to perform multiplication before division
        // wrap(inputAmount).mul(wrap(1 ether)).div(_swETHToETHRate()).unwrap();

        return wrap(inputAmount).mul(wrap(1 ether)).div(wrap(SWETH_ETH_RATE)).unwrap();
    }

    function test_FuzzMint(uint256 inputAmount) public pure {
        uint256 resultOriginal = _mintOriginal(inputAmount);
        uint256 resultFixed    = _mintFixed(inputAmount);

        assert(resultOriginal == resultFixed);
    }

    function test_HardcodedMint() public {
        // found by fuzzer
        console.log(_mintFixed(3656923177187149889) - _mintOriginal(3656923177187149889)); // 1

        // 100 eth
        console.log(_mintFixed(100 ether) - _mintOriginal(100 ether)); // 21

        // 1000 eth
        console.log(_mintFixed(1000 ether) - _mintOriginal(1000 ether)); // 215

        // 10000 eth
        console.log(_mintFixed(10000 ether) - _mintOriginal(10000 ether)); // 2159
    }
}

uint256 swETHAmount = wrap(msg.value).mul(wrap(1 ether)).div(_swETHToETHRate()).unwrap();
HIGH severity: Use Checks-Effects-Interactions pattern in `swEXIT::createWithdrawRequest`. **Description:** The current implementation uses [`_safeMint`](https://github.com/SwellNetwork/v3-contracts-lst/blob/a95ea7942ba895ae84845ab7fec1163d667bee38/contracts/implementations/swEXIT.sol#L209) before modifying state variables:
* `withdrawalRequests[tokenId] `
* `exitingETH`
*...

Vulnerable code:
function createWithdrawRequest(
    uint256 amount
  ) external override checkWhitelist(msg.sender) {
    if (AccessControlManager.withdrawalsPaused()) {
      revert WithdrawalsPaused();
    }

    if (amount < withdrawRequestMinimum) {
      revert WithdrawRequestTooSmall(amount, withdrawRequestMinimum);
    }

    if (amount > withdrawRequestMaximum) {
      revert WithdrawRequestTooLarge(amount, withdrawRequestMaximum);
    }

    IswETH swETH = AccessControlManager.swETH();
    swETH.transferFrom(msg.sender, address(this), amount);

    // Burn the tokens first to prevent reentrancy and to validate they own the requested amount of swETH
    swETH.burn(amount);

-   uint256 tokenId = _lastTokenIdCreated + 1; // Start off at 1
+   uint256 tokenId = ++_lastTokenIdCreated; // Starts off at 1

-   _safeMint(msg.sender, tokenId);

    uint256 lastTokenIdProcessed = getLastTokenIdProcessed();

    uint256 rateWhenCreated = AccessControlManager.swETH().swETHToETHRate();

    withdrawalRequests[tokenId] = WithdrawRequest({
      amount: amount,
      timestamp: block.timestamp,
      lastTokenIdProcessed: lastTokenIdProcessed,
      rateWhenCreated: rateWhenCreated
    });

    exitingETH += wrap(amount).mul(wrap(rateWhenCreated)).unwrap();
-   _lastTokenIdCreated = tokenId;
+   _safeMint(msg.sender, tokenId);

    emit WithdrawRequestCreated(
      tokenId,
      amount,
      block.timestamp,
      lastTokenIdProcessed,
      rateWhenCreated,
      msg.sender
    );

  }
HIGH severity: Refactor identical code in `NodeOperatorRegistry::getNextValidatorDetails`. **Description:** The bodies of these two `else if` [branches](https://github.com/SwellNetwork/v3-contracts-lst/blob/a95ea7942ba895ae84845ab7fec1163d667bee38/contracts/implementations/NodeOperatorRegistry.sol#L151-L162) are identical:

Hence the code can be simplified to:

**Swell:** Fixed in...

Vulnerable code:
} else if (foundOperatorId == 0) {
  // If no operator has been found yet set the smallest operator active keys to the current operator
  smallestOperatorActiveKeys = operatorActiveKeys;

  foundOperatorId = operatorId;

  // If the current operator has less keys than the smallest operator active keys, then we want to use this operator
} else if (smallestOperatorActiveKeys > operatorActiveKeys) {
  smallestOperatorActiveKeys = operatorActiveKeys;

  foundOperatorId = operatorId;
}

// If no operator has been found yet set the smallest operator active keys to the current operator
// If the current operator has less keys than the smallest operator active keys, then we want to use this operator
} else if (foundOperatorId == 0 ||
           smallestOperatorActiveKeys > operatorActiveKeys) {
  smallestOperatorActiveKeys = operatorActiveKeys;
  foundOperatorId = operatorId;
}
HIGH severity: `StrategyPassiveManagerUniswap` gives ERtoken allowances to `unirouter` but doesn't remove allowances when `unirouter` is updated. **Description:** `StrategyPassiveManagerUniswap` gives ERC20 token [allowances](https://github.com/beefyfinance/experiments/blob/14a313b76888581b05d42b6f7b6097c79f3e65c6/contracts/protocol/concliq/uniswap/StrategyPassiveManagerUniswap.sol#L745-L748) to `unirouter`:

`unirouter` is inherited from...

Vulnerable code:
function _giveAllowances() private {
    IERC20Metadata(lpToken0).forceApprove(unirouter, type(uint256).max);
    IERC20Metadata(lpToken1).forceApprove(unirouter, type(uint256).max);
}

function setUnirouter(address _unirouter) external onlyOwner {
    unirouter = _unirouter;
    emit SetUnirouter(_unirouter);
}
LOW severity: Owner of `StrategyPassiveManagerUniswap` can rug-pull users' deposited tokens by manipulating `onlyCalmPeriods` parameters. **Description:** While `StrategyPassiveManagerUniswap` does have some permissioned roles, one of the attack paths we were asked to check was that the permissioned roles could not rug-pull the users' deposited tokens. There is a way that the owner of the `StrategyPassiveManagerUniswap` contract...

Vulnerable code:
// @audit `price` is derived from `pool.slot0`
shares = _amount1 + (_amount0 * price / PRECISION);
LOW severity: `_onlyCalmPeriods` does not consider MIN/MAX ticks, which can DOS deposit, withdraw and harvest in edge cases. **Description:** In Uniswap V3 liquidity providers can only provide liquidity between price ranges `[1.0001^{MIN_ TICK};1.0001^{MAX_TICK})`. Therefore these are the min and max prices.

If `twapTick - maxTickDeviationNegative < MIN_TICK`, this function would revert even if `tick` has been the same.....

Vulnerable code:
function _onlyCalmPeriods() private view {
        int24 tick = currentTick();
        int56 twapTick = twap();

        if(
            twapTick - maxTickDeviationNegative > tick  ||
            twapTick + maxTickDeviationPositive < tick) revert NotCalm();
    }

+   const int56 MIN_TICK = -887272;
+   const int56 MAX_TICK = 887272;
    function _onlyCalmPeriods() private view {
        int24 tick = currentTick();
        int56 twapTick = twap();

+       int56 minCalmTick = max(twapTick - maxTickDeviationNegative, MIN_TICK);
+       int56 maxCalmTick = min(twapTick - maxTickDeviationPositive, MAX_TICK);

        if(
-           twapTick - maxTickDeviationNegative > tick  ||
-           twapTick + maxTickDeviationPositive < tick) revert NotCalm();
+           minCalmTick > tick  ||
+           maxCalmTick < tick) revert NotCalm();
    }
HIGH severity: `StrategyPassiveManagerUniswap::price` will revert due to overflow for large but valid `sqrtPriceX96`. **Description:** The maximum value of `sqrtPriceX96` is [1461446703485210103287273052203988822378723970342](https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/libraries/TickMath.sol#L16) but `StrategyPassiveManagerUniswap::price` will revert due to overflow...

Vulnerable code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../src/FullMath.sol";
import "forge-std/Test.sol";

// run from base project directory with:
// forge test --match-contract PriceTest
contract PriceTest is Test {

    uint256 private constant PRECISION = 1e36;

    function price(uint160 sqrtPriceX96) internal pure returns (uint256 _price) {
        _price = FullMath.mulDiv(uint256(sqrtPriceX96) ** 2, PRECISION, (2 ** 192));
    }

    function test_price(uint160 sqrtPriceX96) external {
        price(sqrtPriceX96);
    }
}

encountered 1 failing test in test/PriceTest.t.sol:PriceTest
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11); counterexample:
calldata=0x4f3b91450000000000000000000000000000000100000000000000000000000000000000
args=[340282366920938463463374607431768211456 [3.402e38]]] test_price(uint160)
(runs: 3, μ: 1319, ~: 1421)

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../src/FullMath.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import "forge-std/Test.sol";

// run from base project directory with:
// forge test --match-contract PriceTest

contract PriceTest is Test {

    uint256 private constant PRECISION = 1e36;

    function price(uint160 sqrtPriceX96) internal pure returns (uint256 _price) {
        _price = FullMath.mulDiv(uint256(sqrtPriceX96) ** 2, PRECISION, (2 ** 192));
    }

    function newPrice(uint160 sqrtPriceX96) internal pure returns (uint256 _price) {
        _price = FullMath.mulDiv(uint256(sqrtPriceX96), Math.sqrt(PRECISION), (2 ** 96)) ** 2;
    }

    function test_price(uint160 sqrtPriceX96) external {
        assertEq(price(sqrtPriceX96), newPrice(sqrtPriceX96));
    }
}
HIGH severity: Withdraw can return zero tokens while burning a positive amount of shares. **Description:** Invariant fuzzing found an edge-case where a user could burn an amount of shares > 0 but receive zero output tokens. The cause appears to be a rounding down to zero precision loss for small `_shares` value in `BeefyVaultConcLiq::withdraw`...

Vulnerable code:
uint256 _amount0 = (_bal0 * _shares) / _totalSupply;
uint256 _amount1 = (_bal1 * _shares) / _totalSupply;

if (_amount0 < _minAmount0 || _amount1 < _minAmount1 ||
   (_amount0 == 0 && _amount1 == 0)) revert TooMuchSlippage();
HIGH severity: Use existing `price` function in `StrategyPassiveManagerUniswap::_setAltTick`. **Description:** `StrategyPassiveManagerUniswap` has a `price` function that [converts](https://github.com/beefyfinance/experiments/blob/14a313b76888581b05d42b6f7b6097c79f3e65c6/contracts/protocol/concliq/uniswap/StrategyPassiveManagerUniswap.sol#L534-L537) `sqrtPriceX96` returned by uniswap...

Vulnerable code:
if (bal0 > 0) {
    amount0 = bal0 * price() / PRECISION;
}
HIGH severity: Use existing `available` function in `BeefyVaultConcLiq::balances`. **Description:** `BeefyVaultConcLiq` has an `available` function that [returns](https://github.com/beefyfinance/experiments/blob/14a313b76888581b05d42b6f7b6097c79f3e65c6/contracts/protocol/concliq/vault/BeefyVaultConcLiq.sol#L91-L94) the token balances held by the vault contract.

Refactor...

Vulnerable code:
(uint256 stratBal0, uint256 stratBal1) = IStrategyConcLiq(strategy).balances();
(uint256 vaultBal0, uint256 vaultBal1) = available();
return (stratBal0 + vaultBal0, stratBal1 + vaultBal1);
LOW severity: `public` functions not used internally could be marked `external`. **Description:** `public` functions not used internally could be marked `external`:

- Found in contracts/protocol/beefy/StratFeeManagerInitializable.sol [Line: 190](contracts/protocol/beefy/StratFeeManagerInitializable.sol#L190)

	

- Found in...

Vulnerable code:
function lockedProfit() public virtual view returns (uint256 locked0, uint256 locked1) {

function price() public view returns (uint256 _price) {

function lpToken0ToNative() public view returns (address[] memory) {
HIGH severity: Avoid unnecessary initialization to zero in `BeefyVaultConcLiq::deposit`. **Description:** `BeefyVaultConcLiq::deposit` declares the `shares` variable on [L127](https://github.com/beefyfinance/experiments/blob/14a313b76888581b05d42b6f7b6097c79f3e65c6/contracts/protocol/concliq/vault/BeefyVaultConcLiq.sol#L157) initializing it to zero even though the `shares` variable is.....

Vulnerable code:
uint256 shares = _amount1 + (_amount0 * price / PRECISION);
HIGH severity: Fail fast in `BeefyVaultConcLiq:withdraw`. **Description:** In `BeefyVaultConcLiq:withdraw` the `minAmount0` and `minAmount1` [slippage check](https://github.com/beefyfinance/experiments/blob/14a313b76888581b05d42b6f7b6097c79f3e65c6/contracts/protocol/concliq/vault/BeefyVaultConcLiq.sol#L225) should be before the call to...

Vulnerable code:
uint256 _amount0 = (_bal0 * _shares) / _totalSupply;
uint256 _amount1 = (_bal1 * _shares) / _totalSupply;

// @audit fail fast
if (_amount0 < _minAmount0 || _amount1 < _minAmount1) revert TooMuchSlippage();

strategy.withdraw(_amount0, _amount1);
HIGH severity: Use `calldata` instead of `memory` for function arguments that do not get mutated. **Description:** Use `calldata` instead of `memory` for function arguments that do not get mutated:

File:BeefyVaultConcLiq.sol

**Beefy:**
Fixed in commit [8349866](https://github.com/beefyfinance/experiments/commit/8349866c048412ec4c395eb0666b9e5aad6d6447).

**Cyfrin:** Verified.

\clearpage

Vulnerable code:
47:        string memory _name,
48:        string memory _symbol
LOW severity: Redemptions are blocked when sequencers are down. **Description:** Given that rollups such as [Optimism](https://docs.optimism.io/chain/differences#address-aliasing) and [Arbitrum](https://docs.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing) offer methods for forced transaction inclusion, it is important that the aliased sender address is...

Vulnerable code:
// Confirm that the caller is the `mintRecipient` to ensure atomic execution.
require(
    msg.sender.toUniversalAddress() == deposit.mintRecipient, "caller must be mintRecipient"
);
HIGH severity: A given CCTP domain can be registered for multiple foreign chains due to insufficient validation in `Governance::registerEmitterAndDomain`. **Description:** [`Governance::registerEmitterAndDomain`](https://github.com/wormhole-foundation/wormhole-circle-integration/blob/f7df33b159a71b163b8b5c7e7381c0d8f193da99/evm/src/contracts/CircleIntegration/Governance.sol#L48-L84) is a Governance action that is used to register the emitter address.....

Vulnerable code:
function registerEmitterAndDomain(bytes memory encodedVaa) public {
    /* snip: parsing of Governance VAA payload */

    // For now, ensure that we cannot register the same foreign chain again.
    require(registeredEmitters[foreignChain] == 0, "chain already registered");

    /* snip: additional parsing of Governance VAA payload */

    // Set the registeredEmitters state variable.
    registeredEmitters[foreignChain] = foreignAddress;

    // update the chainId to domain (and domain to chainId) mappings
    getChainToDomain()[foreignChain] = cctpDomain;
    getDomainToChain()[cctpDomain] = foreignChain;
}

+ require (getDomainToChain()[cctpDomain] == 0, "CCTP domain already registered for a different foreign chain");