msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Underlying delta is calculated on internal token balance
Severity: medium

Description:
The underlying delta is calculated on the internal token balance, which might cause inconsistency with tokens of varying decimals.\\n```\\nFile: TreasuryAction.sol\\n    function _executeRebalance(uint16 currencyId) private {\\n        IPrimeCashHoldingsOracle oracle = PrimeCashExchangeRate.getPrimeCashHoldingsOracle(currencyId);\\n        uint8[] memory rebalancingTargets = _getRebalancingTargets(currencyId, oracle.holdings());\\n        (RebalancingData memory data) = REBALANCING_STRATEGY.calculateRebalance(oracle, rebalancingTargets);\\n\\n        (/* */, uint256 totalUnderlyingValueBefore) = oracle.getTotalUnderlyingValueStateful();\\n\\n        // Process redemptions first\\n        Token memory underlyingToken = TokenHandler.getUnderlyingToken(currencyId);\\n        TokenHandler.executeMoneyMarketRedemptions(underlyingToken, data.redeemData);\\n\\n        // Process deposits\\n        _executeDeposits(underlyingToken, data.depositData);\\n\\n        (/* */, uint256 totalUnderlyingValueAfter) = oracle.getTotalUnderlyingValueStateful();\\n\\n        int256 underlyingDelta = totalUnderlyingValueBefore.toInt().sub(totalUnderlyingValueAfter.toInt());\\n        require(underlyingDelta.abs() < Constants.REBALANCING_UNDERLYING_DELTA);\\n    }\\n```\\n\\nThe `underlyingDelta` is denominated in internal token precision (1e8) and is computed by taking the difference between `totalUnderlyingValueBefore` and `totalUnderlyingValueAfter` in Line 300 above.\\nNext, the `underlyingDelta` is compared against the `Constants.REBALANCING_UNDERLYING_DELTA` (10_000=0.0001) to ensure that the rebalance did not exceed the acceptable delta threshold.\\nHowever, the same `Constants.REBALANCING_UNDERLYING_DELTA` is used across all tokens such as ETH, DAI, and USDC. As a result, the delta will not be consistent with tokens of varying decimals.

Impact:
Using the internal token precision (1e8) might result in an over-sensitive trigger for tokens with fewer decimals (e.g. 1e6) as they are scaled up and an under-sensitive one for tokens with more decimals (e.g. 1e18) as they are scaled down, leading to inconsistency across different tokens when checking against the `Constants.REBALANCING_UNDERLYING_DELTA`.\\nThis also means that the over-sensitive one will trigger a revert more easily and vice versa.

Recommended mitigation:
Consider using the external token balance and scale `Constants.REBALANCING_UNDERLYING_DELTA` to the token's decimals.
Vulnerability: Secondary debt dust balances are not truncated
Severity: medium

Description:
Dust balances in primary debt are truncated toward zero. However, this truncation was not performed against secondary debts.\\n```\\nFile: VaultAccount.sol\\n    function updateAccountDebt(\\n..SNIP..\\n        // Truncate dust balances towards zero\\n        if (0 < vaultState.totalDebtUnderlying && vaultState.totalDebtUnderlying < 10) vaultState.totalDebtUnderlying = 0;\\n..SNIP..\\n    }\\n```\\n\\n`vaultState.totalDebtUnderlying` is primarily used to track the total debt of primary currency. Within the `updateAccountDebt` function, any dust balance in the `vaultState.totalDebtUnderlying` is truncated towards zero at the end of the function as shown above.\\n```\\nFile: VaultSecondaryBorrow.sol\\n    function _updateTotalSecondaryDebt(\\n        VaultConfig memory vaultConfig,\\n        address account,\\n        uint16 currencyId,\\n        uint256 maturity,\\n        int256 netUnderlyingDebt,\\n        PrimeRate memory pr\\n    ) private {\\n        VaultStateStorage storage balance = LibStorage.getVaultSecondaryBorrow()\\n            [vaultConfig.vault][maturity][currencyId];\\n        int256 totalDebtUnderlying = VaultStateLib.readDebtStorageToUnderlying(pr, maturity, balance.totalDebt);\\n        \\n        // Set the new debt underlying to storage\\n        totalDebtUnderlying = totalDebtUnderlying.add(netUnderlyingDebt);\\n        VaultStateLib.setTotalDebtStorage(\\n            balance, pr, vaultConfig, currencyId, maturity, totalDebtUnderlying, false // not settled\\n        );\\n```\\n\\nHowever, this approach was not consistently applied when handling dust balance in secondary debt within the `_updateTotalSecondaryDebt` function. Within the `_updateTotalSecondaryDebt` function, the dust balance in secondary debts is not truncated.

