Rebalance process reverts due to zero amount deposit and redemption
Depositing or redeeming zero amount against certain external money markets will cause the rebalancing process to revert.\\nFor a specific holding (e.g. cToken), the `redeemAmounts` and `depositAmounts` are mutually exclusive. So if the `redeemAmounts` for a specific holding is non-zero, the `depositAmounts` will be zero and vice-versa. This is because of the if-else block at Lines 48-56 below. Only `redeemAmounts` or `depositAmounts` of a specific holding can be initialized, but not both.\\n```\\nFile: ProportionalRebalancingStrategy.sol\\n        for (uint256 i; i < holdings.length;) {\\n            address holding = holdings[i];\\n            uint256 targetAmount = totalValue * rebalancingTargets[i] / uint256(Constants.PERCENTAGE_DECIMALS);\\n            uint256 currentAmount = values[i];\\n\\n            redeemHoldings[i] = holding;\\n            depositHoldings[i] = holding;\\n\\n            if (targetAmount < currentAmount) {\\n                unchecked {\\n                    redeemAmounts[i] = currentAmount - targetAmount;\\n                }\\n            } else if (currentAmount < targetAmount) {\\n                unchecked {\\n                    depositAmounts[i] = targetAmount - currentAmount;\\n                }\\n            }\\n\\n            unchecked {\\n                ++i;\\n            }\\n        }\\n```\\n\\nFor each holding, the following codes always deposit or redeem a zero value. For example, cETH holding, if the `redeemAmounts` is 100 ETH, the `depositAmounts` will be zero. (because of the if-else block). Therefore, `getDepositCalldataForRebalancing` function will be executed and attempt to deposit zero amount to Compound.\\n```\\nFile: ProportionalRebalancingStrategy.sol\\n        rebalancingData.redeemData = oracle.getRedemptionCalldataForRebalancing(redeemHoldings, redeemAmounts);\\n        rebalancingData.depositData = oracle.getDepositCalldataForRebalancing(depositHoldings, depositAmounts);\\n```\\n\\nThe problem is that the deposit/mint or redeem/burn function of certain external money markets will revert if the amount is zero. Notional is considering integrating with a few external monkey markets and one of them is AAVE.\\nIn this case, when Notional `deposit` zero amount to AAVE or `redeem` zero amount from AAVE, it causes the rebalancing process to revert because of the `onlyAmountGreaterThanZero` modifier on the AAVE's `deposit` and `redeem` function.\\n```\\nfunction deposit(address _reserve, uint256 _amount, uint16 _referralCode)\\n    external\\n    payable\\n    nonReentrant\\n    onlyActiveReserve(_reserve)\\n    onlyUnfreezedReserve(_reserve)\\n    onlyAmountGreaterThanZero(_amount)\\n{\\n```\\n\\n```\\nfunction redeemUnderlying(\\n    address _reserve,\\n    address payable _user,\\n    uint256 _amount,\\n    uint256 _aTokenBalanceAfterRedeem\\n)\\n    external\\n    nonReentrant\\n    onlyOverlyingAToken(_reserve)\\n    onlyActiveReserve(_reserve)\\n    onlyAmountGreaterThanZero(_amount)\\n{\\n```\\n\\nThe above issue is not only limited to AAVE and might also happen in other external markets.
Consider implementing validation to ensure the contract does not deposit zero amount to or redeem zero amount from the external market.\\nFollowing is the pseudocode for the potential fixes that could be implemented within the `_getDepositCalldataForRebalancing` of the holding contract to mitigate this issue. The same should be done for redemption.\\n```\\nfunction _getDepositCalldataForRebalancing(\\n    address[] calldata holdings, \\n    uint256[] calldata depositAmounts\\n) internal view virtual override returns (\\n    DepositData[] memory depositData\\n) {\\n    require(holdings.length == NUM_ASSET_TOKENS);\\n    for (int i = 0; i < holdings.length; i++) {\\n     if (depositAmounts[i] > 0) {\\n      // populate the depositData[i] with the deposit calldata to external money market>\\n     }\\n    }\\n}\\n```\\n\\nThe above solution will return an empty calldata if the deposit amount is zero for a specific holding.\\nWithin the `_executeDeposits` function, skip the `depositData` if it has not been initialized.\\n```\\nfunction _executeDeposits(Token memory underlyingToken, DepositData[] memory deposits) private {\\n    uint256 totalUnderlyingDepositAmount;\\n\\n    for (uint256 i; i < deposits.length; i++) {\\n        DepositData memory depositData = deposits[i];\\n        // if depositData is not initialized, skip to the next one\\n```\\n
Notional would not be able to rebalance its underlying holding. The key feature of Notional V3 is to allow its Treasury Manager to rebalance underlying holdings into various other money market protocols.\\nThis makes Notional more resilient to issues in external protocols and future-proofs the protocol. If rebalancing does not work, Notional will be unable to move its fund out of a vulnerable external market, potentially draining protocol funds if this is not mitigated.\\nAnother purpose of rebalancing is to allow Notional to allocate Notional V3's capital to new opportunities or protocols that provide a good return. If rebalancing does not work, the protocol and its users will lose out on the gain from the investment.\\nOn the other hand, if an external monkey market that Notional invested in is consistently underperforming or yielding negative returns, Notional will perform a rebalance to reallocate its funds to a better market. However, if rebalancing does not work, they will be stuck with a suboptimal asset allocation, and the protocol and its users will incur losses.
```\\nFile: ProportionalRebalancingStrategy.sol\\n        for (uint256 i; i < holdings.length;) {\\n            address holding = holdings[i];\\n            uint256 targetAmount = totalValue * rebalancingTargets[i] / uint256(Constants.PERCENTAGE_DECIMALS);\\n            uint256 currentAmount = values[i];\\n\\n            redeemHoldings[i] = holding;\\n            depositHoldings[i] = holding;\\n\\n            if (targetAmount < currentAmount) {\\n                unchecked {\\n                    redeemAmounts[i] = currentAmount - targetAmount;\\n                }\\n            } else if (currentAmount < targetAmount) {\\n                unchecked {\\n                    depositAmounts[i] = targetAmount - currentAmount;\\n                }\\n            }\\n\\n            unchecked {\\n                ++i;\\n            }\\n        }\\n```\\n