From 53c951652edc1817dd59ca3136f11d4710655803 Mon Sep 17 00:00:00 2001 From: kexley <87971154+kexleyBeefy@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:42:11 +0100 Subject: [PATCH] Remove prices from mint/burn logic Add MIN_ASSET_PRICE and guard mints so that mint quotes return zero when the oracle price is below the minimum. Refactor mint/burn amount and newRatio calculations to use token-decimals-scaled supply math (capSupply and per-asset supplies) instead of price-derived capValue, simplifying and preventing division by price mismatches. Update tests and gas snapshot: adjust Scenario and unit test expectations, and add a new test validating zero quote when oracle price < MIN_ASSET_PRICE. --- contracts/vault/libraries/MinterLogic.sol | 36 ++++++++++------------- snapshots/Vault.gas.t.json | 4 +-- test/scenario/Scenario.basic.t.sol | 8 ++--- test/vault/libraries/MinterLogic.t.sol | 16 +++++++++- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/contracts/vault/libraries/MinterLogic.sol b/contracts/vault/libraries/MinterLogic.sol index dcd91f72..f0367702 100644 --- a/contracts/vault/libraries/MinterLogic.sol +++ b/contracts/vault/libraries/MinterLogic.sol @@ -17,6 +17,9 @@ library MinterLogic { /// @dev Share precision uint256 constant SHARE_PRECISION = 1e33; + /// @dev Minimum asset price + uint256 constant MIN_ASSET_PRICE = 0.995e8; + /// @notice Calculate the amount out from a swap including fees /// @param $ Storage pointer /// @param params Parameters for a swap @@ -74,38 +77,29 @@ library MinterLogic { view returns (uint256 amount, uint256 newRatio) { - (uint256 assetPrice,) = IOracle(_oracle).getPrice(params.asset); - (uint256 capPrice,) = IOracle(_oracle).getPrice(address(this)); - uint256 assetDecimalsPow = 10 ** IERC20Metadata(params.asset).decimals(); uint256 capDecimalsPow = 10 ** IERC20Metadata(address(this)).decimals(); - uint256 capSupply = IERC20(address(this)).totalSupply(); - uint256 capValue = capSupply * capPrice / capDecimalsPow; - uint256 allocationValue = IVault(address(this)).totalSupplies(params.asset) * assetPrice / assetDecimalsPow; + uint256 allocationValue = IVault(address(this)).totalSupplies(params.asset) * capDecimalsPow / assetDecimalsPow; - uint256 assetValue; if (params.mint) { - assetValue = params.amount * assetPrice / assetDecimalsPow; + (uint256 assetPrice,) = IOracle(_oracle).getPrice(params.asset); + if (assetPrice < MIN_ASSET_PRICE) { + amount = 0; + } else { + amount = params.amount * capDecimalsPow / assetDecimalsPow; + } if (capSupply == 0) { newRatio = 0; - amount = assetValue * capDecimalsPow / assetPrice; } else { - newRatio = (allocationValue + assetValue) * RAY_PRECISION / (capValue + assetValue); - amount = assetValue * capDecimalsPow / capPrice; + newRatio = RAY_PRECISION * (allocationValue + amount) / (capSupply + amount); } } else { - assetValue = params.amount * capPrice / capDecimalsPow; - if (params.amount == capSupply) { - newRatio = RAY_PRECISION; - amount = assetValue * assetDecimalsPow / assetPrice; + amount = params.amount * assetDecimalsPow / capDecimalsPow; + if (params.amount >= allocationValue || params.amount >= capSupply) { + newRatio = 0; } else { - if (allocationValue < assetValue || capValue <= assetValue) { - newRatio = 0; - } else { - newRatio = (allocationValue - assetValue) * RAY_PRECISION / (capValue - assetValue); - } - amount = assetValue * assetDecimalsPow / assetPrice; + newRatio = RAY_PRECISION * (allocationValue - params.amount) / (capSupply - params.amount); } } } diff --git a/snapshots/Vault.gas.t.json b/snapshots/Vault.gas.t.json index 2013ef14..145e9bed 100644 --- a/snapshots/Vault.gas.t.json +++ b/snapshots/Vault.gas.t.json @@ -1,4 +1,4 @@ { - "simple_burn": "253210", - "simple_mint": "250005" + "simple_burn": "111917", + "simple_mint": "142649" } \ No newline at end of file diff --git a/test/scenario/Scenario.basic.t.sol b/test/scenario/Scenario.basic.t.sol index 6e552c38..a21f00eb 100644 --- a/test/scenario/Scenario.basic.t.sol +++ b/test/scenario/Scenario.basic.t.sol @@ -92,16 +92,16 @@ contract ScenarioBasicTest is CapIntegrationFixture { cUSD.mint(address(usdt), 2000e6, 3000e18, alice, block.timestamp + 1 hours); vm.expectRevert(); /// Time - cUSD.mint(address(usdt), 2000e6, 1990e18, alice, block.timestamp - 1); - cUSD.mint(address(usdt), 2000e6, 1990e18, alice, block.timestamp + 1 hours); - assertGt(cUSD.balanceOf(alice), 1990e18); + cUSD.mint(address(usdt), 2000e6, 1989e18, alice, block.timestamp - 1); + cUSD.mint(address(usdt), 2000e6, 1989e18, alice, block.timestamp + 1 hours); + assertGt(cUSD.balanceOf(alice), 1989e18); vm.stopPrank(); vm.startPrank(bob); usdc.approve(address(cUSD), 10000e6); - cUSD.mint(address(usdc), 2000e6, 1990e6, bob, block.timestamp + 1 hours); + cUSD.mint(address(usdc), 2000e6, 1989e6, bob, block.timestamp + 1 hours); assertLt(cUSD.balanceOf(bob), 2000e18); diff --git a/test/vault/libraries/MinterLogic.t.sol b/test/vault/libraries/MinterLogic.t.sol index 97879fed..9aaae70b 100644 --- a/test/vault/libraries/MinterLogic.t.sol +++ b/test/vault/libraries/MinterLogic.t.sol @@ -152,7 +152,21 @@ contract MinterLogicTest is Test { uint256 amount = vault.minter_getAmountOut(params); - assertApproxEqAbs(amount, 200e18, 2, "Amount should account for price difference"); + assertApproxEqAbs(amount, 100e18, 2, "Amount should not account for price difference"); + } + + function test_getAmountOut_zero_when_oracle_below_min_mint_price() public { + vault.mockMinimumTotalSupply(1000e18); + vault.mockTotalSupplies(address(asset), 1000e18); + vault.mockTotalSupplies(address(vault), 1000e18); + + oracle.setPrice(address(asset), 0.994e8); + + IMinter.AmountOutParams memory params = + IMinter.AmountOutParams({ asset: address(asset), amount: 100e18, mint: true, user: msg.sender }); + + uint256 amount = vault.minter_getAmountOut(params); + assertEq(amount, 0, "Below MIN_ASSET_PRICE mint quote is zero so mint reverts on InvalidAmount"); } function test_getAmountOut_zeroAmount() public {