msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Some functions of `Goldilend` will revert forever.
Severity: high

Description:
`Goldilend.multisigInterestClaim()/apdaoInterestClaim()/sunsetProtocol()` will revert forever because they doesn't withdraw `ibgt` from `ibgtVault` before the transfer.\\n```\\n  function multisigInterestClaim() external {\\n    if(msg.sender != multisig) revert NotMultisig();\\n    uint256 interestClaim = multisigClaims;\\n    multisigClaims = 0;\\n    SafeTransferLib.safeTransfer(ibgt, multisig, interestClaim);\\n  }\\n\\n  /// @inheritdoc IGoldilend\\n  function apdaoInterestClaim() external {\\n    if(msg.sender != apdao) revert NotAPDAO();\\n    uint256 interestClaim = apdaoClaims;\\n    apdaoClaims = 0;\\n    SafeTransferLib.safeTransfer(ibgt, apdao, interestClaim);\\n  }\\n\\n// rest of code\\n\\n  function sunsetProtocol() external {\\n    if(msg.sender != timelock) revert NotTimelock();\\n    SafeTransferLib.safeTransfer(ibgt, multisig, poolSize - outstandingDebt);\\n  }\\n```\\n\\nAs `ibgtVault` has all `ibgt` of `Goldilend`, they should withdraw from `ibgtVault` first.

Impact:
`Goldilend.multisigInterestClaim()/apdaoInterestClaim()/sunsetProtocol()` will revert forever.

Recommended mitigation:
3 functions should be changed like the below.\\n```\\n  function multisigInterestClaim() external {\\n    if(msg.sender != multisig) revert NotMultisig();\\n    uint256 interestClaim = multisigClaims;\\n    multisigClaims = 0;\\n+  iBGTVault(ibgtVault).withdraw(interestClaim);\\n    SafeTransferLib.safeTransfer(ibgt, multisig, interestClaim);\\n  }\\n\\n  /// @inheritdoc IGoldilend\\n  function apdaoInterestClaim() external {\\n    if(msg.sender != apdao) revert NotAPDAO();\\n    uint256 interestClaim = apdaoClaims;\\n    apdaoClaims = 0;\\n+  iBGTVault(ibgtVault).withdraw(interestClaim);\\n    SafeTransferLib.safeTransfer(ibgt, apdao, interestClaim);\\n  }\\n\\n// rest of code\\n\\n  function sunsetProtocol() external {\\n    if(msg.sender != timelock) revert NotTimelock();\\n+  iBGTVault(ibgtVault).withdraw(poolSize - outstandingDebt);\\n    SafeTransferLib.safeTransfer(ibgt, multisig, poolSize - outstandingDebt);\\n  }\\n```\\n
Vulnerability: `Goldigovernor._getProposalState()` shouldn't use `totalSupply`
Severity: medium

Description:
In `_getProposalState()`, it uses `Goldiswap(goldiswap).totalSupply()` during the comparison.\\n```\\n  function _getProposalState(uint256 proposalId) internal view returns (ProposalState) {\\n    Proposal storage proposal = proposals[proposalId];\\n    if (proposal.cancelled) return ProposalState.Canceled;\\n    else if (block.number <= proposal.startBlock) return ProposalState.Pending;\\n    else if (block.number <= proposal.endBlock) return ProposalState.Active;\\n    else if (proposal.eta == 0) return ProposalState.Succeeded;\\n    else if (proposal.executed) return ProposalState.Executed;\\n    else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < Goldiswap(goldiswap).totalSupply() / 20) { //@audit shouldn't use totalSupply\\n      return ProposalState.Defeated;\\n    }\\n    else if (block.timestamp >= proposal.eta + Timelock(timelock).GRACE_PERIOD()) {\\n      return ProposalState.Expired;\\n    }\\n    else {\\n      return ProposalState.Queued;\\n    }\\n  }\\n```\\n\\nAs `totalSupply` is increasing in real time, a `Queued` proposal might be changed to `Defeated` one unexpectedly due to the increased supply.

Impact:
A proposal state might be changed unexpectedly.

Recommended mitigation:
We should introduce another mechanism for the quorum check rather than using `totalSupply`.
Vulnerability: In `Goldivault.redeemYield()`, users can redeem more yield tokens using reentrancy
Severity: medium

