DeFiHardhat
35,000 USDC
View results
Submission Details
Severity: low
Valid

Becasue of devision before multiplication in `LibFertilizer:remainingRecapitalization()` less fertilizers is mintable than is needed to fully recapitalize the lost LP token value

Summary

There is a division before multiplication at LibFertilizer:remainingRecapitalisation(), which will cause the protocol to enable the mint of less fertilizer than intended. This will result in less value recapitalized than lost and therefor loss of value for the holders of UNRIPE_LP tokens.

Vulnerability Details

When calling LibFertilizer::remainingRecapitalization() the amount of totalDollars this calculated that still need to be recapitalized:

uint256 totalDollars = C.dollarPerUnripeLP().mul(C.unripeLP().totalSupply()).div(DECIMALS);

For this the function C.dollarPerUnripeLP is used invoking a division operation before the following multiplication, leading to precision loss:

function dollarPerUnripeLP() internal pure returns (uint256) {
return 1e12/UNRIPE_LP_PER_DOLLAR;
}

POC

Add this file to test folder and run forge test -vv --mt test_precision_loss

// SPDX-License-Identifier: MIT
pragma solidity =0.7.6;
import {SafeMath} from "openzeppelin/utils/math/SafeMath.sol";
import "forge-std/Test.sol";
import "forge-std/console.sol";
contract TestPrecisionLoss is Test {
using SafeMath for uint256;
function setUp() public {}
function test_precision_loss() external {
uint256 UNRIPE_LP_TOTAL_SUPPLY = 96033202232306;
uint128 DECIMALS = 1e6;
uint256 UNRIPE_LP_PER_DOLLAR = 1884592; // 145_113_507_403_282 / 77_000_000
uint256 dollarPerUnripeLP = 1e12 / UNRIPE_LP_TOTAL_SUPPLY;
uint256 totalDollars1 = (1e12 / UNRIPE_LP_PER_DOLLAR)
.mul(UNRIPE_LP_TOTAL_SUPPLY)
.div(DECIMALS);
console.log("totalDollars1: ", totalDollars1);
uint256 totalDollars2 = (1e12 * UNRIPE_LP_TOTAL_SUPPLY)
.div(UNRIPE_LP_PER_DOLLAR)
.div(DECIMALS);
console.log("totalDollars2: ", totalDollars2);
console.log("the diff: ", totalDollars2 - totalDollars1);
}
}

The output of the test is the following:

Running 1 test for test/testPrecisionLoss.sol:TestPrecisionLoss
[PASS] test_precision_loss() (gas: 5691)
Logs:
totalDollars1: 50956945702101
totalDollars2: 50957025304313
the diff: 79602212

Impact

remainingRecapitalization() will always return lesser value than it should be. This will lead to less fertilizer available for mint and therefore less USD value that will get recapitalized for the UNRIPE_LP holders. Here is the related code in FertilizerFacet::mintFertilizer():

uint128 remaining = uint128(LibFertilizer.remainingRecapitalization().div(1e6));// remaining <= 77_000_000 so downcasting is safe.
require(fertilizerAmountOut <= remaining, "Fertilizer: Not enough remaining.");

Tools Used

Foundry

Recommendations

--uint256 totalDollars = C.dollarPerUnripeLP().mul(C.unripeLP().totalSupply()).div(DECIMALS);
++uint256 totalDollars = (1e12).mul(C.unripeLP().totalSupply()).div(C.unripeLPPerDollara()).div(DECIMALS);
Updates

Lead Judging Commences

giovannidisiena Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Remaining recapitalization rounding

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.