Impact:
The inconsistency in handling dust balances in primary and secondary debt could potentially lead to discrepancies in debt accounting within the protocol, accumulation of dust, and result in unforeseen consequences.

Recommended mitigation:
Consider truncating dust balance in secondary debt within the `_updateTotalSecondaryDebt` function similar to what has been done for primary debt.
Vulnerability: No minimum borrow size check against secondary debts
Severity: medium

Description:
Secondary debts were not checked against the minimum borrow size during exit, which could lead to accounts with insufficient debt becoming insolvent and the protocol incurring bad debts.\\n```\\nFile: VaultAccount.sol\\n    function _setVaultAccount(\\n..SNIP..\\n        // An account must maintain a minimum borrow size in order to enter the vault. If the account\\n        // wants to exit under the minimum borrow size it must fully exit so that we do not have dust\\n        // accounts that become insolvent.\\n        if (\\n            vaultAccount.accountDebtUnderlying.neg() < vaultConfig.minAccountBorrowSize &&\\n            // During local currency liquidation and settlement, the min borrow check is skipped\\n            checkMinBorrow\\n        ) {\\n            // NOTE: use 1 to represent the minimum amount of vault shares due to rounding in the\\n            // vaultSharesToLiquidator calculation\\n            require(vaultAccount.accountDebtUnderlying == 0 || vaultAccount.vaultShares <= 1, "Min Borrow");\\n        }\\n```\\n\\nA vault account has one primary debt (accountDebtUnderlying) and one or more secondary debts (accountDebtOne and accountDebtTwo).\\nWhen a vault account exits the vault, Notional will check that its primary debt (accountDebtUnderlying) meets the minimum borrow size requirement. If a vault account wants to exit under the minimum borrow size it must fully exit so that we do not have dust accounts that become insolvent. This check is being performed in Line 140 above.\\nHowever, this check is not performed against the secondary debts. As a result, it is possible that the secondary debts fall below the minimum borrow size after exiting.

Impact:
Vault accounts with debt below the minimum borrow size are at risk of becoming insolvent, leaving the protocol with bad debts.

Recommended mitigation:
Consider performing a similar check against the secondary debts (accountDebtOne and accountDebtTwo) within the `_setVaultAccount` function to ensure they do not fall below the minimum borrow size.
Vulnerability: It may be possible to liquidate on behalf of another account
Severity: medium

Description:
If the caller of any liquidation action is the vault itself, there is no validation of the `liquidator` parameter and therefore, any arbitrary account may act as the `liquidator` if they have approved any amount of funds for the `VaultLiquidationAction.sol` contract.\\nWhile the vault implementation itself should most likely handle proper validation of the parameters provided to actions enabled by the vault, the majority of important validation should be done within the Notional protocol. The base implementation for vaults does not seem to sanitise `liquidator` and hence users could deleverage accounts on behalf of a `liquidator` which has approved Notional's contracts.\\n```\\nFile: VaultLiquidationAction.sol\\n    function _authenticateDeleverage(\\n        address account,\\n        address vault,\\n        address liquidator\\n    ) private returns (\\n        VaultConfig memory vaultConfig,\\n        VaultAccount memory vaultAccount,\\n        VaultState memory vaultState\\n    ) {\\n        // Do not allow invalid accounts to liquidate\\n        requireValidAccount(liquidator);\\n        require(liquidator != vault);\\n\\n        // Cannot liquidate self, if a vault needs to deleverage itself as a whole it has other methods \\n        // in VaultAction to do so.\\n        require(account != msg.sender);\\n        require(account != liquidator);\\n\\n        vaultConfig = VaultConfiguration.getVaultConfigStateful(vault);\\n        require(vaultConfig.getFlag(VaultConfiguration.DISABLE_DELEVERAGE) == false);\\n\\n        // Authorization rules for deleveraging\\n        if (vaultConfig.getFlag(VaultConfiguration.ONLY_VAULT_DELEVERAGE)) {\\n            require(msg.sender == vault);\\n        } else {\\n            require(msg.sender == liquidator);\\n        }\\n\\n        vaultAccount = VaultAccountLib.getVaultAccount(account, vaultConfig);\\n\\n        // Vault accounts that are not settled must be settled first by calling settleVaultAccount\\n        // before liquidation. settleVaultAccount is not permissioned so anyone may settle the account.\\n        require(block.timestamp < vaultAccount.maturity, "Must Settle");\\n\\n        if (vaultAccount.maturity == Constants.PRIME_CASH_VAULT_MATURITY) {\\n            // Returns the updated prime vault state\\n            vaultState = vaultAccount.accruePrimeCashFeesToDebtInLiquidation(vaultConfig);\\n        } else {\\n            vaultState = VaultStateLib.getVaultState(vaultConfig, vaultAccount.maturity);\\n        }\\n    }\\n```\\n