Description:
Possible reentrancy in `Goldivault.redeemYield()` if `yieldToken` has a `beforeTokenTransfer` hook.\\nLet's assume `yt.totalSupply` = 100, `yieldToken.balance` = 100 and the user has 20 yt.\\nThe user calls `redeemYield()` with 10 yt.\\nThen `yt.totalSupply` will be changed to 90 and it will transfer `100 * 10 / 100 = 10 yieldToken` to the user.\\nInside the `beforeTokenTransfer` hook, the user calls `redeemYield()` again with 10 yt.\\nAs `yieldToken.balance` is still 100, he will receive `100 * 10 / 90 = 11 yieldToken`.\\n```\\n  function redeemYield(uint256 amount) external {\\n    if(amount == 0) revert InvalidRedemption();\\n    if(block.timestamp < concludeTime + delay || !concluded) revert NotConcluded();\\n    uint256 yieldShare = FixedPointMathLib.divWad(amount, ERC20(yt).totalSupply());\\n    YieldToken(yt).burnYT(msg.sender, amount);\\n    uint256 yieldTokensLength = yieldTokens.length;\\n    for(uint8 i; i < yieldTokensLength; ++i) {\\n      uint256 finalYield;\\n      if(yieldTokens[i] == depositToken) {\\n        finalYield = ERC20(yieldTokens[i]).balanceOf(address(this)) - depositTokenAmount;\\n      }\\n      else {\\n        finalYield = ERC20(yieldTokens[i]).balanceOf(address(this));\\n      }\\n      uint256 claimable = FixedPointMathLib.mulWad(finalYield, yieldShare);\\n      SafeTransferLib.safeTransfer(yieldTokens[i], msg.sender, claimable);\\n    }\\n    emit YieldTokenRedemption(msg.sender, amount);\\n  }\\n```\\n

Impact:
Malicious users can steal `yieldToken` using `redeemYield()`.

Recommended mitigation:
We should add a `nonReentrant` modifier to `redeemYield()`.
Vulnerability: Wrong validation in `Goldigovernor.cancel()`
Severity: medium

Description:
In `Goldigovernor.cancel()`, the proposer should have fewer votes than `proposalThreshold` to cancel his proposal.\\n```\\n  function cancel(uint256 proposalId) external {\\n    if(_getProposalState(proposalId) == ProposalState.Executed) revert InvalidProposalState();\\n    Proposal storage proposal = proposals[proposalId];\\n    if(msg.sender != proposal.proposer) revert NotProposer();\\n    if(GovLocks(govlocks).getPriorVotes(proposal.proposer, block.number - 1) > proposalThreshold) revert AboveThreshold(); //@audit incorrect\\n    proposal.cancelled = true;\\n    uint256 targetsLength = proposal.targets.length;\\n    for (uint256 i = 0; i < targetsLength; i++) {\\n      Timelock(timelock).cancelTransaction(proposal.targets[i], proposal.eta, proposal.values[i], proposal.calldatas[i], proposal.signatures[i]);\\n    }\\n    emit ProposalCanceled(proposalId);\\n  }\\n```\\n

Impact:
A proposer can't cancel his proposal unless he decreases his voting power.

Recommended mitigation:
It should be modified like this.\\n```\\nif(msg.sender != proposal.proposer && GovLocks(govlocks).getPriorVotes(proposal.proposer, block.number - 1) > proposalThreshold) revert Error;\\n```\\n
Vulnerability: Users wouldn't cancel their proposals due to the increased `proposalThreshold`.
Severity: medium

Description:
When users call `cancel()`, it validates the caller's voting power with `proposalThreshold` which can be changed using `setProposalThreshold()`.\\n```\\n  function setProposalThreshold(uint256 newProposalThreshold) external {\\n    if(msg.sender != multisig) revert NotMultisig();\\n    if(newProposalThreshold < MIN_PROPOSAL_THRESHOLD || newProposalThreshold > MAX_PROPOSAL_THRESHOLD) revert InvalidVotingParameter();\\n    uint256 oldProposalThreshold = proposalThreshold;\\n    proposalThreshold = newProposalThreshold;\\n    emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);\\n  }\\n```\\n\\nHere is a possible scenario.\\nLet's assume `proposalThreshold` = 100 and a user has 100 voting power.\\nThe user has proposed a proposal using `propose()`.\\nAfter that, `proposalThreshold` was increased to 150 by `multisig`.\\nWhen the user calls `cancel()`, it will revert as he doesn't have enough voting power.

