From cebb5dcbbc1226a900563b48a5af36b362504b1d Mon Sep 17 00:00:00 2001 From: Austrian <114922365+0xAustrian@users.noreply.github.com> Date: Tue, 14 May 2024 12:57:21 -0300 Subject: [PATCH] test: swapExactAmountIn happyPath (#14) * test: happyPath for exitPool * test: remove unnecessary mock * test: wip swapExactAmountIn * test: improve assumes * test: more progress on happyPath * test: wip * test: working version with commented require * test: improve assumes * test: bound denorms * test: bound swapFee * fix: remove unused function --- foundry.toml | 2 +- test/unit/BPool.t.sol | 111 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/foundry.toml b/foundry.toml index bd346497..015deb5e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,7 +26,7 @@ src = 'src/interfaces/' [fuzz] runs = 1000 -max_test_rejects = 1000000 +max_test_rejects = 1_000_000 [rpc_endpoints] mainnet = "${MAINNET_RPC}" diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index b221e367..cd5e28dd 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -5,6 +5,7 @@ import {BPool} from 'contracts/BPool.sol'; import {MockBPool} from 'test/smock/MockBPool.sol'; import {BConst} from 'contracts/BConst.sol'; +import {BMath} from 'contracts/BMath.sol'; import {IERC20} from 'contracts/BToken.sol'; import {Test} from 'forge-std/Test.sol'; import {LibString} from 'solmate/utils/LibString.sol'; @@ -65,6 +66,10 @@ abstract contract BasePoolTest is Test, BConst, Utils { bPool.set__publicSwap(_isPublicSwap); } + function _setSwapFee(uint256 _swapFee) internal { + bPool.set__swapFee(_swapFee); + } + function _setFinalize(bool _isFinalized) internal { bPool.set__finalized(_isFinalized); } @@ -500,7 +505,111 @@ contract BPool_Unit_ExitPool is BasePoolTest { function test_Emit_LogCall() private view {} } -contract BPool_Unit_SwapExactAmountIn is BasePoolTest { +contract BPool_Unit_SwapExactAmountIn is BasePoolTest, BMath { + address tokenIn; + address tokenOut; + + struct SwapExactAmountIn_FuzzScenario { + uint256 tokenAmountIn; + uint256 tokenInBalance; + uint256 tokenInDenorm; + uint256 tokenOutBalance; + uint256 tokenOutDenorm; + uint256 swapFee; + } + + function _setValues(SwapExactAmountIn_FuzzScenario memory _fuzz) internal { + tokenIn = tokens[0]; + tokenOut = tokens[1]; + + // Create mocks for tokenIn and tokenOut (only use the first 2 tokens) + _mockTransferFrom(tokenIn); + _mockTransfer(tokenOut); + + // Set balances + _setRecord( + tokenIn, + BPool.Record({ + bound: true, + index: 0, // NOTE: irrelevant for this method + denorm: _fuzz.tokenInDenorm, + balance: _fuzz.tokenInBalance + }) + ); + _setRecord( + tokenOut, + BPool.Record({ + bound: true, + index: 0, // NOTE: irrelevant for this method + denorm: _fuzz.tokenOutDenorm, + balance: _fuzz.tokenOutBalance + }) + ); + + // Set swapFee + _setSwapFee(_fuzz.swapFee); + // Set public swap + _setPublicSwap(true); + // Set finalize + _setFinalize(true); + } + + function _assumeHappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) internal view { + // safe bound assumptions + _fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT); + _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT); + _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE); + + // min + vm.assume(_fuzz.tokenInBalance >= MIN_BALANCE); + vm.assume(_fuzz.tokenOutBalance >= MIN_BALANCE); + + // max - calcSpotPrice (spotPriceBefore) + vm.assume(_fuzz.tokenInBalance < type(uint256).max / _fuzz.tokenInDenorm); + vm.assume(_fuzz.tokenOutBalance < type(uint256).max / _fuzz.tokenOutDenorm); + + // max - calcSpotPrice (spotPriceAfter) + vm.assume(_fuzz.tokenAmountIn < type(uint256).max - _fuzz.tokenInBalance); + vm.assume(_fuzz.tokenInBalance + _fuzz.tokenAmountIn < type(uint256).max / _fuzz.tokenInDenorm); + + // internal calculation for calcSpotPrice + uint256 _numer = bdiv(_fuzz.tokenInBalance, _fuzz.tokenInDenorm); + uint256 _denom = bdiv(_fuzz.tokenOutBalance, _fuzz.tokenOutDenorm); + uint256 _ratio = bdiv(_numer, _denom); + uint256 _scale = bdiv(BONE, bsub(BONE, _fuzz.swapFee)); + vm.assume(_ratio < type(uint256).max / _scale); + + // MAX_IN_RATIO + vm.assume(_fuzz.tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO)); + + // L338 BPool.sol + uint256 _spotPriceBefore = calcSpotPrice( + _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee + ); + uint256 _tokenAmountOut = calcOutGivenIn( + _fuzz.tokenInBalance, + _fuzz.tokenInDenorm, + _fuzz.tokenOutBalance, + _fuzz.tokenOutDenorm, + _fuzz.tokenAmountIn, + _fuzz.swapFee + ); + vm.assume(_tokenAmountOut > BONE); + vm.assume(bmul(_spotPriceBefore, _tokenAmountOut) <= _fuzz.tokenAmountIn); + } + + modifier happyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) { + _assumeHappyPath(_fuzz); + _setValues(_fuzz); + _; + } + + function test_HappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { + uint256 _maxPrice = type(uint256).max; + uint256 _minAmountOut = 0; + bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, _minAmountOut, _maxPrice); + } + function test_Revert_NotBoundTokenIn() private view {} function test_Revert_NotBoundTokenOut() private view {}