Impact:
A user may be forced to liquidate an account they do not wish to purchase vault shares for.

Recommended mitigation:
Make the necessary changes to `BaseStrategyVault.sol` or `_authenticateDeleverage()`, whichever is preferred.
Vulnerability: MarginTrading.sol: Missing flash loan initiator check allows attacker to open trades, close trades and steal funds
Severity: high

Description:
The `MarginTrading.executeOperation` function is called when a flash loan is made (and it can only be called by the lendingPool).\\nThe wrong assumption by the protocol is that the flash loan can only be initiated by the `MarginTrading` contract itself.\\nHowever this is not true. A flash loan can be initiated for any `receiverAddress`.\\n\\nSo an attacker can execute a flash loan with the `MarginTrading` contract as `receiverAddress`. Also the funds that are needed to pay back the flash loan are pulled from the `receiverAddress` and NOT from the initiator:\\nThis means the attacker can close a position or repay a position in the `MarginTrading` contract.\\nBy crafting a malicious swap, the attacker can even steal funds.\\nLet's assume there is an ongoing trade in a `MarginTrading` contract:\\n```\\ndaiAToken balance = 30000\\nwethDebtToken balance = 10\\n\\nThe price of WETH when the trade was opened was ~ 3000 DAI\\n```\\n\\nIn order to profit from this the attacker does the following (not considering fees for simplicity):\\nTake a flash loan of 30000 DAI with `MarginTrading` as `receiverAddress` with `mode=0` (flash loan is paid back in the same transaction)\\nPrice of WETH has dropped to 2000 DAI. The attacker uses a malicious swap contract that pockets 10000 DAI for the attacker and swaps the remaining 20000 DAI to 10 WETH (the attacker can freely choose the swap contract in the `_params` of the flash loan).\\nThe 10 WETH debt is repaid\\nWithdraw 30000 DAI from Aave to pay back the flash loan