Impact:
Users wouldn't cancel their proposals due to the increased `proposalThreshold`.

Recommended mitigation:
It would be good to cache `proposalThreshold` as a proposal state.
Vulnerability: `Goldilend.liquidate()` might revert due to underflow
Severity: medium

Description:
In `repay()`, there would be a rounding during the `interest` calculation.\\n```\\n  function repay(uint256 repayAmount, uint256 _userLoanId) external {\\n      Loan memory userLoan = loans[msg.sender][_userLoanId];\\n      if(userLoan.borrowedAmount < repayAmount) revert ExcessiveRepay();\\n      if(block.timestamp > userLoan.endDate) revert LoanExpired();\\n      uint256 interestLoanRatio = FixedPointMathLib.divWad(userLoan.interest, userLoan.borrowedAmount);\\nL425  uint256 interest = FixedPointMathLib.mulWadUp(repayAmount, interestLoanRatio); //@audit rounding issue\\n      outstandingDebt -= repayAmount - interest > outstandingDebt ? outstandingDebt : repayAmount - interest;\\n      // rest of code\\n  }\\n// rest of code\\n  function liquidate(address user, uint256 _userLoanId) external {\\n      Loan memory userLoan = loans[msg.sender][_userLoanId];\\n      if(block.timestamp < userLoan.endDate || userLoan.liquidated || userLoan.borrowedAmount == 0) revert Unliquidatable();\\n      loans[user][_userLoanId].liquidated = true;\\n      loans[user][_userLoanId].borrowedAmount = 0;\\nL448  outstandingDebt -= userLoan.borrowedAmount - userLoan.interest;\\n      // rest of code\\n  }\\n```\\n\\nHere is a possible scenario.\\nThere are 2 borrowers of `borrowedAmount = 100, `interest` = 10`. And `outstandingDebt = 2 * (100 - 10) = 180`.\\nThe first borrower calls `repay()` with `repayAmount = 100`.\\nDue to the rounding issue at L425, `interest` is 9 instead of 10. And `outstandingDebt = 180 - (100 - 9) = 89`.\\nIn `liquidate()` for the second borrower, it will revert at L448 because `outstandingDebt = 89 < borrowedAmount - `interest` = 90`.

Impact:
`liquidate()` might revert due to underflow.

Recommended mitigation:
In `liquidate()`, `outstandingDebt` should be updated like the below.\\n```\\n  /// @inheritdoc IGoldilend\\n  function liquidate(address user, uint256 _userLoanId) external {\\n    Loan memory userLoan = loans[msg.sender][_userLoanId];\\n    if(block.timestamp < userLoan.endDate || userLoan.liquidated || userLoan.borrowedAmount == 0) revert Unliquidatable();\\n    loans[user][_userLoanId].liquidated = true;\\n    loans[user][_userLoanId].borrowedAmount = 0;\\n// Add the line below\\n  uint256 debtToRepay = userLoan.borrowedAmount - userLoan.interest;\\n// Add the line below\\n  outstandingDebt -= debtToRepay > outstandingDebt ? outstandingDebt : debtToRepay;\\n   // rest of code\\n  }\\n```\\n
Vulnerability: In `Goldigovernor`, wrong assumption of block time
Severity: medium

Description:
In `Goldigovernor.sol`, voting period/delay limits are set with 15s block time.\\n```\\n  /// @notice Minimum voting period\\n  uint32 public constant MIN_VOTING_PERIOD = 5760; // About 24 hours\\n\\n  /// @notice Maximum voting period\\n  uint32 public constant MAX_VOTING_PERIOD = 80640; // About 2 weeks\\n\\n  /// @notice Minimum voting delay\\n  uint32 public constant MIN_VOTING_DELAY = 1;\\n\\n  /// @notice Maximum voting delay\\n  uint32 public constant MAX_VOTING_DELAY = 40320; // About 1 week\\n```\\n\\nBut Berachain has 5s block time according to its documentation.\\n```\\nBerachain has the following properties:\\n\\n- Block time: 5s\\n```\\n\\nSo these limits will be set shorter than expected.

