Summary
The validation logic in CreditDelegationBranch.sol:rebalanceVaultsAssets() incorrectly reverts when the in-credit vault has a negative unsettledRealizedDebt
and the in-debt vault has a positive unsettledRealizedDebt
. This is not how function description says, the in-credit vault should have a deficit (< 0
), and the in-debt vault should have a surplus (> 0
).
Vulnerability Details
CreditDelegationBranch.sol:rebalanceVaultsAssets()
function descriptions states that in-credit vault should have negative unsettledRealizedDebt
while in-debt vault should have positive unsettledRealizedDebt
:
function rebalanceVaultsAssets(uint128[2] calldata vaultsIds) external onlyRegisteredSystemKeepers {
Vault.Data storage inCreditVault = Vault.loadExisting(vaultsIds[0]);
Vault.Data storage inDebtVault = Vault.loadExisting(vaultsIds[1]);
if (inCreditVault.engine != inDebtVault.engine) {
revert Errors.VaultsConnectedToDifferentEngines();
}
uint256[] memory vaultsIdsForRecalculation = new uint256[](2);
vaultsIdsForRecalculation[0] = vaultsIds[0];
vaultsIdsForRecalculation[1] = vaultsIds[1];
Vault.recalculateVaultsCreditCapacity(vaultsIdsForRecalculation);
SD59x18 inDebtVaultUnsettledRealizedDebtUsdX18 = inDebtVault.getUnsettledRealizedDebt();
SD59x18 inCreditVaultUnsettledRealizedDebtUsdX18 = inCreditVault.getUnsettledRealizedDebt();
if (
inCreditVaultUnsettledRealizedDebtUsdX18.lte(SD59x18_ZERO)
|| inDebtVaultUnsettledRealizedDebtUsdX18.gte(SD59x18_ZERO)
) {
revert Errors.InvalidVaultDebtSettlementRequest();
}
...
}
But this code block is doing the opposite:
if (
inCreditVaultUnsettledRealizedDebtUsdX18.lte(SD59x18_ZERO)
|| inDebtVaultUnsettledRealizedDebtUsdX18.gte(SD59x18_ZERO)
) {
revert Errors.InvalidVaultDebtSettlementRequest();
}
Check how function flow will go with data from function description:
In-credit vault:
realizedDebt = -100
depositedUSDC = 200
unsettledRealizedDebt = (-100) + (-200) = -300
In-debt vault:
realizedDebt = 50
depositedUSDC = 10
unsettledRealizedDebt = 50 + (-10) = 40
Result:
inCreditVaultUnsettledRealizedDebtUsd <= 0 - true
inDebtVaultUnsettledRealizedDebtUsd >= 0 - true
But function should not revert
with this parameters, vaults should be rebalanced.
PoC
Add this test to test/integration/market-making/credit-delegation-branch/rebalanceVaultsAssets/rebalanceVaultsAssets.t.sol
:
function testFuzz_RevertWhen_InvalidVaultDebtSettlementRequestShouldntBeAsInExample(
uint256 inCreditVaultId,
uint256 inDebtVaultId
)
external
whenTheIndebtVaultAndIncreditVaultsEnginesMatch
{
VaultConfig memory inCreditVaultConfig = getFuzzVaultConfig(inCreditVaultId);
VaultConfig memory inDebtVaultConfig = getFuzzVaultConfig(inDebtVaultId);
vm.assume(inCreditVaultConfig.vaultId != inDebtVaultConfig.vaultId);
uint128[2] memory vaultIds;
vaultIds[0] = inCreditVaultConfig.vaultId;
vaultIds[1] = inDebtVaultConfig.vaultId;
changePrank({ msgSender: users.owner.account });
marketMakingEngine.setVaultEngine(inCreditVaultConfig.vaultId, address(1));
marketMakingEngine.setVaultEngine(inDebtVaultConfig.vaultId, address(1));
marketMakingEngine.workaround_setVaultDebt(inCreditVaultConfig.vaultId, -100);
marketMakingEngine.workaround_setVaultDepositedUsdc(inCreditVaultConfig.vaultId, 200);
marketMakingEngine.workaround_setVaultDebt(inDebtVaultConfig.vaultId, 50);
marketMakingEngine.workaround_setVaultDepositedUsdc(inDebtVaultConfig.vaultId, 10);
changePrank({ msgSender: address(perpsEngine) });
vm.expectRevert(abi.encodeWithSelector(Errors.InvalidVaultDebtSettlementRequest.selector));
marketMakingEngine.rebalanceVaultsAssets(vaultIds);
}
Run test with command:
forge test --mt testFuzz_RevertWhen_InvalidVaultDebtSettlementRequestShouldntBeAsInExample
Output:
Ran 1 test for test/integration/market-making/credit-delegation-branch/rebalanceVaultsAssets/rebalanceVaultsAssets.t.sol:CreditDelegationBranch_RebalanceVaultsAssets_Integration_Test
[PASS] testFuzz_RevertWhen_InvalidVaultDebtSettlementRequestShouldntBeAsInExample(uint256,uint256) (runs: 1006, μ: 705910, ~: 706406)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.52s (1.49s CPU time)
Impact
CreditDelegationBranch.sol:rebalanceVaultsAssets()
does not work as intended and reverts
when real in-credit vault needs rebalancing from in-debt vault.
Tools Used
Manual Review
Recommendations
Change requirement:
if (
inCreditVaultUnsettledRealizedDebtUsdX18.gte(SD59x18_ZERO)
|| inDebtVaultUnsettledRealizedDebtUsdX18.lte(SD59x18_ZERO)
) {
revert Errors.InvalidVaultDebtSettlementRequest();
}