Impact:
The attacker can close trades, partially close trades and even steal funds.\\n(Note: It's not possible for the attacker to open trades because he cannot incur debt on behalf of the `MarginTrading` contract)

Recommended mitigation:
The fix is straightforward:\\n```\\ndiff --git a/dodo-margin-trading-contracts/contracts/marginTrading/MarginTrading.sol b/dodo-margin-trading-contracts/contracts/marginTrading/MarginTrading.sol\\nindex f68c1f3..5b4b485 100644\\n--- a/dodo-margin-trading-contracts/contracts/marginTrading/MarginTrading.sol\\n// Add the line below\\n// Add the line below\\n// Add the line below\\n b/dodo-margin-trading-contracts/contracts/marginTrading/MarginTrading.sol\\n@@ -125,6 // Add the line below\\n125,7 @@ contract MarginTrading is OwnableUpgradeable, IMarginTrading, IFlashLoanReceiver\\n         address _initiator,\\n         bytes calldata _params\\n     ) external override onlyLendingPool returns (bool) {\\n// Add the line below\\n        require(_initiator == address(this));\\n         //decode params exe swap and deposit\\n         {\\n```\\n\\nThis ensures that the flash loan has been initiated by the `MarginTrading.executeFlashLoans` function which is the intended initiator.
Vulnerability: AuraSpell#openPositionFarm fails to return all rewards to user
Severity: high

Description:
When a user adds to an existing position on AuraSpell, the contract burns their current position and remints them a new one. The issues is that WAuraPool will send all reward tokens to the contract but it only sends Aura back to the user, causing all other rewards to be lost.\\n```\\n    for (uint i = 0; i < rewardTokens.length; i++) {\\n        IERC20Upgradeable(rewardTokens[i]).safeTransfer(\\n            msg.sender,\\n            rewards[i]\\n        );\\n    }\\n```\\n\\nInside WAuraPools#burn reward tokens are sent to the user.\\n```\\n    IBank.Position memory pos = bank.getCurrentPositionInfo();\\n    if (pos.collateralSize > 0) {\\n        (uint256 pid, ) = wAuraPools.decodeId(pos.collId);\\n        if (param.farmingPoolId != pid)\\n            revert Errors.INCORRECT_PID(param.farmingPoolId);\\n        if (pos.collToken != address(wAuraPools))\\n            revert Errors.INCORRECT_COLTOKEN(pos.collToken);\\n        bank.takeCollateral(pos.collateralSize);\\n        wAuraPools.burn(pos.collId, pos.collateralSize);\\n        _doRefundRewards(AURA);\\n    }\\n```\\n\\nWe see above that the contract only refunds Aura to the user causing all other extra reward tokens received by the contract to be lost to the user.

Impact:
User will lose all extra reward tokens from their original position

Recommended mitigation:
WAuraPool returns the reward tokens it sends. Use this list to refund all tokens to the user
Vulnerability: ShortLongSpell#openPosition uses the wrong balanceOf when determining how much collateral to put
Severity: high

Description:
The _doPutCollateral subcall in ShortLongSpell#openPosition uses the balance of the uToken rather than the vault resulting in the vault tokens being left in the contract which will be stolen.\\n```\\n    address vault = strategies[param.strategyId].vault;\\n    _doPutCollateral(\\n        vault,\\n        IERC20Upgradeable(ISoftVault(vault).uToken()).balanceOf(\\n            address(this)\\n        )\\n    );\\n```\\n\\nWhen putting the collateral the contract is putting vault but it uses the balance of the uToken instead of the balance of the vault.

Impact:
Vault tokens will be left in contract and stolen

Recommended mitigation:
Use the balanceOf vault rather than vault.uToken
Vulnerability: BalancerPairOracle#getPrice will revert due to division by zero in some cases
Severity: medium

Description:
`BalancerPairOracle#getPrice` internally calls `computeFairReserves`, which returns fair reserve amounts given spot reserves, weights, and fair prices. When the parameter `resA` passed to `computeFairReserves` is smaller than `resB`, division by 0 will occur.\\nIn `BalancerPairOracle#getPrice`, resA and resB passed to `computeFairReserves` are the balance of TokenA and TokenB of the pool respectively. It is common for the balance of TokenB to be greater than the balance of TokenA.\\n```\\nfunction computeFairReserves(\\n        uint256 resA,\\n        uint256 resB,\\n        uint256 wA,\\n        uint256 wB,\\n        uint256 pxA,\\n        uint256 pxB\\n    ) internal pure returns (uint256 fairResA, uint256 fairResB) {\\n     // rest of code\\n     //@audit r0 = 0 when resA < resB.\\n->      uint256 r0 = resA / resB;\\n        uint256 r1 = (wA * pxB) / (wB * pxA);\\n        // fairResA = resA * (r1 / r0) ^ wB\\n        // fairResB = resB * (r0 / r1) ^ wA\\n        if (r0 > r1) {\\n            uint256 ratio = r1 / r0;\\n            fairResA = resA * (ratio ** wB);\\n            fairResB = resB / (ratio ** wA);\\n        } else {\\n->          uint256 ratio = r0 / r1;  // radio = 0 when r0 = 0\\n->          fairResA = resA / (ratio ** wB);    // revert divided by 0\\n            fairResB = resB * (ratio ** wA);\\n        }\\n    }\\n```\\n\\nAnother case is when the decimals of tokenA is smaller than the decimals of tokenB, such as usdc(e6)-weth(e18).

Impact:
All functions that subcall `BalancerPairOracle#getPrice` will be affected.

Recommended mitigation:
```\\n// Remove the line below\\n// Remove the line below\\n// Remove the line below\\n a/blueberry// Remove the line below\\ncore/contracts/oracle/BalancerPairOracle.sol\\n// Add the line below\\n// Add the line below\\n// Add the line below\\n b/blueberry// Remove the line below\\ncore/contracts/oracle/BalancerPairOracle.sol\\n@@ // Remove the line below\\n50,7 // Add the line below\\n50,7 @@ contract BalancerPairOracle is UsingBaseOracle, IBaseOracle {\\n         // // Remove the line below\\n// Remove the line below\\n> fairResA / r1^wB = constant product\\n         // // Remove the line below\\n// Remove the line below\\n> fairResA = resA^wA * resB^wB * r1^wB\\n         // // Remove the line below\\n// Remove the line below\\n> fairResA = resA * (resB/resA)^wB * r1^wB = resA * (r1/r0)^wB\\n// Remove the line below\\n        uint256 r0 = resA / resB;\\n// Add the line below\\n        uint256 r0 = resA * 10**(decimalsB) / resB;\\n         uint256 r1 = (wA * pxB) / (wB * pxA);\\n         // fairResA = resA * (r1 / r0) ^ wB\\n         // fairResB = resB * (r0 / r1) ^ wA\\n```\\n
Vulnerability: Updating the feeManger on config will cause desync between bank and vaults
Severity: medium

Description:
When the bank is initialized it caches the current config.feeManager. This is problematic since feeManger can be updated in config. Since it is precached the address in bank will not be updated leading to a desync between contracts the always pull the freshest value for feeManger and bank.\\n```\\n    feeManager = config_.feeManager();\\n```\\n\\nAbove we see that feeManger is cached during initialization.\\n```\\n    withdrawAmount = config.feeManager().doCutVaultWithdrawFee(\\n        address(uToken),\\n        shareAmount\\n    );\\n```\\n\\nThis is in direct conflict with other contracts the always use the freshest value. This is problematic for a few reasons. The desync will lead to inconsistent fees across the ecosystem either charging users too many fees or not enough.

Impact:
After update users will experience inconsistent fees across the ecosystem

Recommended mitigation:
BlueBerryBank should always use config.feeManger instead of caching it.
Vulnerability: ShortLongSpell#openPosition attempts to burn wrong token
Severity: medium

Description:
ShortLongSpell#openPosition attempts to burn vault.uToken when it should be using vault instead. The result is that ShortLongSpell#openPosition will be completely nonfunctional when the user is adding to their position\\n```\\n        address burnToken = address(ISoftVault(strategy.vault).uToken());\\n        if (collSize > 0) {\\n            if (posCollToken != address(wrapper))\\n                revert Errors.INCORRECT_COLTOKEN(posCollToken);\\n            bank.takeCollateral(collSize);\\n            wrapper.burn(burnToken, collSize);\\n            _doRefund(burnToken);\\n        }\\n```\\n\\nWe see above that the contract attempts to withdraw vault.uToken from the wrapper.\\n```\\n    _doPutCollateral(\\n        vault,\\n        IERC20Upgradeable(ISoftVault(vault).uToken()).balanceOf(\\n            address(this)\\n        )\\n    );\\n```\\n\\nThis is in direct conflict with the collateral that is actually deposited which is vault. This will cause the function to always revert when adding to an existing position.

Impact:
ShortLongSpell#openPosition will be completely nonfunctional when the user is adding to their position

Recommended mitigation:
Burn token should be vault rather than vault.uToken
Vulnerability: All allowances to DepositStableCoinToDealer and GeneralRepay can be stolen due to unsafe call
Severity: high

Description:
DepositStableCoinToDealer.sol and GeneralRepay.sol are helper contracts that allow a user to swap and enter JOJODealer and JUSDBank respectively. The issue is that the call is unsafe allowing the contract to call the token contracts directly and transfer tokens from anyone who has approved the contract.\\n```\\n    IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);\\n    (address approveTarget, address swapTarget, bytes memory data) = abi\\n    .decode(param, (address, address, bytes));\\n    // if usdt\\n    IERC20(asset).approve(approveTarget, 0);\\n    IERC20(asset).approve(approveTarget, amount);\\n    (bool success, ) = swapTarget.call(data);\\n    if (success == false) {\\n        assembly {\\n            let ptr := mload(0x40)\\n            let size := returndatasize()\\n            returndatacopy(ptr, 0, size)\\n            revert(ptr, size)\\n        }\\n    }\\n```\\n\\nWe can see above that the call is totally unprotected allowing a user to make any call to any contract. This can be abused by calling the token contract and using the allowances of others. The attack would go as follows:\\nUser A approves the contract for 100 USDT\\nUser B sees this approval and calls depositStableCoin with the swap target as the USDT contract with themselves as the receiver\\nThis transfers all of user A USDT to them

Impact:
All allowances can be stolen

Recommended mitigation:
Only allow users to call certain whitelisted contracts.