Impact:
Voting period/delay limits will be set shorter than expected.

Recommended mitigation:
We should calculate these limits with 5s block time.
Vulnerability: Silent overflow in `TrimmedAmount::shift` could result in rate limiter being bypassed
Severity: medium

Description:
Within `TrimmedAmount::trim`, there is an explicit check that ensures the scaled amount does not exceed the maximum uint64:\\n```\\n// NOTE: amt after trimming must fit into uint64 (that's the point of\\n// trimming, as Solana only supports uint64 for token amts)\\nif (amountScaled > type(uint64).max) {\\n    revert AmountTooLarge(amt);\\n}\\n```\\n\\nHowever, no such check exists within `TrimmedAmount::shift` which means there is potential for silent overflow when casting to `uint64` here:\\n```\\nfunction shift(\\n    TrimmedAmount memory amount,\\n    uint8 toDecimals\\n) internal pure returns (TrimmedAmount memory) {\\n    uint8 actualToDecimals = minUint8(TRIMMED_DECIMALS, toDecimals);\\n    return TrimmedAmount(\\n        uint64(scale(amount.amount, amount.decimals, actualToDecimals)), actualToDecimals\\n    );\\n}\\n```\\n

Impact:
A silent overflow in `TrimmedAmount::shift` could result in the rate limiter being bypassed, considering its usage in `NttManager::_transferEntryPoint`. Given the high impact and reasonable likelihood of this issue occurring, it is classified a MEDIUM severity finding.

Recommended mitigation:
Explicitly check the scaled amount in `TrimmedAmount::shift` does not exceed the maximum `uint64`.
Vulnerability: Disabled Transceivers cannot be re-enabled by calling `TransceiverRegistry::_setTransceiver` after 64 have been registered
Severity: medium

Description:
`TransceiverRegistry::_setTransceiver` handles the registering of Transceivers, but note that they cannot be re-registered as this has other downstream effects, so this function is also responsible for the re-enabling of previously registered but currently disabled Transceivers.\\n```\\nfunction _setTransceiver(address transceiver) internal returns (uint8 index) {\\n    /* snip */\\n    if (transceiver == address(0)) {\\n        revert InvalidTransceiverZeroAddress();\\n    }\\n\\n    if (_numTransceivers.registered >= MAX_TRANSCEIVERS) {\\n        revert TooManyTransceivers();\\n    }\\n\\n    if (transceiverInfos[transceiver].registered) {\\n        transceiverInfos[transceiver].enabled = true;\\n    } else {\\n    /* snip */\\n}\\n```\\n\\nThis function reverts if the passed transceiver address is `address(0)` or the number of registered transceivers is already at its defined maximum of 64. Assuming a total of 64 registered Transceivers, with some of these Transceivers having been previously disabled, the placement of this latter validation will prevent a disabled Transceiver from being re-enabled since the subsequent block in which the storage indicating its enabled state is set to `true` is not reachable. Consequently, it will not be possible to re-enable any disabled transceivers after having registered the maximum number of Transceivers, meaning that this function will never be callable without redeployment.

Impact:
Under normal circumstances, this maximum number of registered Transceivers should never be reached, especially since the underlying Transceivers are upgradeable. However, while unlikely based on operational assumptions, this undefined behavior could have a high impact, and so this is classified as a MEDIUM severity finding.

Recommended mitigation:
Move the placement of the maximum Transceivers validation to within the `else` block that is responsible for handling the registration of new Transceivers.
Vulnerability: NTT Manager cannot be unpaused once paused
Severity: medium

Description:
`NttManagerState::pause` exposes pause functionality to be triggered by permissioned actors but has no corresponding unpause functionality. As such, once the NTT Manager is paused, it will not be possible to unpause without a contract upgrade.\\n```\\nfunction pause() public onlyOwnerOrPauser {\\n    _pause();\\n}\\n```\\n

