code-423n4 / 2024-01-opus-findings

0 stars 0 forks source link

Debt from a liquidated trove may not be redistributed due to an issue inside the function `shrine.redistribute_helper()` #106

Closed c4-bot-8 closed 7 months ago

c4-bot-8 commented 8 months ago

Lines of code

https://github.com/code-423n4/2024-01-opus/blob/4720e9481a4fb20f4ab4140f9cc391a23ede3817/src/core/shrine.cairo#L1881-L1887 https://github.com/code-423n4/2024-01-opus/blob/4720e9481a4fb20f4ab4140f9cc391a23ede3817/src/core/shrine.cairo#L1995-L2005 https://github.com/code-423n4/2024-01-opus/blob/4720e9481a4fb20f4ab4140f9cc391a23ede3817/src/types.cairo#L92-L95 https://github.com/code-423n4/2024-01-opus/blob/4720e9481a4fb20f4ab4140f9cc391a23ede3817/src/core/shrine.cairo#L2086

Vulnerability details

Impact

The function shrine.redistribute_helper() may wrongly calculate a liquidated trove's debt amount to be redistributed to other troves to be 0. Thus no debt may be redistributed to other troves upon liquidations.

Details:

Example:

Impact:

Note that this issue is not a division before multiplication issue, because division before multiplication is used to truncate the debt_error.

Similar issues in codebase:

Proof of Concept

POC: https://gist.github.com/zzzitron/202f6b2aedef776fbcd9d8225b36b5fd

This POC is almost identical to the existing test_shrine_redistribute_dust_yang_rounding test. The difference is only the deposited yang1 amount of TROVE_1, and an added assertion that shows that yang1 unit debt is wrongly 0 which makes the assertion and the test fail.

POC steps:

  1. Redistribute
  2. Show that yang1 unit debt is zero. The added assertion makes the test fail since the yang1 unit debt is wrongly zero.

In the POC this assertion checks whether yang1 unit debt is not zero and fails:

// test_shrine_redistribution.cairo

        assert(
            shrine.get_redistribution_for_yang(yang1_addr, expected_redistribution_id).unit_debt.is_non_zero(),
            'audit: wrong unit debt'
        );

Note that when adjusting the test by making the yang1 deposit amount for TROVE_1 smaller (using TROVE1_YANG1_DEPOSIT), that all of a sudden the test passes successfully, and yang1 unit debt is indeed not zero. This shows that when other depositors deposited a high enough amount, then the denominator redistributed_yang_recipient_pool in the division line 1881-1882 shrine.cairo will be too big resulting in the rounding down to 0.

Note that the yang1 deposit of the redistributed_trove in this POC is 1_000 Wad (trove2_yang1_amt), which is why the yang1 deposit of TROVE_1 had to be set to 10_000 (CUSTOM_TROVE1_YANG1_DEPOSIT) so that the issue arises. If the yang1 deposit of the redistributed_trove would be for example only 1 Wad than the deposit of the other trove doesn't have to be 10_000 Wad and instead it may be enough to be 10 Wad. Or as an alternative there may be 10 other troves where each of these troves deposited 1 Wad so that the sum of the other deposits is 10 Wad in that case.

Tools Used

Manual Review

Recommended Mitigation Steps

Consider adjusting the calculations by adding precision:

// shrine.cairo
+1880                           let precision = 10**18;
1881                            redistributed_yang_unit_debt = adjusted_debt_to_distribute_for_yang * precision
1882                                / redistributed_yang_recipient_pool; // @audit applying precision
1883
1884                            // Due to loss of precision from fixed point division, the actual debt distributed will be less than
1885                            // or equal to the amount of debt to distribute.
1886                            let actual_debt_distributed: Wad = redistributed_yang_unit_debt
1887                                * redistributed_yang_recipient_pool / precision; // @audit normalizing precision
1888                            debt_error = adjusted_debt_to_distribute_for_yang - actual_debt_distributed;

Assessed type

Math

c4-pre-sort commented 7 months ago

bytes032 marked the issue as sufficient quality report

c4-pre-sort commented 7 months ago

bytes032 marked the issue as primary issue

c4-sponsor commented 7 months ago

tserg (sponsor) disputed

tserg commented 7 months ago

"redistributed_yang_unit_debt = 10 / 990 = 0": This is wrong because 10 is first scaled up by wad i.e. the suggested fix is already inherent in the WadDiv implementation.

Also, if redistributed_yang_uni_debt is zero, then actual_debt_distributed on the next line would also be zero, and all the debt would go towards the debt_error instead.

Lastly, in the POC test, the unit debt for yang1 is not zero.

alex-ppg commented 7 months ago

The Warden specifies how truncation may lead to errors that would round values to 0, however, these truncations cannot occur as the Warden has failed to detect the types involved in the arithmetic operations are Wad.

As we can observe in the relevant library implementation, the native *, /, etc. arithmetic operators are overloaded and performed using the usual WadRayMath notation which would ensure they are properly scaled and do not round down as described.

c4-judge commented 7 months ago

alex-ppg marked the issue as unsatisfactory: Invalid