From dfbed2610ff249b033f9b938019e647f969750b2 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 22 Jul 2024 06:35:31 -0300 Subject: [PATCH 1/4] feat: adding btt test for joinswap extern amount in (#164) * refactor: explicitly set weights in every scenario, remove it from base contract * test: btt tests for joinswapExternAmountIn * chore: remove preexisting unit tests replaced by ones in this pr * test: query tokenIn balance --- test/unit/BPool.t.sol | 180 ------------------ test/unit/BPool/BPoolBase.sol | 3 - test/unit/BPool/BPool_Bind.t.sol | 2 + .../BPool/BPool_JoinswapExternAmountIn.t.sol | 95 +++++++++ .../BPool/BPool_JoinswapExternAmountIn.tree | 20 ++ test/unit/BPool/BPool_Unbind.t.sol | 2 + 6 files changed, 119 insertions(+), 183 deletions(-) create mode 100644 test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol create mode 100644 test/unit/BPool/BPool_JoinswapExternAmountIn.tree diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 97ad7488..096e2889 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -819,186 +819,6 @@ contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest { } } -contract BPool_Unit_JoinswapExternAmountIn is BasePoolTest { - address tokenIn; - - struct JoinswapExternAmountIn_FuzzScenario { - uint256 tokenAmountIn; - uint256 tokenInBalance; - uint256 tokenInDenorm; - uint256 totalSupply; - uint256 totalWeight; - uint256 swapFee; - } - - function _setValues(JoinswapExternAmountIn_FuzzScenario memory _fuzz) internal { - tokenIn = tokens[0]; - - // Create mocks for tokenIn - _mockTransferFrom(tokenIn); - - // Set balances - _setRecord( - tokenIn, - IBPool.Record({ - bound: true, - index: 0, // NOTE: irrelevant for this method - denorm: _fuzz.tokenInDenorm - }) - ); - _mockPoolBalance(tokenIn, _fuzz.tokenInBalance); - - // Set swapFee - _setSwapFee(_fuzz.swapFee); - // Set finalize - _setFinalize(true); - // Set totalSupply - _setTotalSupply(_fuzz.totalSupply); - // Set totalWeight - _setTotalWeight(_fuzz.totalWeight); - } - - function _assumeHappyPath(JoinswapExternAmountIn_FuzzScenario memory _fuzz) internal pure { - // safe bound assumptions - _fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT); - _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE); - _fuzz.totalWeight = bound(_fuzz.totalWeight, MIN_WEIGHT * TOKENS_AMOUNT, MAX_TOTAL_WEIGHT); - - _fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max); - _fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max); - - // max - vm.assume(_fuzz.tokenInBalance < type(uint256).max - _fuzz.tokenAmountIn); - - // MAX_IN_RATIO - vm.assume(_fuzz.tokenInBalance < type(uint256).max / MAX_IN_RATIO); - vm.assume(_fuzz.tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO)); - - // internal calculation for calcPoolOutGivenSingleIn - _assumeCalcPoolOutGivenSingleIn( - _fuzz.tokenInDenorm, - _fuzz.tokenInBalance, - _fuzz.tokenAmountIn, - _fuzz.swapFee, - _fuzz.totalWeight, - _fuzz.totalSupply - ); - } - - modifier happyPath(JoinswapExternAmountIn_FuzzScenario memory _fuzz) { - _assumeHappyPath(_fuzz); - _setValues(_fuzz); - _; - } - - function test_Revert_NotFinalized(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _setFinalize(false); - - vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector); - bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - } - - function test_Revert_NotBound( - JoinswapExternAmountIn_FuzzScenario memory _fuzz, - address _tokenIn - ) public happyPath(_fuzz) { - assumeNotForgeAddress(_tokenIn); - - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bPool.joinswapExternAmountIn(_tokenIn, _fuzz.tokenAmountIn, 0); - } - - function test_Revert_TokenAmountInAboveMaxIn(JoinswapExternAmountIn_FuzzScenario memory _fuzz) - public - happyPath(_fuzz) - { - uint256 _tokenAmountIn = bmul(_fuzz.tokenInBalance, MAX_IN_RATIO); - - vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector); - bPool.joinswapExternAmountIn(tokenIn, _tokenAmountIn + 1, 0); - } - - function test_Revert_PoolAmountOutBelowMinPoolAmountOut( - JoinswapExternAmountIn_FuzzScenario memory _fuzz, - uint256 _minPoolAmountOut - ) public happyPath(_fuzz) { - uint256 _poolAmountIn = calcPoolOutGivenSingleIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - _minPoolAmountOut = bound(_minPoolAmountOut, _poolAmountIn + 1, type(uint256).max); - - vm.expectRevert(IBPool.BPool_PoolAmountOutBelowMinPoolAmountOut.selector); - bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, _minPoolAmountOut); - } - - function test_Revert_Reentrancy(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public { - _expectRevertByReentrancy(); - bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - } - - function test_Emit_LogJoin(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - vm.expectEmit(); - emit IBPool.LOG_JOIN(address(this), tokenIn, _fuzz.tokenAmountIn); - - bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - } - - function test_Set_ReentrancyLock(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _expectSetReentrancyLock(); - bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - } - - function test_Mint_PoolShare(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - (uint256 _poolAmountOut) = bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - - assertEq(bPool.totalSupply(), _fuzz.totalSupply + _poolAmountOut); - } - - function test_Push_PoolShare(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _balanceBefore = bPool.balanceOf(address(this)); - - (uint256 _poolAmountOut) = bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - - assertEq(bPool.balanceOf(address(this)), _balanceBefore + _poolAmountOut); - } - - function test_Pull_Underlying(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - vm.expectCall( - address(tokenIn), - abi.encodeWithSelector(IERC20.transferFrom.selector, address(this), address(bPool), _fuzz.tokenAmountIn) - ); - bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - } - - function test_Returns_PoolAmountOut(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _expectedPoolAmountOut = calcPoolOutGivenSingleIn( - _fuzz.tokenInBalance, - _fuzz.tokenInDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.tokenAmountIn, - _fuzz.swapFee - ); - - (uint256 _poolAmountOut) = bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - - assertEq(_poolAmountOut, _expectedPoolAmountOut); - } - - function test_Emit_LogCall(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - vm.expectEmit(); - bytes memory _data = abi.encodeWithSelector(BPool.joinswapExternAmountIn.selector, tokenIn, _fuzz.tokenAmountIn, 0); - emit IBPool.LOG_CALL(BPool.joinswapExternAmountIn.selector, address(this), _data); - - bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0); - } -} - contract BPool_Unit_JoinswapPoolAmountOut is BasePoolTest { address tokenIn; diff --git a/test/unit/BPool/BPoolBase.sol b/test/unit/BPool/BPoolBase.sol index 0516fcec..113ddffd 100644 --- a/test/unit/BPool/BPoolBase.sol +++ b/test/unit/BPool/BPoolBase.sol @@ -11,9 +11,6 @@ contract BPoolBase is Test, BConst, Utils { MockBPool public bPool; address public deployer = makeAddr('deployer'); - uint256 public tokenWeight = 1e18; - uint256 public totalWeight = 10e18; - function setUp() public virtual { vm.prank(deployer); bPool = new MockBPool(); diff --git a/test/unit/BPool/BPool_Bind.t.sol b/test/unit/BPool/BPool_Bind.t.sol index 0b146ac7..b3570b0b 100644 --- a/test/unit/BPool/BPool_Bind.t.sol +++ b/test/unit/BPool/BPool_Bind.t.sol @@ -7,6 +7,8 @@ import {IBPool} from 'interfaces/IBPool.sol'; contract BPoolBind is BPoolBase { uint256 public tokenBindBalance = 100e18; + uint256 public tokenWeight = 1e18; + uint256 public totalWeight = 10e18; function setUp() public virtual override { super.setUp(); diff --git a/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol b/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol new file mode 100644 index 00000000..04981553 --- /dev/null +++ b/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol @@ -0,0 +1,95 @@ +// 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 BPoolJoinswapExternAmountIn is BPoolBase, BNum { + address public tokenIn; + + // Valid scenario + uint256 public tokenAmountIn = 1e18; + uint256 public tokenInWeight = 2e18; + uint256 public totalWeight = 10e18; + uint256 public tokenInBalance = 50e18; + + // (((tokenAmountIn*(1-(1-tokenInWeight/totalWeight)*MIN_FEE)+tokenInBalance)/tokenInBalance)^(tokenInWeight/totalWeight))*INIT_POOL_SUPPLY - INIT_POOL_SUPPLY + // (((1*(1-(1-2/10)*(10^-6))+50)/50)^(2/10))*100 - 100 + // 0.396837555601045600 + uint256 public expectedPoolOut = 0.3968375556010456e18; + + function setUp() public virtual override { + super.setUp(); + tokenIn = tokens[0]; + bPool.set__finalized(true); + // mint an initial amount of pool shares (expected to happen at _finalize) + bPool.call__mintPoolShare(INIT_POOL_SUPPLY); + bPool.set__tokens(_tokensToMemory()); + bPool.set__totalWeight(totalWeight); + _setRecord(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight})); + vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenInBalance))); + } + + function test_RevertWhen_ReentrancyLockIsSet() external { + bPool.call__setLock(_MUTEX_TAKEN); + // it should revert + vm.expectRevert(IBPool.BPool_Reentrancy.selector); + bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut); + } + + function test_RevertWhen_PoolIsNotFinalized() external { + bPool.set__finalized(false); + // it should revert + vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector); + bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut); + } + + function test_RevertWhen_TokenIsNotBound() external { + // it should revert + vm.expectRevert(IBPool.BPool_TokenNotBound.selector); + bPool.joinswapExternAmountIn(makeAddr('unknown token'), tokenAmountIn, expectedPoolOut); + } + + function test_RevertWhen_TokenAmountInExceedsMaxRatio(uint256 amountIn) external { + amountIn = bound(amountIn, bdiv(INIT_POOL_SUPPLY, MAX_IN_RATIO) + 1, type(uint256).max); + // it should revert + vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector); + bPool.joinswapExternAmountIn(tokenIn, amountIn, expectedPoolOut); + } + + function test_RevertWhen_CalculatedPoolAmountOutIsLessThanExpected(uint256 expectedPoolOut_) external { + expectedPoolOut_ = bound(expectedPoolOut_, expectedPoolOut + 1, type(uint256).max); + // it should revert + vm.expectRevert(IBPool.BPool_PoolAmountOutBelowMinPoolAmountOut.selector); + bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut_); + } + + function test_WhenPreconditionsAreMet() external { + // it sets reentrancy lock + bPool.expectCall__setLock(_MUTEX_TAKEN); + // it queries the contracts token in balance + vm.expectCall(tokenIn, abi.encodeCall(IERC20.balanceOf, (address(bPool)))); + // it calls _pullUnderlying for token + bPool.mock_call__pullUnderlying(tokenIn, address(this), tokenAmountIn); + bPool.expectCall__pullUnderlying(tokenIn, address(this), tokenAmountIn); + // it mints the pool shares + bPool.expectCall__mintPoolShare(expectedPoolOut); + // it sends pool shares to caller + bPool.expectCall__pushPoolShare(address(this), expectedPoolOut); + // it emits LOG_CALL event + bytes memory _data = + abi.encodeWithSelector(IBPool.joinswapExternAmountIn.selector, tokenIn, tokenAmountIn, expectedPoolOut); + vm.expectEmit(); + emit IBPool.LOG_CALL(IBPool.joinswapExternAmountIn.selector, address(this), _data); + // it emits LOG_JOIN event for token + vm.expectEmit(); + emit IBPool.LOG_JOIN(address(this), tokenIn, tokenAmountIn); + bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut); + + // it clears the reentrancy lock + assertEq(_MUTEX_FREE, bPool.call__getLock()); + } +} diff --git a/test/unit/BPool/BPool_JoinswapExternAmountIn.tree b/test/unit/BPool/BPool_JoinswapExternAmountIn.tree new file mode 100644 index 00000000..6e49f3ab --- /dev/null +++ b/test/unit/BPool/BPool_JoinswapExternAmountIn.tree @@ -0,0 +1,20 @@ +BPool::JoinswapExternAmountIn +├── when reentrancy lock is set +│ └── it should revert +├── when pool is not finalized +│ └── it should revert +├── when token is not bound +│ └── it should revert +├── when token amount in exceeds max ratio +│ └── it should revert +├── when calculated pool amount out is less than expected +│ └── it should revert +└── when preconditions are met + ├── it emits LOG_CALL event + ├── it sets the reentrancy lock + ├── it queries the contracts token in balance + ├── it emits LOG_JOIN event for token + ├── it calls _pullUnderlying for token + ├── it mints the pool shares + ├── it sends pool shares to caller + └── it clears the reentrancy lock diff --git a/test/unit/BPool/BPool_Unbind.t.sol b/test/unit/BPool/BPool_Unbind.t.sol index 19eb9ced..8741b776 100644 --- a/test/unit/BPool/BPool_Unbind.t.sol +++ b/test/unit/BPool/BPool_Unbind.t.sol @@ -8,6 +8,8 @@ import {IBPool} from 'interfaces/IBPool.sol'; contract BPoolUnbind is BPoolBase { uint256 public boundTokenAmount = 100e18; + uint256 public tokenWeight = 1e18; + uint256 public totalWeight = 10e18; function setUp() public virtual override { super.setUp(); From cb92f9da93011faa65b98d6448adba82492a7479 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 22 Jul 2024 13:27:56 -0300 Subject: [PATCH 2/4] feat: add btt tests for exitswap pool amount in (#169) * refactor: explicitly set weights in every scenario, remove it from base contract * test: btt tests for joinswapExternAmountIn * chore: remove preexisting unit tests replaced by ones in this pr * test: query tokenIn balance * test: btt tests for exitswapPoolAmountIn * chore: remove preexisting unit tests replaced by ones in this pr * fix: feedback from review --- test/unit/BPool.t.sol | 249 ------------------ .../BPool/BPool_ExitswapPoolAmountIn.t.sol | 108 ++++++++ .../BPool/BPool_ExitswapPoolAmountIn.tree | 24 ++ 3 files changed, 132 insertions(+), 249 deletions(-) create mode 100644 test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol create mode 100644 test/unit/BPool/BPool_ExitswapPoolAmountIn.tree diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 096e2889..f9828571 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -1051,255 +1051,6 @@ contract BPool_Unit_JoinswapPoolAmountOut is BasePoolTest { } } -contract BPool_Unit_ExitswapPoolAmountIn is BasePoolTest { - address tokenOut; - - struct ExitswapPoolAmountIn_FuzzScenario { - uint256 poolAmountIn; - uint256 tokenOutBalance; - uint256 tokenOutDenorm; - uint256 totalSupply; - uint256 totalWeight; - uint256 swapFee; - } - - function _setValues(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) internal { - tokenOut = tokens[0]; - - // Create mocks for tokenOut - _mockTransfer(tokenOut); - - // Set balances - _setRecord( - tokenOut, - IBPool.Record({ - bound: true, - index: 0, // NOTE: irrelevant for this method - denorm: _fuzz.tokenOutDenorm - }) - ); - _mockPoolBalance(tokenOut, _fuzz.tokenOutBalance); - - // Set swapFee - _setSwapFee(_fuzz.swapFee); - // Set finalize - _setFinalize(true); - // Set balance - _setPoolBalance(address(this), _fuzz.poolAmountIn); // give LP tokens to fn caller - // Set totalSupply - _setTotalSupply(_fuzz.totalSupply - _fuzz.poolAmountIn); - // Set totalWeight - _setTotalWeight(_fuzz.totalWeight); - } - - function _assumeHappyPath(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) internal pure { - // safe bound assumptions - _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT); - _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE); - _fuzz.totalWeight = bound(_fuzz.totalWeight, MIN_WEIGHT * TOKENS_AMOUNT, MAX_TOTAL_WEIGHT); - _fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max); - - // max - vm.assume(_fuzz.poolAmountIn < _fuzz.totalSupply); - vm.assume(_fuzz.totalSupply < type(uint256).max - _fuzz.poolAmountIn); - - // min - vm.assume(_fuzz.tokenOutBalance >= MIN_BALANCE); - - // internal calculation for calcSingleOutGivenPoolIn - _assumeCalcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - - uint256 _tokenAmountOut = calcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - - // max - vm.assume(_fuzz.tokenOutBalance < type(uint256).max - _tokenAmountOut); - - // MAX_OUT_RATIO - vm.assume(_fuzz.tokenOutBalance < type(uint256).max / MAX_OUT_RATIO); - vm.assume(_tokenAmountOut <= bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO)); - } - - modifier happyPath(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) { - _assumeHappyPath(_fuzz); - _setValues(_fuzz); - _; - } - - function test_Revert_NotFinalized(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _setFinalize(false); - - vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector); - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - } - - function test_Revert_NotBound( - ExitswapPoolAmountIn_FuzzScenario memory _fuzz, - address _tokenOut - ) public happyPath(_fuzz) { - vm.assume(_tokenOut != tokenOut); - assumeNotForgeAddress(_tokenOut); - - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bPool.exitswapPoolAmountIn(_tokenOut, _fuzz.poolAmountIn, 0); - } - - function test_Revert_TokenAmountOutBelowMinAmountOut( - ExitswapPoolAmountIn_FuzzScenario memory _fuzz, - uint256 _minAmountOut - ) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - _minAmountOut = bound(_minAmountOut, _tokenAmountOut + 1, type(uint256).max); - - vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinAmountOut.selector); - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, _minAmountOut); - } - - function test_Revert_TokenAmountOutAboveMaxOut(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public { - // Replicating _assumeHappyPath, but removing irrelevant assumptions and conditioning the revert - _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT); - _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE); - _fuzz.totalWeight = bound(_fuzz.totalWeight, MIN_WEIGHT * TOKENS_AMOUNT, MAX_TOTAL_WEIGHT); - _fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max / MAX_OUT_RATIO); - _fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max); - vm.assume(_fuzz.totalSupply < type(uint256).max - _fuzz.poolAmountIn); - vm.assume(_fuzz.poolAmountIn < _fuzz.totalSupply); - _assumeCalcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - uint256 _tokenAmountOut = calcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - vm.assume(_fuzz.tokenOutBalance < type(uint256).max / MAX_OUT_RATIO); - vm.assume(_tokenAmountOut > bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO)); - - _setValues(_fuzz); - - vm.expectRevert(IBPool.BPool_TokenAmountOutAboveMaxOut.selector); - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - } - - function test_Revert_Reentrancy(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _expectRevertByReentrancy(); - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - } - - function test_Emit_LogExit(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - - vm.expectEmit(); - emit IBPool.LOG_EXIT(address(this), tokenOut, _tokenAmountOut); - - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - } - - function test_Pull_PoolShare(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _balanceBefore = bPool.balanceOf(address(this)); - - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - - assertEq(bPool.balanceOf(address(this)), _balanceBefore - _fuzz.poolAmountIn); - } - - function test_Set_ReentrancyLock(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - _expectSetReentrancyLock(); - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - } - - function test_Burn_PoolShare(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _totalSupplyBefore = bPool.totalSupply(); - uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE); - - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - - assertEq(bPool.totalSupply(), _totalSupplyBefore - bsub(_fuzz.poolAmountIn, _exitFee)); - } - - function test_Push_PoolShare(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - address _factoryAddress = bPool.FACTORY(); - uint256 _balanceBefore = bPool.balanceOf(_factoryAddress); - uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE); - - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - - assertEq(bPool.balanceOf(_factoryAddress), _balanceBefore - _fuzz.poolAmountIn + _exitFee); - } - - function test_Push_Underlying(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - - vm.expectCall(address(tokenOut), abi.encodeWithSelector(IERC20.transfer.selector, address(this), _tokenAmountOut)); - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - } - - function test_Returns_TokenAmountOut(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _expectedTokenAmountOut = calcSingleOutGivenPoolIn( - _fuzz.tokenOutBalance, - _fuzz.tokenOutDenorm, - _fuzz.totalSupply, - _fuzz.totalWeight, - _fuzz.poolAmountIn, - _fuzz.swapFee - ); - - (uint256 _tokenAmountOut) = bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - - assertEq(_tokenAmountOut, _expectedTokenAmountOut); - } - - function test_Emit_LogCall(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - vm.expectEmit(); - bytes memory _data = abi.encodeWithSelector(BPool.exitswapPoolAmountIn.selector, tokenOut, _fuzz.poolAmountIn, 0); - emit IBPool.LOG_CALL(BPool.exitswapPoolAmountIn.selector, address(this), _data); - - bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0); - } -} - contract BPool_Unit_ExitswapExternAmountOut is BasePoolTest { address tokenOut; diff --git a/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol b/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol new file mode 100644 index 00000000..2650fddf --- /dev/null +++ b/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BPoolBase} from './BPoolBase.sol'; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import {BMath} from 'contracts/BMath.sol'; +import {BNum} from 'contracts/BNum.sol'; +import {IBPool} from 'interfaces/IBPool.sol'; + +contract BPoolExitSwapPoolAmountIn is BPoolBase, BMath { + // Valid scenario: + address public tokenOut; + uint256 public tokenOutWeight = 3e18; + uint256 public tokenOutBalance = 400e18; + uint256 public totalWeight = 9e18; + uint256 public poolAmountIn = 1e18; + // calcSingleOutGivenPoolIn(400, 3, 100, 9, 1, 10^(-6)) + uint256 public expectedAmountOut = 11.880392079733333329e18; + + function setUp() public virtual override { + super.setUp(); + tokenOut = tokens[1]; + _setRecord(tokenOut, IBPool.Record({bound: true, index: 0, denorm: tokenOutWeight})); + bPool.set__tokens(tokens); + bPool.set__totalWeight(totalWeight); + bPool.set__finalized(true); + bPool.call__mintPoolShare(INIT_POOL_SUPPLY); + + vm.mockCall(tokenOut, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenOutBalance))); + + bPool.mock_call__pullPoolShare(address(this), poolAmountIn); + bPool.mock_call__burnPoolShare(poolAmountIn); + bPool.mock_call__pushPoolShare(deployer, 0); + bPool.mock_call__pushUnderlying(tokenOut, address(this), expectedAmountOut); + } + + function test_RevertWhen_ReentrancyLockIsSet() external { + bPool.call__setLock(_MUTEX_TAKEN); + // it should revert + vm.expectRevert(IBPool.BPool_Reentrancy.selector); + bPool.exitswapPoolAmountIn(tokenOut, poolAmountIn, expectedAmountOut); + } + + function test_RevertWhen_PoolIsNotFinalized() external { + bPool.set__finalized(false); + // it should revert + vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector); + bPool.exitswapPoolAmountIn(tokenOut, poolAmountIn, expectedAmountOut); + } + + function test_RevertWhen_TokenIsNotBound() external { + // it should revert + vm.expectRevert(IBPool.BPool_TokenNotBound.selector); + bPool.exitswapPoolAmountIn(makeAddr('unknown token'), poolAmountIn, expectedAmountOut); + } + + function test_RevertWhen_TotalSupplyIsZero() external { + bPool.call__burnPoolShare(INIT_POOL_SUPPLY); + // it should revert + vm.expectRevert(BNum.BNum_SubUnderflow.selector); + bPool.exitswapPoolAmountIn(tokenOut, poolAmountIn, expectedAmountOut); + } + + function test_RevertWhen_ComputedTokenAmountOutIsLessThanMinAmountOut() external { + // it should revert + vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinAmountOut.selector); + bPool.exitswapPoolAmountIn(tokenOut, poolAmountIn, expectedAmountOut + 1); + } + + function test_RevertWhen_ComputedTokenAmountOutExceedsMaxAllowedRatio() external { + // trying to burn ~20 pool tokens would result in half of the tokenOut + // under management being sent to the caller: + // calcPoolInGivenSingleOut(tokenOutBalance, tokenOutWeight, INIT_POOL_SUPPLY, totalWeight, tokenOutBalance / 2, MIN_FEE); + // and MAX_OUT_RATIO is ~0.3 + uint256 poolAmountIn_ = 20e18; + // it should revert + vm.expectRevert(IBPool.BPool_TokenAmountOutAboveMaxOut.selector); + bPool.exitswapPoolAmountIn(tokenOut, poolAmountIn_, expectedAmountOut); + } + + function test_WhenPreconditionsAreMet() external { + // it sets the reentrancy lock + bPool.expectCall__setLock(_MUTEX_TAKEN); + // it queries token out balance + vm.expectCall(tokenOut, abi.encodeCall(IERC20.balanceOf, (address(bPool)))); + // it pulls poolAmountIn shares + bPool.expectCall__pullPoolShare(address(this), poolAmountIn); + // it burns poolAmountIn - exitFee shares + bPool.expectCall__burnPoolShare(poolAmountIn); + // it sends exitFee to factory + bPool.expectCall__pushPoolShare(deployer, 0); + // it calls _pushUnderlying for token out + bPool.expectCall__pushUnderlying(tokenOut, address(this), expectedAmountOut); + // it emits LOG_CALL event + bytes memory _data = abi.encodeCall(IBPool.exitswapPoolAmountIn, (tokenOut, poolAmountIn, expectedAmountOut)); + vm.expectEmit(); + emit IBPool.LOG_CALL(IBPool.exitswapPoolAmountIn.selector, address(this), _data); + // it emits LOG_EXIT event for token out + emit IBPool.LOG_EXIT(address(this), tokenOut, expectedAmountOut); + // it returns token out amount + uint256 out = bPool.exitswapPoolAmountIn(tokenOut, poolAmountIn, expectedAmountOut); + assertEq(out, expectedAmountOut); + // it clears the reentrancy lock + assertEq(bPool.call__getLock(), _MUTEX_FREE); + } +} diff --git a/test/unit/BPool/BPool_ExitswapPoolAmountIn.tree b/test/unit/BPool/BPool_ExitswapPoolAmountIn.tree new file mode 100644 index 00000000..b4e9d64a --- /dev/null +++ b/test/unit/BPool/BPool_ExitswapPoolAmountIn.tree @@ -0,0 +1,24 @@ +BPool::ExitSwapPoolAmountIn +├── when reentrancy lock is set +│ └── it should revert +├── when pool is not finalized +│ └── it should revert +├── when token is not bound +│ └── it should revert +├── when total supply is zero +│ └── it should revert // subtraction underflow +├── when computed token amount out is less than minAmountOut +│ └── it should revert +├── when computed token amount out exceeds max allowed ratio +│ └── it should revert +└── when preconditions are met + ├── it emits LOG_CALL event + ├── it sets the reentrancy lock + ├── it queries token out balance + ├── it emits LOG_EXIT event for token out + ├── it pulls poolAmountIn shares + ├── it burns poolAmountIn - exitFee shares + ├── it sends exitFee to factory + ├── it calls _pushUnderlying for token out + ├── it returns token out amount + └── it clears the reentrancy lock From 6c13de8ac3e156baf7e89b7b30563823e38d72cc Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 22 Jul 2024 13:46:55 -0300 Subject: [PATCH 3/4] feat: adding bcowpool verify btt tests (#155) * test: btt tests for bpool.swapExactAmountIn * chore: delete preexisting unit tests * test: small renames from feedback * test: be explicit about untestable code * test: adding skipped test for unreachable condition * test: code wasnt so unreachable after all * refactor: get rid of _setRecord * test: btt tests for bcowpool.verify * chore: delete preexisting unit tests * chore: testcase renaming from review * chore: get rid of _setTokens altogether * test: fuzz all possible valid order.sellAmount values * chore: rename correctOrder -> validOrder * fix: rename base file so it is skipped by coverage * test: ensure verify asks for ERC20 balances * chore: make bun happy --- test/unit/BCoWPool.t.sol | 143 +----------------- test/unit/BCoWPool/BCoWPoolBase.t.sol | 28 ++++ test/unit/BCoWPool/BCoWPool_Verify.t.sol | 132 ++++++++++++++++ test/unit/BCoWPool/BCoWPool_Verify.tree | 24 +++ test/unit/BPool.t.sol | 131 ++-------------- test/unit/BPool/BPool.t.sol | 4 +- test/unit/BPool/BPoolBase.sol | 12 +- test/unit/BPool/BPool_Bind.t.sol | 2 +- test/unit/BPool/BPool_ExitPool.t.sol | 4 +- .../BPool/BPool_ExitswapPoolAmountIn.t.sol | 2 +- test/unit/BPool/BPool_JoinPool.t.sol | 4 +- .../BPool/BPool_JoinswapExternAmountIn.t.sol | 2 +- test/unit/BPool/BPool_SwapExactAmountIn.t.sol | 4 +- .../unit/BPool/BPool_SwapExactAmountOut.t.sol | 4 +- test/unit/BPool/BPool_Unbind.t.sol | 6 +- 15 files changed, 212 insertions(+), 290 deletions(-) create mode 100644 test/unit/BCoWPool/BCoWPoolBase.t.sol create mode 100644 test/unit/BCoWPool/BCoWPool_Verify.t.sol create mode 100644 test/unit/BCoWPool/BCoWPool_Verify.tree diff --git a/test/unit/BCoWPool.t.sol b/test/unit/BCoWPool.t.sol index 7c7e566c..0a73ea41 100644 --- a/test/unit/BCoWPool.t.sol +++ b/test/unit/BCoWPool.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {BasePoolTest, SwapExactAmountInUtils} from './BPool.t.sol'; +import {BasePoolTest} from './BPool.t.sol'; import {IERC20} from '@cowprotocol/interfaces/IERC20.sol'; import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol'; import {IERC1271} from '@openzeppelin/contracts/interfaces/IERC1271.sol'; @@ -142,147 +142,6 @@ contract BCoWPool_Unit_Commit is BaseCoWPoolTest { } } -contract BCoWPool_Unit_Verify is BaseCoWPoolTest, SwapExactAmountInUtils { - function setUp() public virtual override(BaseCoWPoolTest, BasePoolTest) { - BaseCoWPoolTest.setUp(); - } - - function _assumeHappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) internal pure override { - // safe bound assumptions - _fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT); - _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT); - // LP fee when swapping via CoW will always be zero - _fuzz.swapFee = 0; - - // min - max - calcSpotPrice (spotPriceBefore) - _fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenInDenorm); - _fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenOutDenorm); - - // MAX_IN_RATIO - vm.assume(_fuzz.tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO)); - - _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); - } - - modifier assumeNotBoundToken(address _token) { - for (uint256 i = 0; i < TOKENS_AMOUNT; i++) { - vm.assume(tokens[i] != _token); - } - _; - } - - function test_Revert_NonBoundBuyToken(address _otherToken) public assumeNotBoundToken(_otherToken) { - GPv2Order.Data memory order = correctOrder; - order.buyToken = IERC20(_otherToken); - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bCoWPool.verify(order); - } - - function test_Revert_NonBoundSellToken(address _otherToken) public assumeNotBoundToken(_otherToken) { - GPv2Order.Data memory order = correctOrder; - order.sellToken = IERC20(_otherToken); - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bCoWPool.verify(order); - } - - function test_Revert_ReceiverIsNotBCoWPool(address _receiver) public { - vm.assume(_receiver != GPv2Order.RECEIVER_SAME_AS_OWNER); - GPv2Order.Data memory order = correctOrder; - order.receiver = _receiver; - vm.expectRevert(IBCoWPool.BCoWPool_ReceiverIsNotBCoWPool.selector); - bCoWPool.verify(order); - } - - function test_Revert_LargeDurationOrder(uint256 _timeOffset) public { - _timeOffset = bound(_timeOffset, MAX_ORDER_DURATION + 1, type(uint32).max - block.timestamp); - GPv2Order.Data memory order = correctOrder; - order.validTo = uint32(block.timestamp + _timeOffset); - vm.expectRevert(IBCoWPool.BCoWPool_OrderValidityTooLong.selector); - bCoWPool.verify(order); - } - - function test_Revert_NonZeroFee(uint256 _fee) public { - _fee = bound(_fee, 1, type(uint256).max); - GPv2Order.Data memory order = correctOrder; - order.feeAmount = _fee; - vm.expectRevert(IBCoWPool.BCoWPool_FeeMustBeZero.selector); - bCoWPool.verify(order); - } - - function test_Revert_InvalidOrderKind(bytes32 _orderKind) public { - vm.assume(_orderKind != GPv2Order.KIND_SELL); - GPv2Order.Data memory order = correctOrder; - order.kind = _orderKind; - vm.expectRevert(IBCoWPool.BCoWPool_InvalidOperation.selector); - bCoWPool.verify(order); - } - - function test_Revert_InvalidBuyBalanceKind(bytes32 _balanceKind) public { - vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20); - GPv2Order.Data memory order = correctOrder; - order.buyTokenBalance = _balanceKind; - vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector); - bCoWPool.verify(order); - } - - function test_Revert_InvalidSellBalanceKind(bytes32 _balanceKind) public { - vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20); - GPv2Order.Data memory order = correctOrder; - order.sellTokenBalance = _balanceKind; - vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector); - bCoWPool.verify(order); - } - - function test_Revert_TokenAmountInAboveMaxIn( - SwapExactAmountIn_FuzzScenario memory _fuzz, - uint256 _offset - ) public happyPath(_fuzz) { - _offset = bound(_offset, 1, type(uint256).max - _fuzz.tokenInBalance); - uint256 _tokenAmountIn = bmul(_fuzz.tokenInBalance, MAX_IN_RATIO) + _offset; - GPv2Order.Data memory order = correctOrder; - order.buyAmount = _tokenAmountIn; - - vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector); - bCoWPool.verify(order); - } - - function test_Revert_InsufficientReturn( - SwapExactAmountIn_FuzzScenario memory _fuzz, - uint256 _offset - ) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.tokenAmountIn, 0 - ); - _offset = bound(_offset, 1, _tokenAmountOut); - GPv2Order.Data memory order = correctOrder; - order.buyAmount = _fuzz.tokenAmountIn; - order.sellAmount = _tokenAmountOut + _offset; - - vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector); - bCoWPool.verify(order); - } - - function test_Success_HappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256 _tokenAmountOut = calcOutGivenIn( - _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.tokenAmountIn, 0 - ); - GPv2Order.Data memory order = correctOrder; - order.buyAmount = _fuzz.tokenAmountIn; - order.sellAmount = _tokenAmountOut; - - bCoWPool.verify(order); - } -} - contract BCoWPool_Unit_IsValidSignature is BaseCoWPoolTest { function setUp() public virtual override { super.setUp(); diff --git a/test/unit/BCoWPool/BCoWPoolBase.t.sol b/test/unit/BCoWPool/BCoWPoolBase.t.sol new file mode 100644 index 00000000..3418e59e --- /dev/null +++ b/test/unit/BCoWPool/BCoWPoolBase.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BPoolBase} from '../BPool/BPoolBase.sol'; +import {BCoWConst} from 'contracts/BCoWConst.sol'; +import {BNum} from 'contracts/BNum.sol'; + +import {ISettlement} from 'interfaces/ISettlement.sol'; +import {MockBCoWPool} from 'test/manual-smock/MockBCoWPool.sol'; + +contract BCoWPoolBase is BPoolBase, BCoWConst, BNum { + bytes32 public appData = bytes32('appData'); + address public cowSolutionSettler = makeAddr('cowSolutionSettler'); + bytes32 public domainSeparator = bytes32(bytes2(0xf00b)); + address public vaultRelayer = makeAddr('vaultRelayer'); + address public tokenIn; + address public tokenOut; + MockBCoWPool bCoWPool; + + function setUp() public virtual override { + super.setUp(); + tokenIn = tokens[0]; + tokenOut = tokens[1]; + vm.mockCall(cowSolutionSettler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(domainSeparator)); + vm.mockCall(cowSolutionSettler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(vaultRelayer)); + bCoWPool = new MockBCoWPool(cowSolutionSettler, appData); + } +} diff --git a/test/unit/BCoWPool/BCoWPool_Verify.t.sol b/test/unit/BCoWPool/BCoWPool_Verify.t.sol new file mode 100644 index 00000000..3af73f50 --- /dev/null +++ b/test/unit/BCoWPool/BCoWPool_Verify.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IERC20} from '@cowprotocol/interfaces/IERC20.sol'; +import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol'; + +import {BCoWPoolBase} from './BCoWPoolBase.t.sol'; +import {IBCoWPool} from 'interfaces/IBCoWPool.sol'; +import {IBPool} from 'interfaces/IBPool.sol'; + +contract BCoWPoolVerify is BCoWPoolBase { + // Valid scenario: + uint256 public tokenAmountIn = 1e18; + uint256 public tokenInBalance = 100e18; + uint256 public tokenOutBalance = 80e18; + // pool is expected to keep 4X the value of tokenIn than tokenOut + uint256 public tokenInWeight = 4e18; + uint256 public tokenOutWeight = 1e18; + // from bmath: (with fee zero) 80*(1-(100/(100+1))^(4)) + uint256 public expectedAmountOut = 3.12157244137469736e18; + GPv2Order.Data validOrder; + + function setUp() public virtual override { + super.setUp(); + bCoWPool.set__tokens(tokens); + bCoWPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight})); + bCoWPool.set__records(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))); + + validOrder = GPv2Order.Data({ + sellToken: IERC20(tokenOut), + buyToken: IERC20(tokenIn), + receiver: GPv2Order.RECEIVER_SAME_AS_OWNER, + sellAmount: expectedAmountOut, + buyAmount: tokenAmountIn, + validTo: uint32(block.timestamp + 1 minutes), + appData: appData, + feeAmount: 0, + kind: GPv2Order.KIND_SELL, + partiallyFillable: false, + sellTokenBalance: GPv2Order.BALANCE_ERC20, + buyTokenBalance: GPv2Order.BALANCE_ERC20 + }); + } + + function test_RevertWhen_BuyTokenIsNotBound() external { + validOrder.buyToken = IERC20(makeAddr('unknown token')); + // it should revert + vm.expectRevert(IBPool.BPool_TokenNotBound.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_SellTokenIsNotBound() external { + validOrder.sellToken = IERC20(makeAddr('unknown token')); + // it should revert + vm.expectRevert(IBPool.BPool_TokenNotBound.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_OrderReceiverFlagIsNotSameAsOwner() external { + validOrder.receiver = makeAddr('somebodyElse'); + // it should revert + vm.expectRevert(IBCoWPool.BCoWPool_ReceiverIsNotBCoWPool.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_OrderValidityIsTooLong(uint256 _timeOffset) external { + _timeOffset = bound(_timeOffset, MAX_ORDER_DURATION + 1, type(uint32).max - block.timestamp); + validOrder.validTo = uint32(block.timestamp + _timeOffset); + // it should revert + vm.expectRevert(IBCoWPool.BCoWPool_OrderValidityTooLong.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_FeeAmountIsNotZero(uint256 _fee) external { + _fee = bound(_fee, 1, type(uint256).max); + validOrder.feeAmount = _fee; + // it should revert + vm.expectRevert(IBCoWPool.BCoWPool_FeeMustBeZero.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_OrderKindIsNotKIND_SELL(bytes32 _orderKind) external { + vm.assume(_orderKind != GPv2Order.KIND_SELL); + validOrder.kind = _orderKind; + // it should revert + vm.expectRevert(IBCoWPool.BCoWPool_InvalidOperation.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_BuyTokenBalanceFlagIsNotERC20Balances(bytes32 _balanceKind) external { + vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20); + validOrder.buyTokenBalance = _balanceKind; + // it should revert + vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_SellTokenBalanceFlagIsNotERC20Balances(bytes32 _balanceKind) external { + vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20); + validOrder.sellTokenBalance = _balanceKind; + // it should revert + vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_OrderBuyAmountExceedsMaxRatio(uint256 _buyAmount) external { + _buyAmount = bound(_buyAmount, bmul(tokenInBalance, MAX_IN_RATIO) + 1, type(uint256).max); + validOrder.buyAmount = _buyAmount; + // it should revert + vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector); + bCoWPool.verify(validOrder); + } + + function test_RevertWhen_CalculatedTokenAmountOutIsLessThanOrderSellAmount() external { + validOrder.sellAmount += 1; + // it should revert + vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector); + bCoWPool.verify(validOrder); + } + + function test_WhenPreconditionsAreMet(uint256 _sellAmount) external { + _sellAmount = bound(_sellAmount, 0, validOrder.sellAmount); + validOrder.sellAmount = _sellAmount; + // it should query the balance of the buy token + vm.expectCall(tokenIn, abi.encodeCall(IERC20.balanceOf, (address(bCoWPool)))); + // it should query the balance of the sell token + vm.expectCall(tokenOut, abi.encodeCall(IERC20.balanceOf, (address(bCoWPool)))); + bCoWPool.verify(validOrder); + } +} diff --git a/test/unit/BCoWPool/BCoWPool_Verify.tree b/test/unit/BCoWPool/BCoWPool_Verify.tree new file mode 100644 index 00000000..56187a2a --- /dev/null +++ b/test/unit/BCoWPool/BCoWPool_Verify.tree @@ -0,0 +1,24 @@ +BCoWPool::Verify +├── when buyToken is not bound +│ └── it should revert +├── when sellToken is not bound +│ └── it should revert +├── when order receiver flag is not same as owner +│ └── it should revert +├── when order validity is too long +│ └── it should revert +├── when fee amount is not zero +│ └── it should revert +├── when order kind is not KIND_SELL +│ └── it should revert +├── when buy token balance flag is not ERC20 balances +│ └── it should revert +├── when sell token balance flag is not ERC20 balances +│ └── it should revert +├── when order buy amount exceeds max ratio +│ └── it should revert +├── when calculated token amount out is less than order sell amount +│ └── it should revert +└── when preconditions are met + ├── it should query the balance of the buy token + └── it should query the balance of the sell token diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index f9828571..7e80c886 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -34,9 +34,9 @@ abstract contract BasePoolTest is Test, BConst, Utils, BMath { function _setRandomTokens(uint256 _length) internal returns (address[] memory _tokensToAdd) { _tokensToAdd = _getDeterministicTokenArray(_length); for (uint256 i = 0; i < _length; i++) { - _setRecord(_tokensToAdd[i], IBPool.Record({bound: true, index: i, denorm: 0})); + bPool.set__records(_tokensToAdd[i], IBPool.Record({bound: true, index: i, denorm: 0})); } - _setTokens(_tokensToAdd); + bPool.set__tokens(_tokensToAdd); } function _mockTransfer(address _token) internal { @@ -53,14 +53,6 @@ abstract contract BasePoolTest is Test, BConst, Utils, BMath { vm.mockCall(_token, abi.encodeWithSelector(IERC20.balanceOf.selector, address(bPool)), abi.encode(_balance)); } - function _setTokens(address[] memory _tokens) internal { - bPool.set__tokens(_tokens); - } - - function _setRecord(address _token, IBPool.Record memory _record) internal { - bPool.set__records(_token, _record); - } - function _setSwapFee(uint256 _swapFee) internal { bPool.set__swapFee(_swapFee); } @@ -253,111 +245,6 @@ abstract contract BasePoolTest is Test, BConst, Utils, BMath { } } -abstract contract SwapExactAmountInUtils is BasePoolTest { - 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, - IBPool.Record({ - bound: true, - index: 0, // NOTE: irrelevant for this method - denorm: _fuzz.tokenInDenorm - }) - ); - _mockPoolBalance(tokenIn, _fuzz.tokenInBalance); - - _setRecord( - tokenOut, - IBPool.Record({ - bound: true, - index: 0, // NOTE: irrelevant for this method - denorm: _fuzz.tokenOutDenorm - }) - ); - _mockPoolBalance(tokenOut, _fuzz.tokenOutBalance); - - // Set swapFee - _setSwapFee(_fuzz.swapFee); - // Set finalize - _setFinalize(true); - } - - function _assumeHappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) internal view virtual { - // 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 - max - calcSpotPrice (spotPriceBefore) - _fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenInDenorm); - _fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, 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 (spotPriceBefore) - _assumeCalcSpotPrice( - _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee - ); - - // 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 - ); - - _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); - - // internal calculation for calcSpotPrice (spotPriceAfter) - _assumeCalcSpotPrice( - _fuzz.tokenInBalance + _fuzz.tokenAmountIn, - _fuzz.tokenInDenorm, - _fuzz.tokenOutBalance - _tokenAmountOut, - _fuzz.tokenOutDenorm, - _fuzz.swapFee - ); - - vm.assume(bmul(_spotPriceBefore, _tokenAmountOut) <= _fuzz.tokenAmountIn); - } - - modifier happyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) { - _assumeHappyPath(_fuzz); - _setValues(_fuzz); - _; - } -} - contract BPool_Unit_GetCurrentTokens is BasePoolTest { function test_Returns_CurrentTokens(uint256 _length) public { vm.assume(_length > 0); @@ -435,7 +322,7 @@ contract BPool_Unit_GetNormalizedWeight is BasePoolTest { _weight = bound(_weight, MIN_WEIGHT, MAX_WEIGHT); _totalWeight = bound(_totalWeight, MIN_WEIGHT, MAX_TOTAL_WEIGHT); vm.assume(_weight < _totalWeight); - _setRecord(_token, IBPool.Record({bound: true, index: 0, denorm: _weight})); + bPool.set__records(_token, IBPool.Record({bound: true, index: 0, denorm: _weight})); _setTotalWeight(_totalWeight); assertEq(bPool.getNormalizedWeight(_token), bdiv(_weight, _totalWeight)); @@ -695,9 +582,9 @@ contract BPool_Unit_GetSpotPrice is BasePoolTest { } function _setValues(GetSpotPrice_FuzzScenario memory _fuzz) internal { - _setRecord(_fuzz.tokenIn, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenInDenorm})); + bPool.set__records(_fuzz.tokenIn, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenInDenorm})); _mockPoolBalance(_fuzz.tokenIn, _fuzz.tokenInBalance); - _setRecord(_fuzz.tokenOut, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenOutDenorm})); + bPool.set__records(_fuzz.tokenOut, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenOutDenorm})); _mockPoolBalance(_fuzz.tokenOut, _fuzz.tokenOutBalance); _setSwapFee(_fuzz.swapFee); } @@ -764,9 +651,9 @@ contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest { } function _setValues(GetSpotPriceSansFee_FuzzScenario memory _fuzz) internal { - _setRecord(_fuzz.tokenIn, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenInDenorm})); + bPool.set__records(_fuzz.tokenIn, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenInDenorm})); _mockPoolBalance(_fuzz.tokenIn, _fuzz.tokenInBalance); - _setRecord(_fuzz.tokenOut, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenOutDenorm})); + bPool.set__records(_fuzz.tokenOut, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenOutDenorm})); _mockPoolBalance(_fuzz.tokenOut, _fuzz.tokenOutBalance); _setSwapFee(0); } @@ -838,7 +725,7 @@ contract BPool_Unit_JoinswapPoolAmountOut is BasePoolTest { _mockTransferFrom(tokenIn); // Set balances - _setRecord( + bPool.set__records( tokenIn, IBPool.Record({ bound: true, @@ -1070,7 +957,7 @@ contract BPool_Unit_ExitswapExternAmountOut is BasePoolTest { _mockTransfer(tokenOut); // Set balances - _setRecord( + bPool.set__records( tokenOut, IBPool.Record({ bound: true, diff --git a/test/unit/BPool/BPool.t.sol b/test/unit/BPool/BPool.t.sol index b3bebdee..264fef02 100644 --- a/test/unit/BPool/BPool.t.sol +++ b/test/unit/BPool/BPool.t.sol @@ -33,13 +33,13 @@ contract BPool is BPoolBase { } function test_IsBoundWhenTokenIsBound(address _token) external { - _setRecord(_token, IBPool.Record({bound: true, index: 0, denorm: 0})); + bPool.set__records(_token, IBPool.Record({bound: true, index: 0, denorm: 0})); // it returns true assertTrue(bPool.isBound(_token)); } function test_IsBoundWhenTokenIsNOTBound(address _token) external { - _setRecord(_token, IBPool.Record({bound: false, index: 0, denorm: 0})); + bPool.set__records(_token, IBPool.Record({bound: false, index: 0, denorm: 0})); // it returns false assertFalse(bPool.isBound(_token)); } diff --git a/test/unit/BPool/BPoolBase.sol b/test/unit/BPool/BPoolBase.sol index 113ddffd..36851e2c 100644 --- a/test/unit/BPool/BPoolBase.sol +++ b/test/unit/BPool/BPoolBase.sol @@ -21,16 +21,8 @@ contract BPoolBase is Test, BConst, Utils { function _setRandomTokens(uint256 _length) internal returns (address[] memory _tokensToAdd) { _tokensToAdd = _getDeterministicTokenArray(_length); for (uint256 i = 0; i < _length; i++) { - _setRecord(_tokensToAdd[i], IBPool.Record({bound: true, index: i, denorm: 0})); + bPool.set__records(_tokensToAdd[i], IBPool.Record({bound: true, index: i, denorm: 0})); } - _setTokens(_tokensToAdd); - } - - function _setTokens(address[] memory _tokens) internal { - bPool.set__tokens(_tokens); - } - - function _setRecord(address _token, IBPool.Record memory _record) internal { - bPool.set__records(_token, _record); + bPool.set__tokens(_tokensToAdd); } } diff --git a/test/unit/BPool/BPool_Bind.t.sol b/test/unit/BPool/BPool_Bind.t.sol index b3570b0b..c811dfb2 100644 --- a/test/unit/BPool/BPool_Bind.t.sol +++ b/test/unit/BPool/BPool_Bind.t.sol @@ -38,7 +38,7 @@ contract BPoolBind is BPoolBase { } function test_RevertWhen_TokenIsAlreadyBound() external whenCallerIsController { - _setRecord(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); + bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); // it should revert vm.expectRevert(IBPool.BPool_TokenAlreadyBound.selector); bPool.bind(tokens[0], tokenBindBalance, tokenWeight); diff --git a/test/unit/BPool/BPool_ExitPool.t.sol b/test/unit/BPool/BPool_ExitPool.t.sol index fac066ce..858cbef1 100644 --- a/test/unit/BPool/BPool_ExitPool.t.sol +++ b/test/unit/BPool/BPool_ExitPool.t.sol @@ -31,8 +31,8 @@ contract BPoolExitPool is BPoolBase, BNum { bPool.call__mintPoolShare(INIT_POOL_SUPPLY); bPool.set__tokens(_tokensToMemory()); // token weights are not used for all-token exits - _setRecord(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0})); - _setRecord(tokens[1], IBPool.Record({bound: true, index: 1, denorm: 0})); + bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0})); + bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: 0})); // underlying balances are used instead vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token0Balance))); vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token1Balance))); diff --git a/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol b/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol index 2650fddf..8032cdf7 100644 --- a/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol +++ b/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol @@ -22,7 +22,7 @@ contract BPoolExitSwapPoolAmountIn is BPoolBase, BMath { function setUp() public virtual override { super.setUp(); tokenOut = tokens[1]; - _setRecord(tokenOut, IBPool.Record({bound: true, index: 0, denorm: tokenOutWeight})); + bPool.set__records(tokenOut, IBPool.Record({bound: true, index: 0, denorm: tokenOutWeight})); bPool.set__tokens(tokens); bPool.set__totalWeight(totalWeight); bPool.set__finalized(true); diff --git a/test/unit/BPool/BPool_JoinPool.t.sol b/test/unit/BPool/BPool_JoinPool.t.sol index 1f501275..eeb01efe 100644 --- a/test/unit/BPool/BPool_JoinPool.t.sol +++ b/test/unit/BPool/BPool_JoinPool.t.sol @@ -27,8 +27,8 @@ contract BPoolJoinPool is BPoolBase { bPool.call__mintPoolShare(INIT_POOL_SUPPLY); bPool.set__tokens(_tokensToMemory()); // token weights are not used for all-token joins - _setRecord(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0})); - _setRecord(tokens[1], IBPool.Record({bound: true, index: 1, denorm: 0})); + bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0})); + bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: 0})); // underlying balances are used instead vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token0Balance))); vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token1Balance))); diff --git a/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol b/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol index 04981553..afa67556 100644 --- a/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol +++ b/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol @@ -29,7 +29,7 @@ contract BPoolJoinswapExternAmountIn is BPoolBase, BNum { bPool.call__mintPoolShare(INIT_POOL_SUPPLY); bPool.set__tokens(_tokensToMemory()); bPool.set__totalWeight(totalWeight); - _setRecord(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight})); + bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight})); vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenInBalance))); } diff --git a/test/unit/BPool/BPool_SwapExactAmountIn.t.sol b/test/unit/BPool/BPool_SwapExactAmountIn.t.sol index 709b3461..9537bf7f 100644 --- a/test/unit/BPool/BPool_SwapExactAmountIn.t.sol +++ b/test/unit/BPool/BPool_SwapExactAmountIn.t.sol @@ -35,8 +35,8 @@ contract BPoolSwapExactAmountIn is BPoolBase, BNum { 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})); + bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight})); + bPool.set__records(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))); diff --git a/test/unit/BPool/BPool_SwapExactAmountOut.t.sol b/test/unit/BPool/BPool_SwapExactAmountOut.t.sol index f648b9a9..85e168b0 100644 --- a/test/unit/BPool/BPool_SwapExactAmountOut.t.sol +++ b/test/unit/BPool/BPool_SwapExactAmountOut.t.sol @@ -36,8 +36,8 @@ contract BPoolSwapExactAmountOut is BPoolBase, BNum { 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})); + bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight})); + bPool.set__records(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))); diff --git a/test/unit/BPool/BPool_Unbind.t.sol b/test/unit/BPool/BPool_Unbind.t.sol index 8741b776..c0c0f008 100644 --- a/test/unit/BPool/BPool_Unbind.t.sol +++ b/test/unit/BPool/BPool_Unbind.t.sol @@ -44,7 +44,7 @@ contract BPoolUnbind is BPoolBase { } function test_RevertWhen_PoolIsFinalized() external whenCallerIsController { - _setRecord(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0})); + bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0})); bPool.set__finalized(true); // it should revert vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector); @@ -52,7 +52,7 @@ contract BPoolUnbind is BPoolBase { } modifier whenTokenCanBeUnbound() { - _setRecord(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); + bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); bPool.set__totalWeight(totalWeight); address[] memory tokens = new address[](1); tokens[0] = tokens[0]; @@ -83,7 +83,7 @@ contract BPoolUnbind is BPoolBase { } function test_WhenTokenIsNOTLastOnTheTokensArray() external whenCallerIsController whenTokenCanBeUnbound { - _setRecord(tokens[1], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); + bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); bPool.set__tokens(_tokensToMemory()); bPool.unbind(tokens[0]); // it removes the token record From 2a0b42939f2b3e09207ea173eef67e34cdb17dca Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 22 Jul 2024 14:00:23 -0300 Subject: [PATCH 4/4] feat: add btt tests for finalize methods (#159) * test: btt tests for bpool.swapExactAmountIn * chore: delete preexisting unit tests * test: small renames from feedback * test: be explicit about untestable code * test: adding skipped test for unreachable condition * test: code wasnt so unreachable after all * refactor: get rid of _setRecord * test: btt tests for bcowpool.verify * chore: delete preexisting unit tests * chore: testcase renaming from review * chore: get rid of _setTokens altogether * test: fuzz all possible valid order.sellAmount values * chore: rename correctOrder -> validOrder * test: btt tests for bpool.finalize * test: btt tests for bcowpool.finalize * chore: remove preexisting unit tests replaced by ones in this pr * fix: feedback from review * refactor: make caller==controller default scenario * fix: reorganize .tree * fix: feedback from review * fix: feedback from review, calling internal method directly --- test/manual-smock/MockBCoWPool.sol | 39 +++++++++++++++ test/unit/BCoWPool.t.sol | 35 ------------- test/unit/BCoWPool/BCoWPool.t.sol | 47 ++++++++++++++++++ test/unit/BCoWPool/BCoWPool.tree | 8 +++ test/unit/BCoWPool/BCoWPoolBase.sol | 28 +++++++++++ test/unit/BPool/BPool.t.sol | 49 +++++++++++++++++++ test/unit/BPool/BPool.tree | 14 ++++++ test/unit/BPool/BPoolBase.sol | 2 - test/unit/BPool/BPool_Bind.t.sol | 27 +++++----- test/unit/BPool/BPool_Bind.tree | 45 +++++++++-------- test/unit/BPool/BPool_ExitPool.t.sol | 2 +- .../BPool/BPool_ExitswapPoolAmountIn.t.sol | 4 +- test/unit/BPool/BPool_Unbind.t.sol | 19 +++---- test/unit/BPool/BPool_Unbind.tree | 37 +++++++------- 14 files changed, 246 insertions(+), 110 deletions(-) create mode 100644 test/unit/BCoWPool/BCoWPool.t.sol create mode 100644 test/unit/BCoWPool/BCoWPool.tree create mode 100644 test/unit/BCoWPool/BCoWPoolBase.sol diff --git a/test/manual-smock/MockBCoWPool.sol b/test/manual-smock/MockBCoWPool.sol index 5ce2c703..c94a6bb1 100644 --- a/test/manual-smock/MockBCoWPool.sol +++ b/test/manual-smock/MockBCoWPool.sol @@ -359,6 +359,45 @@ contract MockBCoWPool is BCoWPool, Test { vm.expectCall(address(this), abi.encodeWithSignature('_pushUnderlying(address,address,uint256)', token, to, amount)); } + function mock_call__pushPoolShare(address to, uint256 amount) public { + vm.mockCall(address(this), abi.encodeWithSignature('_pushPoolShare(address,uint256)', to, amount), abi.encode()); + } + + function _pushPoolShare(address to, uint256 amount) internal override { + (bool _success, bytes memory _data) = + address(this).call(abi.encodeWithSignature('_pushPoolShare(address,uint256)', to, amount)); + + if (_success) return abi.decode(_data, ()); + else return super._pushPoolShare(to, amount); + } + + function call__pushPoolShare(address to, uint256 amount) public { + return _pushPoolShare(to, amount); + } + + function expectCall__pushPoolShare(address to, uint256 amount) public { + vm.expectCall(address(this), abi.encodeWithSignature('_pushPoolShare(address,uint256)', to, amount)); + } + + function mock_call__mintPoolShare(uint256 amount) public { + vm.mockCall(address(this), abi.encodeWithSignature('_mintPoolShare(uint256)', amount), abi.encode()); + } + + function _mintPoolShare(uint256 amount) internal override { + (bool _success, bytes memory _data) = address(this).call(abi.encodeWithSignature('_mintPoolShare(uint256)', amount)); + + if (_success) return abi.decode(_data, ()); + else return super._mintPoolShare(amount); + } + + function call__mintPoolShare(uint256 amount) public { + return _mintPoolShare(amount); + } + + function expectCall__mintPoolShare(uint256 amount) public { + vm.expectCall(address(this), abi.encodeWithSignature('_mintPoolShare(uint256)', amount)); + } + function call__afterFinalize() public { return _afterFinalize(); } diff --git a/test/unit/BCoWPool.t.sol b/test/unit/BCoWPool.t.sol index 0a73ea41..08b6673b 100644 --- a/test/unit/BCoWPool.t.sol +++ b/test/unit/BCoWPool.t.sol @@ -78,41 +78,6 @@ contract BCoWPool_Unit_Constructor is BaseCoWPoolTest { } } -contract BCoWPool_Unit_Finalize is BaseCoWPoolTest { - function setUp() public virtual override { - super.setUp(); - - for (uint256 i = 0; i < TOKENS_AMOUNT; i++) { - vm.mockCall(tokens[i], abi.encodePacked(IERC20.approve.selector), abi.encode(true)); - } - - vm.mockCall(address(bCoWPool.FACTORY()), abi.encodeWithSelector(IBCoWFactory.logBCoWPool.selector), abi.encode()); - } - - function test_Set_Approvals() public { - for (uint256 i = 0; i < TOKENS_AMOUNT; i++) { - vm.expectCall(tokens[i], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)), 1); - } - bCoWPool.finalize(); - } - - function test_Log_IfRevert() public { - vm.mockCallRevert( - address(bCoWPool.FACTORY()), abi.encodeWithSelector(IBCoWFactory.logBCoWPool.selector), abi.encode() - ); - - vm.expectEmit(address(bCoWPool)); - emit IBCoWFactory.COWAMMPoolCreated(address(bCoWPool)); - - bCoWPool.finalize(); - } - - function test_Call_LogBCoWPool() public { - vm.expectCall(address(bCoWPool.FACTORY()), abi.encodeWithSelector(IBCoWFactory.logBCoWPool.selector), 1); - bCoWPool.finalize(); - } -} - contract BCoWPool_Unit_Commit is BaseCoWPoolTest { function test_Revert_NonSolutionSettler(address sender, bytes32 orderHash) public { vm.assume(sender != cowSolutionSettler); diff --git a/test/unit/BCoWPool/BCoWPool.t.sol b/test/unit/BCoWPool/BCoWPool.t.sol new file mode 100644 index 00000000..b596a622 --- /dev/null +++ b/test/unit/BCoWPool/BCoWPool.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IERC20} from '@cowprotocol/interfaces/IERC20.sol'; + +import {BCoWPoolBase} from './BCoWPoolBase.sol'; + +import {IBCoWFactory} from 'interfaces/IBCoWFactory.sol'; +import {IBPool} from 'interfaces/IBPool.sol'; + +contract BCoWPool_afterFinalize is BCoWPoolBase { + uint256 public tokenWeight = 1e18; + + function setUp() public virtual override { + super.setUp(); + bCoWPool.set__tokens(tokens); + bCoWPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); + bCoWPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: tokenWeight})); + + vm.mockCall(address(this), abi.encodeCall(IBCoWFactory.logBCoWPool, ()), abi.encode()); + + vm.mockCall(tokens[0], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)), abi.encode(true)); + vm.mockCall(tokens[1], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)), abi.encode(true)); + } + + function test_WhenCalled() external { + // it calls approve on every bound token + vm.expectCall(tokens[0], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max))); + vm.expectCall(tokens[1], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max))); + // it calls logBCoWPool on the factory + vm.expectCall(address(this), abi.encodeCall(IBCoWFactory.logBCoWPool, ())); + bCoWPool.call__afterFinalize(); + } + + function test_WhenFactorysLogBCoWPoolDoesNotRevert() external { + // it returns + bCoWPool.call__afterFinalize(); + } + + function test_WhenFactorysLogBCoWPoolReverts(bytes memory revertData) external { + vm.mockCallRevert(address(this), abi.encodeCall(IBCoWFactory.logBCoWPool, ()), revertData); + // it emits a COWAMMPoolCreated event + vm.expectEmit(address(bCoWPool)); + emit IBCoWFactory.COWAMMPoolCreated(address(bCoWPool)); + bCoWPool.call__afterFinalize(); + } +} diff --git a/test/unit/BCoWPool/BCoWPool.tree b/test/unit/BCoWPool/BCoWPool.tree new file mode 100644 index 00000000..0dbbd1f0 --- /dev/null +++ b/test/unit/BCoWPool/BCoWPool.tree @@ -0,0 +1,8 @@ +BCoWPool::_afterFinalize +├── when called +│ ├── it calls approve on every bound token +│ └── it calls logBCoWPool on the factory +├── when factorys logBCoWPool does not revert +│ └── it returns +└── when factorys logBCoWPool reverts + └── it emits a COWAMMPoolCreated event diff --git a/test/unit/BCoWPool/BCoWPoolBase.sol b/test/unit/BCoWPool/BCoWPoolBase.sol new file mode 100644 index 00000000..3418e59e --- /dev/null +++ b/test/unit/BCoWPool/BCoWPoolBase.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BPoolBase} from '../BPool/BPoolBase.sol'; +import {BCoWConst} from 'contracts/BCoWConst.sol'; +import {BNum} from 'contracts/BNum.sol'; + +import {ISettlement} from 'interfaces/ISettlement.sol'; +import {MockBCoWPool} from 'test/manual-smock/MockBCoWPool.sol'; + +contract BCoWPoolBase is BPoolBase, BCoWConst, BNum { + bytes32 public appData = bytes32('appData'); + address public cowSolutionSettler = makeAddr('cowSolutionSettler'); + bytes32 public domainSeparator = bytes32(bytes2(0xf00b)); + address public vaultRelayer = makeAddr('vaultRelayer'); + address public tokenIn; + address public tokenOut; + MockBCoWPool bCoWPool; + + function setUp() public virtual override { + super.setUp(); + tokenIn = tokens[0]; + tokenOut = tokens[1]; + vm.mockCall(cowSolutionSettler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(domainSeparator)); + vm.mockCall(cowSolutionSettler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(vaultRelayer)); + bCoWPool = new MockBCoWPool(cowSolutionSettler, appData); + } +} diff --git a/test/unit/BPool/BPool.t.sol b/test/unit/BPool/BPool.t.sol index 264fef02..6bf2db80 100644 --- a/test/unit/BPool/BPool.t.sol +++ b/test/unit/BPool/BPool.t.sol @@ -6,6 +6,8 @@ import {IBPool} from 'interfaces/IBPool.sol'; import {MockBPool} from 'test/smock/MockBPool.sol'; contract BPool is BPoolBase { + uint256 public tokenWeight = 1e18; + function test_ConstructorWhenCalled(address _deployer) external { vm.prank(_deployer); MockBPool _newBPool = new MockBPool(); @@ -50,4 +52,51 @@ contract BPool is BPoolBase { // it returns number of tokens assertEq(bPool.getNumTokens(), _tokensToAdd); } + + function test_FinalizeRevertWhen_CallerIsNotController(address _caller) external { + vm.assume(_caller != address(this)); + vm.prank(_caller); + // it should revert + vm.expectRevert(IBPool.BPool_CallerIsNotController.selector); + bPool.finalize(); + } + + function test_FinalizeRevertWhen_PoolIsFinalized() external { + bPool.set__finalized(true); + // it should revert + vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector); + bPool.finalize(); + } + + function test_FinalizeRevertWhen_ThereAreTooFewTokensBound() external { + address[] memory tokens_ = new address[](1); + tokens_[0] = tokens[0]; + bPool.set__tokens(tokens_); + // it should revert + vm.expectRevert(IBPool.BPool_TokensBelowMinimum.selector); + bPool.finalize(); + } + + function test_FinalizeWhenPreconditionsAreMet() external { + bPool.set__tokens(tokens); + bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); + bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: tokenWeight})); + bPool.mock_call__mintPoolShare(INIT_POOL_SUPPLY); + bPool.mock_call__pushPoolShare(address(this), INIT_POOL_SUPPLY); + + // it calls _afterFinalize hook + bPool.expectCall__afterFinalize(); + // it mints initial pool shares + bPool.expectCall__mintPoolShare(INIT_POOL_SUPPLY); + // it sends initial pool shares to controller + bPool.expectCall__pushPoolShare(address(this), INIT_POOL_SUPPLY); + // it emits a LOG_CALL event + bytes memory data = abi.encodeCall(IBPool.finalize, ()); + vm.expectEmit(address(bPool)); + emit IBPool.LOG_CALL(IBPool.finalize.selector, address(this), data); + + bPool.finalize(); + // it finalizes the pool + assertEq(bPool.call__finalized(), true); + } } diff --git a/test/unit/BPool/BPool.tree b/test/unit/BPool/BPool.tree index 379bec03..1f8f368e 100644 --- a/test/unit/BPool/BPool.tree +++ b/test/unit/BPool/BPool.tree @@ -20,3 +20,17 @@ BPool::isBound BPool::getNumTokens └── when called └── it returns number of tokens + +BPool::finalize +├── when caller is not controller +│ └── it should revert +├── when pool is finalized +│ └── it should revert +├── when there are too few tokens bound +│ └── it should revert +└── when preconditions are met + ├── it emits LOG_CALL event + ├── it finalizes the pool + ├── it mints initial pool shares + ├── it sends initial pool shares to controller + └── it calls _afterFinalize hook diff --git a/test/unit/BPool/BPoolBase.sol b/test/unit/BPool/BPoolBase.sol index 36851e2c..092d8147 100644 --- a/test/unit/BPool/BPoolBase.sol +++ b/test/unit/BPool/BPoolBase.sol @@ -9,10 +9,8 @@ import {Utils} from 'test/utils/Utils.sol'; contract BPoolBase is Test, BConst, Utils { MockBPool public bPool; - address public deployer = makeAddr('deployer'); function setUp() public virtual { - vm.prank(deployer); bPool = new MockBPool(); tokens.push(makeAddr('token0')); tokens.push(makeAddr('token1')); diff --git a/test/unit/BPool/BPool_Bind.t.sol b/test/unit/BPool/BPool_Bind.t.sol index c811dfb2..12fb07a4 100644 --- a/test/unit/BPool/BPool_Bind.t.sol +++ b/test/unit/BPool/BPool_Bind.t.sol @@ -26,76 +26,71 @@ contract BPoolBind is BPoolBase { function test_RevertWhen_CallerIsNOTController(address _caller) external { // it should revert - vm.assume(_caller != deployer); + vm.assume(_caller != address(this)); vm.prank(_caller); vm.expectRevert(IBPool.BPool_CallerIsNotController.selector); bPool.bind(tokens[0], tokenBindBalance, tokenWeight); } - modifier whenCallerIsController() { - vm.startPrank(deployer); - _; - } - - function test_RevertWhen_TokenIsAlreadyBound() external whenCallerIsController { + function test_RevertWhen_TokenIsAlreadyBound() external { bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); // it should revert vm.expectRevert(IBPool.BPool_TokenAlreadyBound.selector); bPool.bind(tokens[0], tokenBindBalance, tokenWeight); } - function test_RevertWhen_PoolIsFinalized() external whenCallerIsController { + function test_RevertWhen_PoolIsFinalized() external { bPool.set__finalized(true); // it should revert vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector); bPool.bind(tokens[0], tokenBindBalance, tokenWeight); } - function test_RevertWhen_MAX_BOUND_TOKENSTokensAreAlreadyBound() external whenCallerIsController { + function test_RevertWhen_MAX_BOUND_TOKENSTokensAreAlreadyBound() external { _setRandomTokens(MAX_BOUND_TOKENS); // it should revert vm.expectRevert(IBPool.BPool_TokensAboveMaximum.selector); bPool.bind(tokens[0], tokenBindBalance, tokenWeight); } - function test_RevertWhen_TokenWeightIsTooLow() external whenCallerIsController { + function test_RevertWhen_TokenWeightIsTooLow() external { // it should revert vm.expectRevert(IBPool.BPool_WeightBelowMinimum.selector); bPool.bind(tokens[0], tokenBindBalance, MIN_WEIGHT - 1); } - function test_RevertWhen_TokenWeightIsTooHigh() external whenCallerIsController { + function test_RevertWhen_TokenWeightIsTooHigh() external { // it should revert vm.expectRevert(IBPool.BPool_WeightAboveMaximum.selector); bPool.bind(tokens[0], tokenBindBalance, MAX_WEIGHT + 1); } - function test_RevertWhen_TooLittleBalanceIsProvided() external whenCallerIsController { + function test_RevertWhen_TooLittleBalanceIsProvided() external { // it should revert vm.expectRevert(IBPool.BPool_BalanceBelowMinimum.selector); bPool.bind(tokens[0], MIN_BALANCE - 1, tokenWeight); } - function test_RevertWhen_WeightSumExceedsMAX_TOTAL_WEIGHT() external whenCallerIsController { + function test_RevertWhen_WeightSumExceedsMAX_TOTAL_WEIGHT() external { bPool.set__totalWeight(2 * MAX_TOTAL_WEIGHT / 3); // it should revert vm.expectRevert(IBPool.BPool_TotalWeightAboveMaximum.selector); bPool.bind(tokens[0], tokenBindBalance, MAX_TOTAL_WEIGHT / 2); } - function test_WhenTokenCanBeBound(uint256 _existingTokens) external whenCallerIsController { + function test_WhenTokenCanBeBound(uint256 _existingTokens) external { _existingTokens = bound(_existingTokens, 0, MAX_BOUND_TOKENS - 1); bPool.set__tokens(_getDeterministicTokenArray(_existingTokens)); bPool.set__totalWeight(totalWeight); // it calls _pullUnderlying - bPool.expectCall__pullUnderlying(tokens[0], deployer, tokenBindBalance); + bPool.expectCall__pullUnderlying(tokens[0], address(this), tokenBindBalance); // it sets the reentrancy lock bPool.expectCall__setLock(_MUTEX_TAKEN); // it emits LOG_CALL event vm.expectEmit(); bytes memory _data = abi.encodeWithSelector(IBPool.bind.selector, tokens[0], tokenBindBalance, tokenWeight); - emit IBPool.LOG_CALL(IBPool.bind.selector, deployer, _data); + emit IBPool.LOG_CALL(IBPool.bind.selector, address(this), _data); bPool.bind(tokens[0], tokenBindBalance, tokenWeight); diff --git a/test/unit/BPool/BPool_Bind.tree b/test/unit/BPool/BPool_Bind.tree index f0ab3278..a7157745 100644 --- a/test/unit/BPool/BPool_Bind.tree +++ b/test/unit/BPool/BPool_Bind.tree @@ -3,26 +3,25 @@ BPool::Bind │ └── it should revert ├── when caller is NOT controller │ └── it should revert -└── when caller is controller - ├── when token is already bound - │ └── it should revert - ├── when pool is finalized - │ └── it should revert - ├── when MAX_BOUND_TOKENS tokens are already bound - │ └── it should revert - ├── when token weight is too low - │ └── it should revert - ├── when token weight is too high - │ └── it should revert - ├── when too little balance is provided - │ └── it should revert - ├── when weight sum exceeds MAX_TOTAL_WEIGHT - │ └── it should revert - └── when token can be bound - ├── it sets the reentrancy lock - ├── it emits LOG_CALL event - ├── it increments _totalWeight - ├── it calls _pullUnderlying - ├── it adds token to the tokens array - ├── it sets the token record - └── it clears the reentrancy lock +├── when token is already bound +│ └── it should revert +├── when pool is finalized +│ └── it should revert +├── when MAX_BOUND_TOKENS tokens are already bound +│ └── it should revert +├── when token weight is too low +│ └── it should revert +├── when token weight is too high +│ └── it should revert +├── when too little balance is provided +│ └── it should revert +├── when weight sum exceeds MAX_TOTAL_WEIGHT +│ └── it should revert +└── when token can be bound + ├── it sets the reentrancy lock + ├── it emits LOG_CALL event + ├── it increments _totalWeight + ├── it calls _pullUnderlying + ├── it adds token to the tokens array + ├── it sets the token record + └── it clears the reentrancy lock diff --git a/test/unit/BPool/BPool_ExitPool.t.sol b/test/unit/BPool/BPool_ExitPool.t.sol index 858cbef1..11259ba6 100644 --- a/test/unit/BPool/BPool_ExitPool.t.sol +++ b/test/unit/BPool/BPool_ExitPool.t.sol @@ -92,7 +92,7 @@ contract BPoolExitPool is BPoolBase, BNum { // it pulls poolAmountIn shares bPool.expectCall__pullPoolShare(address(this), poolAmountIn); // it sends exitFee to factory - bPool.expectCall__pushPoolShare(deployer, exitFee); + bPool.expectCall__pushPoolShare(address(this), exitFee); // it burns poolAmountIn - exitFee shares bPool.expectCall__burnPoolShare(poolAmountIn - exitFee); // it calls _pushUnderlying for every token diff --git a/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol b/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol index 8032cdf7..c9ecdff2 100644 --- a/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol +++ b/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol @@ -32,7 +32,7 @@ contract BPoolExitSwapPoolAmountIn is BPoolBase, BMath { bPool.mock_call__pullPoolShare(address(this), poolAmountIn); bPool.mock_call__burnPoolShare(poolAmountIn); - bPool.mock_call__pushPoolShare(deployer, 0); + bPool.mock_call__pushPoolShare(address(this), 0); bPool.mock_call__pushUnderlying(tokenOut, address(this), expectedAmountOut); } @@ -90,7 +90,7 @@ contract BPoolExitSwapPoolAmountIn is BPoolBase, BMath { // it burns poolAmountIn - exitFee shares bPool.expectCall__burnPoolShare(poolAmountIn); // it sends exitFee to factory - bPool.expectCall__pushPoolShare(deployer, 0); + bPool.expectCall__pushPoolShare(address(this), 0); // it calls _pushUnderlying for token out bPool.expectCall__pushUnderlying(tokenOut, address(this), expectedAmountOut); // it emits LOG_CALL event diff --git a/test/unit/BPool/BPool_Unbind.t.sol b/test/unit/BPool/BPool_Unbind.t.sol index c0c0f008..7034e85c 100644 --- a/test/unit/BPool/BPool_Unbind.t.sol +++ b/test/unit/BPool/BPool_Unbind.t.sol @@ -26,24 +26,19 @@ contract BPoolUnbind is BPoolBase { function test_RevertWhen_CallerIsNOTController(address _caller) external { // it should revert - vm.assume(_caller != deployer); + vm.assume(_caller != address(this)); vm.prank(_caller); vm.expectRevert(IBPool.BPool_CallerIsNotController.selector); bPool.unbind(tokens[0]); } - modifier whenCallerIsController() { - vm.startPrank(deployer); - _; - } - - function test_RevertWhen_TokenIsNotBound() external whenCallerIsController { + function test_RevertWhen_TokenIsNotBound() external { vm.expectRevert(IBPool.BPool_TokenNotBound.selector); // it should revert bPool.unbind(tokens[0]); } - function test_RevertWhen_PoolIsFinalized() external whenCallerIsController { + function test_RevertWhen_PoolIsFinalized() external { bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0})); bPool.set__finalized(true); // it should revert @@ -60,16 +55,16 @@ contract BPoolUnbind is BPoolBase { _; } - function test_WhenTokenIsLastOnTheTokensArray() external whenCallerIsController whenTokenCanBeUnbound { + function test_WhenTokenIsLastOnTheTokensArray() external whenTokenCanBeUnbound { // it sets the reentrancy lock bPool.expectCall__setLock(_MUTEX_TAKEN); // it calls _pushUnderlying - bPool.expectCall__pushUnderlying(tokens[0], deployer, boundTokenAmount); + bPool.expectCall__pushUnderlying(tokens[0], address(this), boundTokenAmount); // it emits LOG_CALL event vm.expectEmit(); bytes memory _data = abi.encodeWithSelector(IBPool.unbind.selector, tokens[0]); - emit IBPool.LOG_CALL(IBPool.unbind.selector, deployer, _data); + emit IBPool.LOG_CALL(IBPool.unbind.selector, address(this), _data); bPool.unbind(tokens[0]); // it clears the reentrancy lock @@ -82,7 +77,7 @@ contract BPoolUnbind is BPoolBase { assertEq(bPool.call__totalWeight(), totalWeight - tokenWeight); } - function test_WhenTokenIsNOTLastOnTheTokensArray() external whenCallerIsController whenTokenCanBeUnbound { + function test_WhenTokenIsNOTLastOnTheTokensArray() external whenTokenCanBeUnbound { bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 0, denorm: tokenWeight})); bPool.set__tokens(_tokensToMemory()); bPool.unbind(tokens[0]); diff --git a/test/unit/BPool/BPool_Unbind.tree b/test/unit/BPool/BPool_Unbind.tree index 0f8fd8e7..12ac3f5b 100644 --- a/test/unit/BPool/BPool_Unbind.tree +++ b/test/unit/BPool/BPool_Unbind.tree @@ -3,22 +3,21 @@ BPool::Unbind │ └── it should revert ├── when caller is NOT controller │ └── it should revert -└── when caller is controller - ├── when token is not bound - │ └── it should revert - ├── when pool is finalized - │ └── it should revert - └── when token can be unbound - ├── when token is last on the tokens array - │ ├── it sets the reentrancy lock - │ ├── it emits LOG_CALL event - │ ├── it calls _pushUnderlying - │ ├── it removes the token record - │ ├── it decreases the total weight - │ ├── it pops from the array - │ └── it clears the reentrancy lock - └── when token is NOT last on the tokens array - ├── it removes the token record - ├── it removes the token from the array - ├── it keeps other tokens in the array - └── it updates records to point to the correct indices +├── when token is not bound +│ └── it should revert +├── when pool is finalized +│ └── it should revert +└── when token can be unbound + ├── when token is last on the tokens array + │ ├── it sets the reentrancy lock + │ ├── it emits LOG_CALL event + │ ├── it calls _pushUnderlying + │ ├── it removes the token record + │ ├── it decreases the total weight + │ ├── it pops from the array + │ └── it clears the reentrancy lock + └── when token is NOT last on the tokens array + ├── it removes the token record + ├── it removes the token from the array + ├── it keeps other tokens in the array + └── it updates records to point to the correct indices