Impact:
The inability to unpause the NTT Manager could result in significant disruption, requiring either a contract upgrade or complete redeployment to resolve this issue.

Recommended mitigation:
```\\n// Add the line below\\n function unpause() public onlyOwnerOrPauser {\\n// Add the line below\\n     _unpause();\\n// Add the line below\\n }\\n```\\n
Vulnerability: Asymmetry in Transceiver pausing capability
Severity: low

Description:
Pausing functionality is exposed via Transceiver::_pauseTransceiver; however, there is no corresponding function that exposes unpausing functionality:\\n```\\n/// @dev pause the transceiver.\\nfunction _pauseTransceiver() internal {\\n    _pause();\\n}\\n```\\n

Impact:
While not an immediate issue since the above function is not currently in use anywhere, this should be resolved to avoid cases where Transceivers could become permanently paused.

Recommended mitigation:
```\\n// Add the line below\\n /// @dev unpause the transceiver.\\n// Add the line below\\n function _unpauseTransceiver() internal {\\n// Add the line below\\n     _unpause();\\n// Add the line below\\n }\\n```\\n
Vulnerability: Incorrect Transceiver payload prefix definition
Severity: low

Description:
The `WH_TRANSCEIVER_PAYLOAD_PREFIX` constant in `WormholeTransceiverState.sol` contains invalid ASCII bytes and, as such, does not match what is written in the inline developer documentation:\\n```\\n/// @dev Prefix for all TransceiverMessage payloads\\n///      This is 0x99'E''W''H'\\n/// @notice Magic string (constant value set by messaging provider) that idenfies the payload as an transceiver-emitted payload.\\n///         Note that this is not a security critical field. It's meant to be used by messaging providers to identify which messages are Transceiver-related.\\nbytes4 constant WH_TRANSCEIVER_PAYLOAD_PREFIX = 0x9945FF10;\\n```\\n\\nThe correct payload prefix is `0x99455748`, which is output when running the following command:\\n```\\ncast --from-utf8 "EWH"\\n```\\n

Impact:
While still a valid 4-byte hex prefix, used purely for identification purposes, an incorrect prefix could cause downstream confusion and result in otherwise valid Transceiver payloads being incorrectly prefixed.

Recommended mitigation:
Update the constant definition to use the correct prefix corresponding to the documented string:\\n```\\n// Add the line below\\n bytes4 constant WH_TRANSCEIVER_PAYLOAD_PREFIX = 0x99455748;\\n```\\n
Vulnerability: Redemptions are blocked when L2 sequencers are down
Severity: medium

Description:
Given that rollups such as Optimism and Arbitrum offer methods for forced transaction inclusion, it is important that the aliased sender address is also checked within `Logic::redeemTokensWithPayload` when verifying the sender is the specified `mintRecipient` to allow for maximum uptime in the event of sequencer downtime.\\n```\\n// Confirm that the caller is the `mintRecipient` to ensure atomic execution.\\nrequire(\\n    msg.sender.toUniversalAddress() == deposit.mintRecipient, "caller must be mintRecipient"\\n);\\n```\\n

Impact:
Failure to consider the aliased `mintRecipient` address prevents the execution of valid VAAs on a target CCTP domain where transactions are batched by a centralized L2 sequencer. Since this VAA could carry a time-sensitive payload, such as the urgent cross-chain liquidity infusion to a protocol, this issue has the potential to have a high impact with reasonable likelihood.\\nProof of Concept:\\nProtocol X attempts to transfer 10,000 USDC from CCTP Domain A to CCTP Domain B.\\nCCTP Domain B is an L2 rollup that batches transactions for publishing onto the L1 chain via a centralized sequencer.\\nThe L2 sequencer goes down; however, transactions can still be executed via forced inclusion on the L1 chain.\\nProtocol X implements the relevant functionality and attempts to redeem 10,000 USDC via forced inclusion.\\nThe Wormhole CCTP integration does not consider the contract's aliased address when validating the `mintRecipient`, so the redemption fails.\\nCross-chain transfer of this liquidity will remain blocked so long as the sequencer is down.

