diff --git a/src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol b/src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol index a3243368..d64bba8e 100644 --- a/src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol +++ b/src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol @@ -13,6 +13,8 @@ import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; contract FixedFeeStrategy is IGsmFeeStrategy { using Math for uint256; + uint256 internal constant MAXIMUM_FEE_PERCENT = 5000; + uint256 internal immutable _buyFee; uint256 internal immutable _sellFee; @@ -23,8 +25,8 @@ contract FixedFeeStrategy is IGsmFeeStrategy { * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps */ constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); + require(buyFee < MAXIMUM_FEE_PERCENT, 'INVALID_BUY_FEE'); + require(sellFee < MAXIMUM_FEE_PERCENT, 'INVALID_SELL_FEE'); require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); _buyFee = buyFee; _sellFee = sellFee; diff --git a/src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol b/src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol index c2fe7f48..f37c922d 100644 --- a/src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol +++ b/src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol @@ -45,8 +45,10 @@ contract FixedPriceStrategy4626 is IGsmPriceStrategy { /// @inheritdoc IGsmPriceStrategy function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - // conversion from 4626 shares to 4626 assets (rounding down) - uint256 vaultAssets = IERC4626(UNDERLYING_ASSET).previewRedeem(assetAmount); + // conversion from 4626 shares to 4626 assets + uint256 vaultAssets = roundUp + ? IERC4626(UNDERLYING_ASSET).previewMint(assetAmount) // round up + : IERC4626(UNDERLYING_ASSET).convertToAssets(assetAmount); // round down return vaultAssets.mulDiv( PRICE_RATIO, @@ -62,7 +64,10 @@ contract FixedPriceStrategy4626 is IGsmPriceStrategy { PRICE_RATIO, roundUp ? Math.Rounding.Up : Math.Rounding.Down ); - // conversion of 4626 assets to the number of 4626 shares burned to pull that amount - return IERC4626(UNDERLYING_ASSET).previewWithdraw(vaultAssets); + // conversion of 4626 assets to 4626 shares + return + roundUp + ? IERC4626(UNDERLYING_ASSET).previewWithdraw(vaultAssets) // round up + : IERC4626(UNDERLYING_ASSET).convertToShares(vaultAssets); // round down } } diff --git a/src/test/TestGhoBase.t.sol b/src/test/TestGhoBase.t.sol index 760a0d51..fa3c3ea8 100644 --- a/src/test/TestGhoBase.t.sol +++ b/src/test/TestGhoBase.t.sol @@ -565,8 +565,8 @@ contract TestGhoBase is Test, Constants, Events { return ghoBought; } - /// Helper function to mint an amount of shares of an ERC4626 token - function _mintShares( + /// Helper function to mint an amount of assets of an ERC4626 token + function _mintVaultAssets( MockERC4626 vault, TestnetERC20 token, address receiver, @@ -579,6 +579,21 @@ contract TestGhoBase is Test, Constants, Events { vm.stopPrank(); } + /// Helper function to mint an amount of shares of an ERC4626 token + function _mintVaultShares( + MockERC4626 vault, + TestnetERC20 token, + address receiver, + uint256 sharesAmount + ) internal { + uint256 assets = vault.previewMint(sharesAmount); + vm.startPrank(FAUCET); + token.mint(FAUCET, assets); + token.approve(address(vault), assets); + vault.deposit(assets, receiver); + vm.stopPrank(); + } + /// Helper function to sell shares of an ERC4626 token in the GSM function _sellAsset( Gsm4626 gsm, @@ -588,7 +603,7 @@ contract TestGhoBase is Test, Constants, Events { uint256 amount ) internal returns (uint256) { uint256 assetsToMint = vault.previewRedeem(amount); - _mintShares(vault, token, address(this), assetsToMint); + _mintVaultAssets(vault, token, address(this), assetsToMint); vault.approve(address(gsm), amount); (, uint256 ghoBought) = gsm.sellAsset(amount, receiver); return ghoBought; diff --git a/src/test/TestGsm4626.t.sol b/src/test/TestGsm4626.t.sol index 77a4da8b..0c767097 100644 --- a/src/test/TestGsm4626.t.sol +++ b/src/test/TestGsm4626.t.sol @@ -69,7 +69,7 @@ contract TestGsm4626 is TestGhoBase { emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0)); GHO_GSM_4626.updateFeeStrategy(address(0)); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -91,7 +91,7 @@ contract TestGsm4626 is TestGhoBase { uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE); uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee; - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); assertEq( USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(ALICE)), DEFAULT_GSM_USDC_AMOUNT @@ -128,7 +128,7 @@ contract TestGsm4626 is TestGhoBase { uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE); uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee; - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -154,7 +154,7 @@ contract TestGsm4626 is TestGhoBase { gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE - 1); GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Exposure Cap', DEFAULT_CAPACITY); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(gsm), DEFAULT_GSM_USDC_EXPOSURE); @@ -234,7 +234,7 @@ contract TestGsm4626 is TestGhoBase { GHO_GSM_4626.updateFeeStrategy(address(0)); // Supply assets to the GSM first - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -267,7 +267,7 @@ contract TestGsm4626 is TestGhoBase { uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee; // Supply assets to the GSM first - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -311,7 +311,7 @@ contract TestGsm4626 is TestGhoBase { uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee; // Supply assets to the GSM first - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -354,7 +354,7 @@ contract TestGsm4626 is TestGhoBase { GHO_GSM_4626.updateFeeStrategy(address(0)); // Supply assets to the GSM first - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_EXPOSURE); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -416,7 +416,7 @@ contract TestGsm4626 is TestGhoBase { uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE); // Supply assets to the GSM first - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -436,7 +436,7 @@ contract TestGsm4626 is TestGhoBase { uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE); // Supply assets to the GSM first - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -710,7 +710,7 @@ contract TestGsm4626 is TestGhoBase { uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE); assertGt(fee, 0, 'Fee not greater than zero'); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -749,7 +749,7 @@ contract TestGsm4626 is TestGhoBase { function testRescueUnderlyingTokens() public { GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this)); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); assertEq(USDC_4626_TOKEN.balanceOf(ALICE), 0, 'Unexpected USDC balance before'); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -765,7 +765,7 @@ contract TestGsm4626 is TestGhoBase { function testRescueUnderlyingTokensWithAccruedFees() public { GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this)); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE); @@ -778,7 +778,7 @@ contract TestGsm4626 is TestGhoBase { 'Unexpected GSM USDC balance before' ); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); assertEq( USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), @@ -812,7 +812,7 @@ contract TestGsm4626 is TestGhoBase { function testSeize() public { assertEq(GHO_GSM_4626.getIsSeized(), false, 'Unexpected seize status before'); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE); @@ -844,7 +844,7 @@ contract TestGsm4626 is TestGhoBase { } function testRevertMethodsAfterSeizure() public { - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE); @@ -871,7 +871,7 @@ contract TestGsm4626 is TestGhoBase { } function testBurnAfterSeize() public { - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE); @@ -899,7 +899,7 @@ contract TestGsm4626 is TestGhoBase { } function testBurnAfterSeizeGreaterAmount() public { - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE); @@ -939,7 +939,7 @@ contract TestGsm4626 is TestGhoBase { } function testInjectGho() public { - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -979,7 +979,7 @@ contract TestGsm4626 is TestGhoBase { } function testInjectGhoMoreThanNeeded() public { - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -1015,7 +1015,7 @@ contract TestGsm4626 is TestGhoBase { } function testInjectUnderlying() public { - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -1034,7 +1034,7 @@ contract TestGsm4626 is TestGhoBase { GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, BOB); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, BOB, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, BOB, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(BOB); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -1056,7 +1056,7 @@ contract TestGsm4626 is TestGhoBase { } function testInjectUnderlyingMoreThanNeeded() public { - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); @@ -1075,7 +1075,7 @@ contract TestGsm4626 is TestGhoBase { GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, BOB); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, BOB, DEFAULT_GSM_USDC_AMOUNT + 1); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, BOB, DEFAULT_GSM_USDC_AMOUNT + 1); vm.startPrank(BOB); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT + 1); @@ -1125,7 +1125,7 @@ contract TestGsm4626 is TestGhoBase { function testDistributeFeesToTreasury() public { uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); diff --git a/src/test/TestGsm4626Edge.t.sol b/src/test/TestGsm4626Edge.t.sol index ec11458d..f1f6725c 100644 --- a/src/test/TestGsm4626Edge.t.sol +++ b/src/test/TestGsm4626Edge.t.sol @@ -118,7 +118,7 @@ contract TestGsm4626Edge is TestGhoBase { uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE); uint256 ghoOut = grossAmount - fee; - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); // Inflate exchange rate _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT, true); @@ -157,7 +157,7 @@ contract TestGsm4626Edge is TestGhoBase { uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE); uint256 ghoOut = grossAmount - fee; - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT); // Deflate exchange rate _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false); @@ -200,7 +200,7 @@ contract TestGsm4626Edge is TestGhoBase { GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Exposure Cap', DEFAULT_CAPACITY); uint128 depositAmount = DEFAULT_GSM_USDC_EXPOSURE / 2; - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, depositAmount); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, depositAmount); // Inflate exchange rate _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, depositAmount, true); @@ -227,7 +227,7 @@ contract TestGsm4626Edge is TestGhoBase { GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Exposure Cap', DEFAULT_CAPACITY); uint128 depositAmount = DEFAULT_GSM_USDC_EXPOSURE * 2; - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, depositAmount); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, depositAmount); // Deflate exchange rate _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_EXPOSURE, false); @@ -532,7 +532,7 @@ contract TestGsm4626Edge is TestGhoBase { 'Unexpected available liquidity' ); - // set the capcity to be less than the amount of fees accrued + // set the capacity to be less than the amount of fees accrued uint128 feePercentToMint = 0.3e4; // 30% uint128 margin = uint128(fee.percentMul(feePercentToMint)); uint128 capacity = DEFAULT_GSM_GHO_AMOUNT + margin; @@ -903,13 +903,19 @@ contract TestGsm4626Edge is TestGhoBase { } function testBuyAssetAtCapacityWithGain() public { + /** + * 1. Alice sellAsset with 1:1 exchangeRate, up to the maximum exposure + * 2. Exchange rate increases, there is an excess of underlying backing GHO + * 3. Alice buyAsset of the maximum exposure, but excess is not minted due to maximum exposure maxed out + * 4. Excess is minted once a buyAsset occurs and the maximum is not maxed out + */ // Use zero fees for easier calculations vm.expectEmit(true, true, false, true, address(GHO_GSM_4626)); emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0)); GHO_GSM_4626.updateFeeStrategy(address(0)); // Supply assets to the GSM first - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE); vm.startPrank(ALICE); USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_EXPOSURE); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -927,9 +933,6 @@ contract TestGsm4626Edge is TestGhoBase { assertEq(deficit, 0, 'Unexpected non-zero deficit'); uint128 buyAmount = DEFAULT_CAPACITY / (((5 * DEFAULT_GSM_USDC_EXPOSURE) / 4) / 100); - (ghoCapacity, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626)); - console2.log('ghoCapacity %e ghoLevel %e', ghoCapacity, ghoLevel); - vm.startPrank(ALICE); GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_CAPACITY); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); @@ -943,35 +946,36 @@ contract TestGsm4626Edge is TestGhoBase { // Ensure GHO level is at 0, but that excess is unchanged (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626)); (ghoCapacity, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626)); - console2.log('ghoCapacity %e ghoLevel %e', ghoCapacity, ghoLevel); assertEq(ghoLevel, 0, 'Unexpected GHO bucket level after initial sell'); (excess, deficit) = GHO_GSM_4626.getCurrentBacking(); assertEq(excess, (DEFAULT_GSM_USDC_EXPOSURE / 4) * 1e12, 'Unexpected excess'); assertEq(deficit, 0, 'Unexpected non-zero deficit'); + // Sell a bit of asset so its possible to buy vm.startPrank(ALICE); - USDC_4626_TOKEN.approve(address(GHO_GSM_4626), 1); + USDC_4626_TOKEN.approve(address(GHO_GSM_4626), 2); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); - // Expected amount is a result of a 25% gain on 1 of the underlying getting rounded down - emit SellAsset(ALICE, ALICE, 1, 1e12, 0); - GHO_GSM_4626.sellAsset(1, ALICE); + // Expected amount is a result of a 25% gain on 2 of the underlying getting rounded down + emit SellAsset(ALICE, ALICE, 2, 2e12, 0); + GHO_GSM_4626.sellAsset(2, ALICE); vm.stopPrank(); - // Ensure GHO level is at 1e12, but that excess is unchanged + // Ensure GHO level is at 2e12, but that excess is unchanged (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626)); - assertEq(ghoLevel, 1e12, 'Unexpected GHO bucket level after initial sell'); + assertEq(ghoLevel, 2e12, 'Unexpected GHO bucket level after initial sell'); (excess, deficit) = GHO_GSM_4626.getCurrentBacking(); assertEq(excess, (DEFAULT_GSM_USDC_EXPOSURE / 4) * 1e12, 'Unexpected excess'); assertEq(deficit, 0, 'Unexpected non-zero deficit'); + // Buy a bit of asset so the excess is minted vm.startPrank(ALICE); - GHO_TOKEN.approve(address(GHO_GSM_4626), 1e12); + GHO_TOKEN.approve(address(GHO_GSM_4626), 2e12); vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); - emit BuyAsset(ALICE, ALICE, 1, 1e12, 0); + emit BuyAsset(ALICE, ALICE, 1, 2e12, 0); GHO_GSM_4626.buyAsset(1, ALICE); vm.stopPrank(); - // Ensure GHO level is at the previous amount of excess, and excess is now 0 + // Ensure GHO level is at the previous amount of excess, and excess is now 1e12 (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626)); assertEq( ghoLevel, @@ -979,7 +983,49 @@ contract TestGsm4626Edge is TestGhoBase { 'Unexpected GHO bucket level after final buy' ); (excess, deficit) = GHO_GSM_4626.getCurrentBacking(); - assertEq(excess, 0, 'Unexpected excess'); + // Excess of 1e12 due to the last purchase (rounding is causing excess on every sell-buy) + assertEq(excess, 1e12, 'Unexpected excess'); + assertEq(deficit, 0, 'Unexpected non-zero deficit'); + } + + function testExcessBuildUpDueToUnbalanced4626() public { + /** + * 1. Vault gets unbalanced, 1 share equals 1.25 assets + * 2. Alice sells 2 assets for 2e12 GHO + * 3. Alice buys 1 asset for 2e12 GHO + * 4. GSM gets 1 asset due to the imprecision error caused by math and unbalance vault + */ + // Use zero fees for easier calculations + vm.expectEmit(true, true, false, true, address(GHO_GSM_4626)); + emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0)); + GHO_GSM_4626.updateFeeStrategy(address(0)); + + // Mint some vault shares first + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE); + + // Simulate imbalance in vault (e.g. gift made to the vault, yield accumulation) + _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_EXPOSURE / 4, true); + + // Sell 2 assets for 2e12 GHO + vm.startPrank(ALICE); + USDC_4626_TOKEN.approve(address(GHO_GSM_4626), 2); + vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); + // Expected amount is a result of a 25% gain on 2 of the underlying getting rounded down + emit SellAsset(ALICE, ALICE, 2, 2e12, 0); + GHO_GSM_4626.sellAsset(2, ALICE); + vm.stopPrank(); + + // Buy 1 asset for 2e12 GHO + vm.startPrank(ALICE); + GHO_TOKEN.approve(address(GHO_GSM_4626), 2e12); + vm.expectEmit(true, true, true, true, address(GHO_GSM_4626)); + emit BuyAsset(ALICE, ALICE, 1, 2e12, 0); + GHO_GSM_4626.buyAsset(1, ALICE); + vm.stopPrank(); + + (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking(); + // Excess of 1e12 due to the last purchase (rounding is causing excess on every sell-buy) + assertEq(excess, 1e12, 'Unexpected excess'); assertEq(deficit, 0, 'Unexpected non-zero deficit'); } } diff --git a/src/test/TestGsmFixedPriceStrategy4626.t.sol b/src/test/TestGsmFixedPriceStrategy4626.t.sol index 7ee3e26b..0e3093f5 100644 --- a/src/test/TestGsmFixedPriceStrategy4626.t.sol +++ b/src/test/TestGsmFixedPriceStrategy4626.t.sol @@ -54,7 +54,7 @@ contract TestGsmFixedPriceStrategy4626 is TestGhoBase { function testPriceFeedHighExchangeRate() public { FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(1e18, address(USDC_4626_TOKEN), 6); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, 100e6); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, 100e6); // Inflate exchange rate to 2 _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, 100e6, true); @@ -65,7 +65,7 @@ contract TestGsmFixedPriceStrategy4626 is TestGhoBase { function testPriceFeedLowExchangeRate() public { FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(1e18, address(USDC_4626_TOKEN), 6); - _mintShares(USDC_4626_TOKEN, USDC_TOKEN, ALICE, 100e6); + _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, 100e6); // Deflate exchange rate to 1/2 _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, 50e6, false); diff --git a/src/test/TestGsmSwapEdge.t.sol b/src/test/TestGsmSwapEdge.t.sol index 07f66c76..318bcec8 100644 --- a/src/test/TestGsmSwapEdge.t.sol +++ b/src/test/TestGsmSwapEdge.t.sol @@ -14,7 +14,7 @@ contract TestGsmSwapEdge is TestGhoBase { function testCannotBuyAllUnderlying() public { TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 5, FAUCET); FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy( - 10000000000000001, + 10000000000000001, // 1e16 + 1 address(newToken), 5 ); @@ -22,7 +22,7 @@ contract TestGsmSwapEdge is TestGhoBase { gsm.initialize(address(this), TREASURY, type(uint128).max); GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', type(uint128).max); - // Sell + // Sell 2 assets for 2e11 GHO vm.prank(FAUCET); newToken.mint(ALICE, 2); @@ -38,7 +38,7 @@ contract TestGsmSwapEdge is TestGhoBase { vm.startPrank(ALICE); GHO_TOKEN.approve(address(gsm), type(uint256).max); - // Try to buy all + // Try to buy all, which is 2 assets for 2e11+1 GHO uint256 allUnderlying = gsm.getAvailableLiquidity(); vm.expectRevert(); gsm.buyAsset(allUnderlying, ALICE); diff --git a/src/test/TestGsmSwapFuzz.t.sol b/src/test/TestGsmSwapFuzz.t.sol index 7cec7aad..d926d435 100644 --- a/src/test/TestGsmSwapFuzz.t.sol +++ b/src/test/TestGsmSwapFuzz.t.sol @@ -14,6 +14,22 @@ contract TestGsmSwapFuzz is TestGhoBase { using PercentageMath for uint256; using PercentageMath for uint128; + struct TestFuzzSwapAssetVars { + // estimation function 1 + uint256 estAssetAmount1; + uint256 estGhoAmount1; + uint256 estGrossAmount1; + uint256 estFeeAmount1; + // estimation function 2 + uint256 estAssetAmount2; + uint256 estGhoAmount2; + uint256 estGrossAmount2; + uint256 estFeeAmount2; + // swap function + uint256 exactAssetAmount; + uint256 exactGhoAmount; + } + function _checkValidPrice( FixedPriceStrategy priceStrat, uint256 assetAmount, @@ -47,6 +63,92 @@ contract TestGsmSwapFuzz is TestGhoBase { ); } + /** + * @dev Check there is no way of making money by sell-buy actions + */ + function testFuzzSellBuyNoArb( + uint8 underlyingDecimals, + uint256 priceRatio, + uint256 assetAmount, + uint256 buyFeeBps, + uint256 sellFeeBps + ) public { + TestFuzzSwapAssetVars memory vars; + + underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27)); + buyFeeBps = bound(buyFeeBps, 0, 5000 - 1); + sellFeeBps = bound(sellFeeBps, 0, 5000 - 1); + priceRatio = bound(priceRatio, 0.01e18, 100e18); + assetAmount = bound(assetAmount, 1, type(uint64).max - 1); + + TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET); + + FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy( + priceRatio, + address(newToken), + underlyingDecimals // decimals + ); + Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy)); + gsm.initialize(address(this), TREASURY, type(uint128).max); + GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', type(uint128).max); + + if (buyFeeBps > 0 || sellFeeBps > 0) { + FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps); + gsm.updateFeeStrategy(address(newFeeStrategy)); + } + + // Strat estimation + (, vars.estGhoAmount1, , ) = gsm.getGhoAmountForSellAsset(assetAmount); + (vars.estAssetAmount2, , , ) = gsm.getAssetAmountForBuyAsset(vars.estGhoAmount1); + assertLe( + vars.estAssetAmount2, + assetAmount, + 'getting more assetAmount than provided in estimation' + ); + + // Init GSM with some assets + (, uint256 aux, , ) = gsm.getGhoAmountForSellAsset(assetAmount); + vm.assume(aux > 0); + vm.startPrank(FAUCET); + newToken.mint(FAUCET, assetAmount); + newToken.approve(address(gsm), type(uint256).max); + gsm.sellAsset(assetAmount, FAUCET); + vm.stopPrank(); + + // Arb Strat estimation + (, vars.estGhoAmount1, , ) = gsm.getGhoAmountForSellAsset(assetAmount); + (vars.estAssetAmount2, , , ) = gsm.getAssetAmountForBuyAsset(vars.estGhoAmount1); + assertLe( + vars.estAssetAmount2, + assetAmount, + 'getting more assetAmount than provided in estimation' + ); + + // Top up Alice + vm.prank(FAUCET); + newToken.mint(ALICE, assetAmount); + + // Arb Strat + vm.startPrank(ALICE); + uint256 aliceBalanceBefore = newToken.balanceOf(ALICE); + newToken.approve(address(gsm), type(uint256).max); + GHO_TOKEN.approve(address(gsm), type(uint256).max); + + (, vars.exactGhoAmount) = gsm.sellAsset(assetAmount, ALICE); + (vars.estAssetAmount1, , , ) = gsm.getAssetAmountForBuyAsset(vars.exactGhoAmount); + assertLe( + vars.estAssetAmount1, + assetAmount, + 'getting more assetAmount than provided in estimation' + ); + vm.assume(vars.estAssetAmount1 > 0); // 0 value is a valid for the property to hold, but buyAsset op would fail + (vars.exactAssetAmount, ) = gsm.buyAsset(vars.estAssetAmount1, ALICE); + + assertLe(vars.exactAssetAmount, assetAmount, 'getting more assetAmount than provided in swap'); + assertLe(newToken.balanceOf(ALICE), aliceBalanceBefore, 'asset balance more than before'); + vm.stopPrank(); + } + /** * @dev It is possible to use values for price ratio that creates unbalance in the GSM, so all GHO cannot be burned. * e.g. With (1e16 + 1) priceRatio, a user gets 1e11 gho for selling 1 asset but gets 1 asset by selling 1e11+1 gho @@ -81,9 +183,7 @@ contract TestGsmSwapFuzz is TestGhoBase { assertTrue(ghoToBurn <= ghoMinted + 1, 'unexpected gsm unbalance'); // Get amount of assets can be purchased based on minted GHO amount - (uint256 assetAmount, uint256 ghoAmount, uint256 grossAmount, ) = gsm.getAssetAmountForBuyAsset( - ghoMinted - ); + (, uint256 ghoAmount, , ) = gsm.getAssetAmountForBuyAsset(ghoMinted); assertTrue(ghoAmount <= ghoMinted); } @@ -362,6 +462,8 @@ contract TestGsmSwapFuzz is TestGhoBase { uint256 buyFeeBps, uint256 sellFeeBps ) public { + TestFuzzSwapAssetVars memory vars; + underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27)); buyFeeBps = bound(buyFeeBps, 0, 5000 - 1); sellFeeBps = bound(sellFeeBps, 0, 5000 - 1); @@ -382,32 +484,41 @@ contract TestGsmSwapFuzz is TestGhoBase { gsm.updateFeeStrategy(address(newFeeStrategy)); } - (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount1, uint256 fee1) = gsm + (vars.estAssetAmount1, vars.estGhoAmount1, vars.estGrossAmount1, vars.estFeeAmount1) = gsm .getGhoAmountForSellAsset(assetAmount); - vm.assume(ghoBought > 0); - (uint256 assetAmount2, uint256 exactGhoBought, uint256 grossAmount2, uint256 fee2) = gsm - .getAssetAmountForSellAsset(ghoBought); + vm.assume(vars.estGhoAmount1 > 0); + (vars.estAssetAmount2, vars.estGhoAmount2, vars.estGrossAmount2, vars.estFeeAmount2) = gsm + .getAssetAmountForSellAsset(vars.estGhoAmount1); assertTrue( - assetAmount >= exactAssetAmount, + assetAmount >= vars.estAssetAmount1, 'exact asset amount being used is higher than the amount passed' ); assertTrue( - assetAmount >= assetAmount2, + assetAmount >= vars.estAssetAmount2, 'exact asset amount being used is higher than the amount passed' ); - assertEq(ghoBought, exactGhoBought, 'bought gho amount do not match'); - assertEq(exactAssetAmount, assetAmount2, 'given assetAmount and estimated do not match'); + assertEq(vars.estGhoAmount1, vars.estGhoAmount2, 'bought gho amount do not match'); + assertEq( + vars.estAssetAmount1, + vars.estAssetAmount2, + 'given assetAmount and estimated do not match' + ); // 1 wei precision error - assertApproxEqAbs(grossAmount1, grossAmount2, 1, 'estimated gross amounts do not match'); - assertApproxEqAbs(fee1, fee2, 1, 'estimated fees do not match'); + assertApproxEqAbs( + vars.estGrossAmount1, + vars.estGrossAmount2, + 1, + 'estimated gross amounts do not match' + ); + assertApproxEqAbs(vars.estFeeAmount1, vars.estFeeAmount2, 1, 'estimated fees do not match'); // In case of 0 sellFee if (sellFeeBps == 0) { - assertEq(grossAmount1, ghoBought, 'unexpected grossAmount1 and ghoBought'); - assertEq(grossAmount2, ghoBought, 'unexpected grossAmount2 and ghoBought'); - assertEq(fee1, 0, 'expected fee1'); - assertEq(fee2, 0, 'expected fee2'); + assertEq(vars.estGrossAmount1, vars.estGhoAmount1, 'unexpected grossAmount1 and ghoBought'); + assertEq(vars.estGrossAmount2, vars.estGhoAmount1, 'unexpected grossAmount2 and ghoBought'); + assertEq(vars.estFeeAmount1, 0, 'expected fee1'); + assertEq(vars.estFeeAmount2, 0, 'expected fee2'); } } @@ -422,6 +533,8 @@ contract TestGsmSwapFuzz is TestGhoBase { uint256 buyFeeBps, uint256 sellFeeBps ) public { + TestFuzzSwapAssetVars memory vars; + underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27)); buyFeeBps = bound(buyFeeBps, 0, 5000 - 1); sellFeeBps = bound(sellFeeBps, 0, 5000 - 1); @@ -442,50 +555,38 @@ contract TestGsmSwapFuzz is TestGhoBase { gsm.updateFeeStrategy(address(newFeeStrategy)); } - (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount1, uint256 fee1) = gsm + (vars.estAssetAmount1, vars.estGhoAmount1, vars.estGrossAmount1, vars.estFeeAmount1) = gsm .getGhoAmountForBuyAsset(assetAmount); - vm.assume(ghoSold > 0); - (uint256 assetAmount2, uint256 exactGhoSold, uint256 grossAmount2, uint256 fee2) = gsm - .getAssetAmountForBuyAsset(ghoSold); + vm.assume(vars.estGhoAmount1 > 0); + (vars.estAssetAmount2, vars.estGhoAmount2, vars.estGrossAmount2, vars.estFeeAmount2) = gsm + .getAssetAmountForBuyAsset(vars.estGhoAmount1); assertTrue( - exactAssetAmount >= assetAmount, + vars.estAssetAmount1 >= assetAmount, 'exact asset amount being used is less than the amount passed' ); assertTrue( - assetAmount2 >= assetAmount, + vars.estAssetAmount2 >= assetAmount, 'exact asset amount being used is less than the amount passed' ); - assertEq(ghoSold, exactGhoSold, 'sold gho amount do not match'); - assertEq(exactAssetAmount, assetAmount2, 'given assetAmount and estimated do not match'); - assertEq(grossAmount1, grossAmount2, 'estimated gross amounts do not match'); - assertEq(fee1, fee2, 'estimated fees do not match'); + assertEq(vars.estGhoAmount1, vars.estGhoAmount2, 'sold gho amount do not match'); + assertEq( + vars.estAssetAmount1, + vars.estAssetAmount2, + 'given assetAmount and estimated do not match' + ); + assertEq(vars.estGrossAmount1, vars.estGrossAmount2, 'estimated gross amounts do not match'); + assertEq(vars.estFeeAmount1, vars.estFeeAmount2, 'estimated fees do not match'); // In case of 0 buyFee if (buyFeeBps == 0) { - assertEq(grossAmount1, ghoSold, 'unexpected grossAmount1 and ghoSold'); - assertEq(grossAmount2, ghoSold, 'unexpected grossAmount2 and ghoSold'); - assertEq(fee1, 0, 'expected fee1'); - assertEq(fee2, 0, 'expected fee2'); + assertEq(vars.estGrossAmount1, vars.estGhoAmount1, 'unexpected grossAmount1 and ghoSold'); + assertEq(vars.estGrossAmount2, vars.estGhoAmount1, 'unexpected grossAmount2 and ghoSold'); + assertEq(vars.estFeeAmount1, 0, 'expected fee1'); + assertEq(vars.estFeeAmount2, 0, 'expected fee2'); } } - struct TestFuzzSwapAssetWithEstimationVars { - // estimation function 1 - uint256 estAssetAmount1; - uint256 estGhoAmount1; - uint256 estGrossAmount1; - uint256 estFeeAmount1; - // estimation function 2 - uint256 estAssetAmount2; - uint256 estGhoAmount2; - uint256 estGrossAmount2; - uint256 estFeeAmount2; - // swap function - uint256 exactAssetAmount; - uint256 exactGhoAmount; - } - /** * @dev Checks sellAsset is aligned with getAssetAmountForSellAsset and getGhoAmountForSellAsset */ @@ -496,7 +597,7 @@ contract TestGsmSwapFuzz is TestGhoBase { uint256 buyFeeBps, uint256 sellFeeBps ) public { - TestFuzzSwapAssetWithEstimationVars memory vars; + TestFuzzSwapAssetVars memory vars; underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27)); buyFeeBps = bound(buyFeeBps, 0, 5000 - 1); @@ -595,7 +696,7 @@ contract TestGsmSwapFuzz is TestGhoBase { uint256 buyFeeBps, uint256 sellFeeBps ) public { - TestFuzzSwapAssetWithEstimationVars memory vars; + TestFuzzSwapAssetVars memory vars; underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27)); buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);