diff --git a/.forge-snapshots/newBCoWFactory.snap b/.forge-snapshots/newBCoWFactory.snap new file mode 100644 index 00000000..65bdb362 --- /dev/null +++ b/.forge-snapshots/newBCoWFactory.snap @@ -0,0 +1 @@ +4889580 \ No newline at end of file diff --git a/.forge-snapshots/newBCoWPool.snap b/.forge-snapshots/newBCoWPool.snap new file mode 100644 index 00000000..6916b44e --- /dev/null +++ b/.forge-snapshots/newBCoWPool.snap @@ -0,0 +1 @@ +4033896 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b0cee1d..fb56bc60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,20 +18,20 @@ jobs: - uses: actions/checkout@v3 - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@v1.2.0 with: version: nightly - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 20.x cache: 'yarn' - name: Install dependencies run: yarn --frozen-lockfile --network-concurrency 1 - - name: Precompile using 0.8.14 and via-ir=false + - name: Precompile contracts run: yarn build - name: Run tests @@ -45,20 +45,20 @@ jobs: - uses: actions/checkout@v3 - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@v1.2.0 with: version: nightly - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 20.x cache: 'yarn' - name: Install dependencies run: yarn --frozen-lockfile --network-concurrency 1 - - name: Precompile using 0.8.14 and via-ir=false + - name: Precompile contracts run: yarn build - name: Run tests @@ -77,14 +77,14 @@ jobs: - uses: wagoid/commitlint-github-action@v5 - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@v1.2.0 with: version: nightly - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 20.x cache: 'yarn' - name: Install bulloak diff --git a/test/integration/DeploymentGas.t.sol b/test/integration/DeploymentGas.t.sol index e2b32d95..d4604024 100644 --- a/test/integration/DeploymentGas.t.sol +++ b/test/integration/DeploymentGas.t.sol @@ -1,27 +1,48 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; +import {BCoWFactory} from 'contracts/BCoWFactory.sol'; import {BFactory} from 'contracts/BFactory.sol'; import {GasSnapshot} from 'forge-gas-snapshot/GasSnapshot.sol'; import {Test} from 'forge-std/Test.sol'; +contract MockSolutionSettler { + address public vaultRelayer; + bytes32 public domainSeparator; +} + contract DeploymentIntegrationGasTest is Test, GasSnapshot { - BFactory public factory; + BFactory public bFactory; + BCoWFactory public bCowFactory; + address solutionSettler; + address deployer = makeAddr('deployer'); function setUp() public { - factory = new BFactory(); + vm.startPrank(deployer); + bFactory = new BFactory(); + + solutionSettler = address(new MockSolutionSettler()); + bCowFactory = new BCoWFactory(solutionSettler, bytes32('appData')); } function testFactoryDeployment() public { snapStart('newBFactory'); new BFactory(); snapEnd(); + + snapStart('newBCoWFactory'); + new BCoWFactory(solutionSettler, bytes32('appData')); + snapEnd(); } - function testDeployment() public { + function testPoolDeployment() public { snapStart('newBPool'); - factory.newBPool(); + bFactory.newBPool(); + snapEnd(); + + snapStart('newBCoWPool'); + bCowFactory.newBPool(); snapEnd(); } } diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 08ace5cb..16868e32 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -818,230 +818,6 @@ contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest { } } -contract BPool_Unit_SwapExactAmountIn is SwapExactAmountInUtils { - function test_Revert_NotBoundTokenIn( - SwapExactAmountIn_FuzzScenario memory _fuzz, - address _tokenIn - ) public happyPath(_fuzz) { - assumeNotForgeAddress(_tokenIn); - vm.assume(_tokenIn != tokenIn); - vm.assume(_tokenIn != tokenOut); - - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bPool.swapExactAmountIn(_tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Revert_NotBoundTokenOut( - SwapExactAmountIn_FuzzScenario memory _fuzz, - address _tokenOut - ) public happyPath(_fuzz) { - assumeNotForgeAddress(_tokenOut); - vm.assume(_tokenOut != tokenIn); - vm.assume(_tokenOut != tokenOut); - - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, _tokenOut, 0, type(uint256).max); - } - - function test_Revert_NotFinalized(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _setFinalize(false); - - vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Revert_TokenAmountInAboveMaxIn(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _tokenAmountIn = bmul(_fuzz.tokenInBalance, MAX_IN_RATIO) + 1; - - vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector); - bPool.swapExactAmountIn(tokenIn, _tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Revert_SpotPriceAboveMaxPrice( - SwapExactAmountIn_FuzzScenario memory _fuzz, - uint256 _maxPrice - ) public happyPath(_fuzz) { - uint256 _spotPriceBefore = calcSpotPrice( - _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee - ); - vm.assume(_spotPriceBefore > 0); - _maxPrice = bound(_maxPrice, 0, _spotPriceBefore - 1); - - vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, _maxPrice); - } - - function test_Revert_TokenAmountOutBelowMinOut( - SwapExactAmountIn_FuzzScenario memory _fuzz, - uint256 _minAmountOut - ) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - _minAmountOut = bound(_minAmountOut, _tokenAmountOut + 1, type(uint256).max); - - vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, _minAmountOut, type(uint256).max); - } - - function test_Revert_Reentrancy(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _expectRevertByReentrancy(); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Revert_SpotPriceAfterBelowSpotPriceBefore() public { - vm.skip(true); - // TODO: this revert might be unreachable. Find a way to test it or remove the revert in the code. - } - - function test_Revert_SpotPriceAfterAboveMaxPrice(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - uint256 _spotPriceBefore = calcSpotPrice( - _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee - ); - uint256 _spotPriceAfter = calcSpotPrice( - _fuzz.tokenInBalance + _fuzz.tokenAmountIn, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance - _tokenAmountOut, - _fuzz.tokenOutDenorm, - _fuzz.swapFee - ); - vm.assume(_spotPriceAfter > _spotPriceBefore); - - vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, _spotPriceBefore); - } - - function test_Revert_SpotPriceBeforeAboveTokenRatio(SwapExactAmountIn_FuzzScenario memory _fuzz) public { - // Replicating _assumeHappyPath, but removing irrelevant assumptions and conditioning the revert - _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); - _fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenInDenorm); - _fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenOutDenorm); - vm.assume(_fuzz.tokenAmountIn < type(uint256).max - _fuzz.tokenInBalance); - vm.assume(_fuzz.tokenInBalance + _fuzz.tokenAmountIn < type(uint256).max / _fuzz.tokenInDenorm); - _assumeCalcSpotPrice( - _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee - ); - vm.assume(_fuzz.tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO)); - uint256 _spotPriceBefore = calcSpotPrice( - _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee - ); - _assumeCalcOutGivenIn(_fuzz.tokenInBalance, _fuzz.tokenAmountIn, _fuzz.swapFee); - uint256 _tokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - vm.assume(_tokenAmountOut > BONE); - _assumeCalcSpotPrice( - _fuzz.tokenInBalance + _fuzz.tokenAmountIn, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance - _tokenAmountOut, - _fuzz.tokenOutDenorm, - _fuzz.swapFee - ); - vm.assume(_spotPriceBefore > bdiv(_fuzz.tokenAmountIn, _tokenAmountOut)); - - _setValues(_fuzz); - - vm.expectRevert(IBPool.BPool_SpotPriceBeforeAboveTokenRatio.selector); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Emit_LogSwap(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - - vm.expectEmit(); - emit IBPool.LOG_SWAP(address(this), tokenIn, tokenOut, _fuzz.tokenAmountIn, _tokenAmountOut); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Set_ReentrancyLock(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _expectSetReentrancyLock(); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Pull_TokenAmountIn(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - vm.expectCall( - address(tokenIn), - abi.encodeWithSelector(IERC20.transferFrom.selector, address(this), address(bPool), _fuzz.tokenAmountIn) - ); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Push_TokenAmountOut(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - - vm.expectCall(address(tokenOut), abi.encodeWithSelector(IERC20.transfer.selector, address(this), _tokenAmountOut)); - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } - - function test_Returns_AmountAndPrice(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _expectedTokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - uint256 _expectedSpotPriceAfter = calcSpotPrice( - _fuzz.tokenInBalance + _fuzz.tokenAmountIn, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance - _expectedTokenAmountOut, - _fuzz.tokenOutDenorm, - _fuzz.swapFee - ); - - (uint256 _tokenAmountOut, uint256 _spotPriceAfter) = - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - - assertEq(_tokenAmountOut, _expectedTokenAmountOut); - assertEq(_spotPriceAfter, _expectedSpotPriceAfter); - } - - function test_Emit_LogCall(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - vm.expectEmit(); - bytes memory _data = abi.encodeWithSelector( - BPool.swapExactAmountIn.selector, tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max - ); - emit IBPool.LOG_CALL(BPool.swapExactAmountIn.selector, address(this), _data); - - bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max); - } -} - contract BPool_Unit_SwapExactAmountOut is BasePoolTest { address tokenIn; address tokenOut; diff --git a/test/unit/BPool/BPool_SwapExactAmountIn.t.sol b/test/unit/BPool/BPool_SwapExactAmountIn.t.sol new file mode 100644 index 00000000..709b3461 --- /dev/null +++ b/test/unit/BPool/BPool_SwapExactAmountIn.t.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BPoolBase} from './BPoolBase.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import {BNum} from 'contracts/BNum.sol'; +import {IBPool} from 'interfaces/IBPool.sol'; + +contract BPoolSwapExactAmountIn is BPoolBase, BNum { + // Valid scenario + address public tokenIn; + uint256 public tokenAmountIn = 3e18; + + uint256 public tokenInBalance = 10e18; + uint256 public tokenOutBalance = 40e18; + // pool is expected to keep 2X the value of tokenIn than tokenOut + uint256 public tokenInWeight = 2e18; + uint256 public tokenOutWeight = 1e18; + + address public tokenOut; + // (tokenInBalance / tokenInWeight) / (tokenOutBalance/ tokenOutWeight) + uint256 public spotPriceBeforeSwapWithoutFee = 0.125e18; + uint256 public spotPriceBeforeSwap = bmul(spotPriceBeforeSwapWithoutFee, bdiv(BONE, bsub(BONE, MIN_FEE))); + // from bmath: 40*(1-(10/(10+3*(1-10^-6)))^2) + uint256 public expectedAmountOut = 16.3313500227545254e18; + // (tokenInBalance / tokenInWeight) / (tokenOutBalance/ tokenOutWeight) + // (13 / 2) / (40-expectedAmountOut/ 1) + uint256 public spotPriceAfterSwapWithoutFee = 0.274624873250014625e18; + uint256 public spotPriceAfterSwap = bmul(spotPriceAfterSwapWithoutFee, bdiv(BONE, bsub(BONE, MIN_FEE))); + + function setUp() public virtual override { + super.setUp(); + tokenIn = tokens[0]; + tokenOut = tokens[1]; + bPool.set__finalized(true); + bPool.set__tokens(tokens); + _setRecord(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight})); + _setRecord(tokenOut, IBPool.Record({bound: true, index: 1, denorm: tokenOutWeight})); + + vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenInBalance))); + vm.mockCall(tokenOut, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenOutBalance))); + } + + function test_RevertWhen_ReentrancyLockIsSet() external { + bPool.call__setLock(_MUTEX_TAKEN); + // it should revert + vm.expectRevert(IBPool.BPool_Reentrancy.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap); + } + + function test_RevertWhen_PoolIsNotFinalized() external { + bPool.set__finalized(false); + // it should revert + vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap); + } + + function test_RevertWhen_TokenInIsNotBound() external { + // it should revert + vm.expectRevert(IBPool.BPool_TokenNotBound.selector); + bPool.swapExactAmountIn(makeAddr('unknown token'), tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap); + } + + function test_RevertWhen_TokenOutIsNotBound() external { + // it should revert + vm.expectRevert(IBPool.BPool_TokenNotBound.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn, makeAddr('unknown token'), expectedAmountOut, spotPriceAfterSwap); + } + + function test_RevertWhen_TokenAmountInExceedsMaxAllowedRatio(uint256 tokenAmountIn_) external { + tokenAmountIn_ = bound(tokenAmountIn_, bmul(tokenInBalance, MAX_IN_RATIO) + 1, type(uint256).max); + // it should revert + vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn_, tokenOut, 0, 0); + } + + function test_RevertWhen_SpotPriceBeforeSwapExceedsMaxPrice() external { + // it should revert + vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceBeforeSwap - 1); + } + + function test_RevertWhen_CalculatedTokenAmountOutIsLessThanMinAmountOut() external { + // it should revert + vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut + 1, spotPriceAfterSwap); + } + + function test_RevertWhen_SpotPriceAfterSwapExceedsSpotPriceBeforeSwap() external { + // it should revert + // skipping since the code for this is unreachable without manually + // overriding `calcSpotPrice` in a mock: + // P_{sb} = \frac{\frac{b_i}{w_i}}{\frac{b_o}{w_o}} + // P_{sa} = \frac{\frac{b_i + a_i}{w_i}}{\frac{b_o - a_o}{w_o}} + // ...and both a_i (amount in) and a_o (amount out) are uints + vm.skip(true); + } + + function test_RevertWhen_SpotPriceAfterSwapExceedsMaxPrice() external { + // it should revert + vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap - 1); + } + + function test_RevertWhen_SpotPriceBeforeSwapExceedsTokenRatioAfterSwap() external { + uint256 tokenAmountIn_ = 30e18; + uint256 balanceTokenIn_ = 36_830_000_000_000_000_000_000_000_000_000; + uint256 weightTokenIn_ = 1e18; + uint256 balanceTokenOut_ = 18_100_000_000_000_000_000_000_000_000_000; + uint256 weightTokenOut_ = 1e18; + uint256 swapFee_ = 0.019e18; + vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(balanceTokenIn_))); + vm.mockCall(tokenOut, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(balanceTokenOut_))); + bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: weightTokenIn_})); + bPool.set__records(tokenOut, IBPool.Record({bound: true, index: 1, denorm: weightTokenOut_})); + bPool.set__swapFee(swapFee_); + // it should revert + vm.expectRevert(IBPool.BPool_SpotPriceBeforeAboveTokenRatio.selector); + bPool.swapExactAmountIn(tokenIn, tokenAmountIn_, tokenOut, 0, type(uint256).max); + } + + function test_WhenPreconditionsAreMet() external { + // it sets reentrancy lock + bPool.expectCall__setLock(_MUTEX_TAKEN); + // it calls _pullUnderlying for tokenIn + bPool.mock_call__pullUnderlying(tokenIn, address(this), tokenAmountIn); + bPool.expectCall__pullUnderlying(tokenIn, address(this), tokenAmountIn); + // it calls _pushUnderlying for tokenOut + bPool.mock_call__pushUnderlying(tokenOut, address(this), expectedAmountOut); + bPool.expectCall__pushUnderlying(tokenOut, address(this), expectedAmountOut); + // it emits a LOG_CALL event + bytes memory _data = abi.encodeCall( + IBPool.swapExactAmountIn, (tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap) + ); + vm.expectEmit(); + emit IBPool.LOG_CALL(IBPool.swapExactAmountIn.selector, address(this), _data); + // it emits a LOG_SWAP event + vm.expectEmit(); + emit IBPool.LOG_SWAP(address(this), tokenIn, tokenOut, tokenAmountIn, expectedAmountOut); + + // it returns the tokenOut amount swapped + // it returns the spot price after the swap + (uint256 out, uint256 priceAfter) = + bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap); + assertEq(out, expectedAmountOut); + assertEq(priceAfter, spotPriceAfterSwap); + // it clears the reeentrancy lock + assertEq(bPool.call__getLock(), _MUTEX_FREE); + } +} diff --git a/test/unit/BPool/BPool_SwapExactAmountIn.tree b/test/unit/BPool/BPool_SwapExactAmountIn.tree new file mode 100644 index 00000000..8d78fd2e --- /dev/null +++ b/test/unit/BPool/BPool_SwapExactAmountIn.tree @@ -0,0 +1,30 @@ +BPool::SwapExactAmountIn +├── when reentrancy lock is set +│ └── it should revert +├── when pool is not finalized +│ └── it should revert +├── when token in is not bound +│ └── it should revert +├── when token out is not bound +│ └── it should revert +├── when token amount in exceeds max allowed ratio +│ └── it should revert +├── when spot price before swap exceeds maxPrice +│ └── it should revert +├── when calculated token amount out is less than minAmountOut +│ └── it should revert +├── when spot price after swap exceeds spot price before swap +│ └── it should revert +├── when spot price after swap exceeds maxPrice +│ └── it should revert +├── when spot price before swap exceeds token ratio after swap +│ └── it should revert +└── when preconditions are met + ├── it emits a LOG_CALL event + ├── it sets the reentrancy lock + ├── it emits a LOG_SWAP event + ├── it calls _pullUnderlying for tokenIn + ├── it calls _pushUnderlying for tokenOut + ├── it returns the tokenOut amount swapped + ├── it returns the spot price after the swap + └── it clears the reeentrancy lock