Recommended mitigation:
Validation of the sender address against the `mintRecipient` should also consider the aliased `mintRecipient` address to allow for maximum uptime when `Logic::redeemTokensWithPayload` is called via forced inclusion.
Vulnerability: A given CCTP domain can be registered for multiple foreign chains due to insufficient validation in `Governance::registerEmitterAndDomain`
Severity: low

Description:
`Governance::registerEmitterAndDomain` is a Governance action that is used to register the emitter address and corresponding CCTP domain for a given foreign chain. Validation is currently performed to ensure that the registered CCTP domain of the foreign chain is not equal to that of the local chain; however, there is no such check to ensure that the given CCTP domain has not already been registered for a different foreign chain. In this case, where the CCTP domain of an existing foreign chain is mistakenly used in the registration of a new foreign chain, the `getDomainToChain` mapping of an existing CCTP domain will be overwritten to the most recently registered foreign chain. Given the validation that prevents foreign chains from being registered again, without a method for updating an already registered emitter, it will not be possible to correct this corruption of state.\\n```\\nfunction registerEmitterAndDomain(bytes memory encodedVaa) public {\\n    /* snip: parsing of Governance VAA payload */\\n\\n    // For now, ensure that we cannot register the same foreign chain again.\\n    require(registeredEmitters[foreignChain] == 0, "chain already registered");\\n\\n    /* snip: additional parsing of Governance VAA payload */\\n\\n    // Set the registeredEmitters state variable.\\n    registeredEmitters[foreignChain] = foreignAddress;\\n\\n    // update the chainId to domain (and domain to chainId) mappings\\n    getChainToDomain()[foreignChain] = cctpDomain;\\n    getDomainToChain()[cctpDomain] = foreignChain;\\n}\\n```\\n

Impact:
The impact of this issue in the current scope is limited since the corrupted state is only ever queried in a public view function; however, if it is important for third-party integrators, then this has the potential to cause downstream issues.\\nProof of Concept:\\nCCTP Domain A is registered for foreign chain identifier X.\\nCCTP Domain A is again registered, this time for foreign chain identifier Y.\\nThe `getDomainToChain` mapping for CCTP Domain A now points to foreign chain identifier Y, while the `getChainToDomain` mapping for both X and Y now points to CCTP domain A.

Recommended mitigation:
Consider adding the following validation when registering a CCTP domain for a foreign chain:\\n```\\n// Add the line below\\n require (getDomainToChain()[cctpDomain] == 0, "CCTP domain already registered for a different foreign chain");\\n```\\n
Vulnerability: Lack of Governance action to update registered emitters
Severity: low

Description:
The Wormhole CCTP integration contract currently exposes a function `Governance::registerEmitterAndDomain` to register an emitter address and its corresponding CCTP domain on the given foreign chain; however, no such function currently exists to update this state. Any mistake made when registering the emitter and CCTP domain is irreversible unless an upgrade is performed on the entirety of the integration contract itself. Deployment of protocol upgrades comes with its own risks and should not be performed as a necessary fix for trivial human errors. Having a separate governance action to update the emitter address, foreign chain identifier, and CCTP domain is a preferable pre-emptive measure against any potential human errors.\\n```\\nfunction registerEmitterAndDomain(bytes memory encodedVaa) public {\\n    /* snip: parsing of Governance VAA payload */\\n\\n    // Set the registeredEmitters state variable.\\n    registeredEmitters[foreignChain] = foreignAddress;\\n\\n    // update the chainId to domain (and domain to chainId) mappings\\n    getChainToDomain()[foreignChain] = cctpDomain;\\n    getDomainToChain()[cctpDomain] = foreignChain;\\n}\\n```\\n

Impact:
In the event an emitter is registered with an incorrect foreign chain identifier or CCTP domain, then a protocol upgrade will be required to mitigate this issue. As such, the risks associated with the deployment of protocol upgrades and the potential time-sensitive nature of this issue designate a low severity issue.\\nProof of Concept:\\nA Governance VAA erroneously registers an emitter with the incorrect foreign chain identifier.\\nA Governance upgrade is now required to re-initialize this state so that the correct foreign chain identifier can be associated with the given emitter address.

Recommended mitigation:
The addition of a `Governance::updateEmitterAndDomain` function is recommended to allow Governance to more easily respond to any issues with the registered emitter state.