diff --git a/.forge-snapshots/exitPool.snap b/.forge-snapshots/exitPool.snap index a39bedae..c25e7c84 100644 --- a/.forge-snapshots/exitPool.snap +++ b/.forge-snapshots/exitPool.snap @@ -1 +1 @@ -174743 \ No newline at end of file +174776 \ No newline at end of file diff --git a/.forge-snapshots/joinPool.snap b/.forge-snapshots/joinPool.snap index f586e9ee..404cfcf1 100644 --- a/.forge-snapshots/joinPool.snap +++ b/.forge-snapshots/joinPool.snap @@ -1 +1 @@ -138985 \ No newline at end of file +138956 \ No newline at end of file diff --git a/.forge-snapshots/newBCoWFactory.snap b/.forge-snapshots/newBCoWFactory.snap index 2be4d980..4a7de3c7 100644 --- a/.forge-snapshots/newBCoWFactory.snap +++ b/.forge-snapshots/newBCoWFactory.snap @@ -1 +1 @@ -4899289 \ No newline at end of file +4370972 \ No newline at end of file diff --git a/.forge-snapshots/newBCoWPool.snap b/.forge-snapshots/newBCoWPool.snap index 02c4c1d9..a1594cc8 100644 --- a/.forge-snapshots/newBCoWPool.snap +++ b/.forge-snapshots/newBCoWPool.snap @@ -1 +1 @@ -4042925 \ No newline at end of file +3555439 \ No newline at end of file diff --git a/.forge-snapshots/newBFactory.snap b/.forge-snapshots/newBFactory.snap index 2eed98b7..1cb0a955 100644 --- a/.forge-snapshots/newBFactory.snap +++ b/.forge-snapshots/newBFactory.snap @@ -1 +1 @@ -4140477 \ No newline at end of file +3612809 \ No newline at end of file diff --git a/.forge-snapshots/newBPool.snap b/.forge-snapshots/newBPool.snap index e3eb6576..622e3a4a 100644 --- a/.forge-snapshots/newBPool.snap +++ b/.forge-snapshots/newBPool.snap @@ -1 +1 @@ -3486610 \ No newline at end of file +3000067 \ No newline at end of file diff --git a/.forge-snapshots/settlementCoWSwap.snap b/.forge-snapshots/settlementCoWSwap.snap index 0e09cffc..7742ce52 100644 --- a/.forge-snapshots/settlementCoWSwap.snap +++ b/.forge-snapshots/settlementCoWSwap.snap @@ -1 +1 @@ -215793 \ No newline at end of file +215771 \ No newline at end of file diff --git a/.forge-snapshots/settlementCoWSwapInverse.snap b/.forge-snapshots/settlementCoWSwapInverse.snap index a7aae089..f28a2b44 100644 --- a/.forge-snapshots/settlementCoWSwapInverse.snap +++ b/.forge-snapshots/settlementCoWSwapInverse.snap @@ -1 +1 @@ -225641 \ No newline at end of file +225619 \ No newline at end of file diff --git a/.forge-snapshots/swapExactAmountIn.snap b/.forge-snapshots/swapExactAmountIn.snap index ec541304..03c23e0c 100644 --- a/.forge-snapshots/swapExactAmountIn.snap +++ b/.forge-snapshots/swapExactAmountIn.snap @@ -1 +1 @@ -104920 \ No newline at end of file +104942 \ No newline at end of file diff --git a/.forge-snapshots/swapExactAmountInInverse.snap b/.forge-snapshots/swapExactAmountInInverse.snap index 004cff8a..b2bbc1af 100644 --- a/.forge-snapshots/swapExactAmountInInverse.snap +++ b/.forge-snapshots/swapExactAmountInInverse.snap @@ -1 +1 @@ -114589 \ No newline at end of file +114765 \ No newline at end of file diff --git a/src/contracts/BPool.sol b/src/contracts/BPool.sol index 7db5cc73..3c3b8bc4 100644 --- a/src/contracts/BPool.sol +++ b/src/contracts/BPool.sol @@ -339,136 +339,6 @@ contract BPool is BToken, BMath, IBPool { _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); } - /// @inheritdoc IBPool - function joinswapExternAmountIn( - address tokenIn, - uint256 tokenAmountIn, - uint256 minPoolAmountOut - ) external _logs_ _lock_ _finalized_ returns (uint256 poolAmountOut) { - if (!_records[tokenIn].bound) { - revert BPool_TokenNotBound(); - } - - Record storage inRecord = _records[tokenIn]; - uint256 tokenInBalance = IERC20(tokenIn).balanceOf(address(this)); - if (tokenAmountIn > bmul(tokenInBalance, MAX_IN_RATIO)) { - revert BPool_TokenAmountInAboveMaxRatio(); - } - - poolAmountOut = - calcPoolOutGivenSingleIn(tokenInBalance, inRecord.denorm, totalSupply(), _totalWeight, tokenAmountIn, _swapFee); - if (poolAmountOut < minPoolAmountOut) { - revert BPool_PoolAmountOutBelowMinPoolAmountOut(); - } - - emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); - - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - } - - /// @inheritdoc IBPool - function joinswapPoolAmountOut( - address tokenIn, - uint256 poolAmountOut, - uint256 maxAmountIn - ) external _logs_ _lock_ _finalized_ returns (uint256 tokenAmountIn) { - if (!_records[tokenIn].bound) { - revert BPool_TokenNotBound(); - } - - Record storage inRecord = _records[tokenIn]; - uint256 tokenInBalance = IERC20(tokenIn).balanceOf(address(this)); - - tokenAmountIn = - calcSingleInGivenPoolOut(tokenInBalance, inRecord.denorm, totalSupply(), _totalWeight, poolAmountOut, _swapFee); - - if (tokenAmountIn == 0) { - revert BPool_InvalidTokenAmountIn(); - } - if (tokenAmountIn > maxAmountIn) { - revert BPool_TokenAmountInAboveMaxAmountIn(); - } - if (tokenAmountIn > bmul(tokenInBalance, MAX_IN_RATIO)) { - revert BPool_TokenAmountInAboveMaxRatio(); - } - - emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); - - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - } - - /// @inheritdoc IBPool - function exitswapPoolAmountIn( - address tokenOut, - uint256 poolAmountIn, - uint256 minAmountOut - ) external _logs_ _lock_ _finalized_ returns (uint256 tokenAmountOut) { - if (!_records[tokenOut].bound) { - revert BPool_TokenNotBound(); - } - - Record storage outRecord = _records[tokenOut]; - uint256 tokenOutBalance = IERC20(tokenOut).balanceOf(address(this)); - - tokenAmountOut = - calcSingleOutGivenPoolIn(tokenOutBalance, outRecord.denorm, totalSupply(), _totalWeight, poolAmountIn, _swapFee); - - if (tokenAmountOut < minAmountOut) { - revert BPool_TokenAmountOutBelowMinAmountOut(); - } - if (tokenAmountOut > bmul(tokenOutBalance, MAX_OUT_RATIO)) { - revert BPool_TokenAmountOutAboveMaxOut(); - } - - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - - emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); - - _pullPoolShare(msg.sender, poolAmountIn); - _burnPoolShare(bsub(poolAmountIn, exitFee)); - _pushPoolShare(FACTORY, exitFee); - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - } - - /// @inheritdoc IBPool - function exitswapExternAmountOut( - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPoolAmountIn - ) external _logs_ _lock_ _finalized_ returns (uint256 poolAmountIn) { - if (!_records[tokenOut].bound) { - revert BPool_TokenNotBound(); - } - - Record storage outRecord = _records[tokenOut]; - uint256 tokenOutBalance = IERC20(tokenOut).balanceOf(address(this)); - if (tokenAmountOut > bmul(tokenOutBalance, MAX_OUT_RATIO)) { - revert BPool_TokenAmountOutAboveMaxOut(); - } - - poolAmountIn = - calcPoolInGivenSingleOut(tokenOutBalance, outRecord.denorm, totalSupply(), _totalWeight, tokenAmountOut, _swapFee); - if (poolAmountIn == 0) { - revert BPool_InvalidPoolAmountIn(); - } - if (poolAmountIn > maxPoolAmountIn) { - revert BPool_PoolAmountInAboveMaxPoolAmountIn(); - } - - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - - emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); - - _pullPoolShare(msg.sender, poolAmountIn); - _burnPoolShare(bsub(poolAmountIn, exitFee)); - _pushPoolShare(FACTORY, exitFee); - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - } - /// @inheritdoc IBPool function getSpotPrice(address tokenIn, address tokenOut) external view _viewlock_ returns (uint256) { if (!_records[tokenIn].bound) { diff --git a/src/interfaces/IBPool.sol b/src/interfaces/IBPool.sol index 9b18cfa4..99efa01b 100644 --- a/src/interfaces/IBPool.sol +++ b/src/interfaces/IBPool.sol @@ -282,58 +282,6 @@ interface IBPool is IERC20 { uint256 maxPrice ) external returns (uint256 tokenAmountIn, uint256 spotPriceAfter); - /** - * @notice Joins a pool providing a single token in, specifying the exact amount of token given - * @param tokenIn The address of the token to swap in and join - * @param tokenAmountIn The amount of token to join - * @param minPoolAmountOut The minimum amount of pool token to receive - * @return poolAmountOut The amount of pool token received - */ - function joinswapExternAmountIn( - address tokenIn, - uint256 tokenAmountIn, - uint256 minPoolAmountOut - ) external returns (uint256 poolAmountOut); - - /** - * @notice Joins a pool providing a single token in, specifying the exact amount of pool tokens received - * @param tokenIn The address of the token to swap in and join - * @param poolAmountOut The amount of pool token to receive - * @param maxAmountIn The maximum amount of token to introduce to the pool - * @return tokenAmountIn The amount of token in introduced - */ - function joinswapPoolAmountOut( - address tokenIn, - uint256 poolAmountOut, - uint256 maxAmountIn - ) external returns (uint256 tokenAmountIn); - - /** - * @notice Exits a pool providing a specific amount of pool tokens in, and receiving only a single token - * @param tokenOut The address of the token to swap out and exit - * @param poolAmountIn The amount of pool token to burn - * @param minAmountOut The minimum amount of token to receive - * @return tokenAmountOut The amount of token received - */ - function exitswapPoolAmountIn( - address tokenOut, - uint256 poolAmountIn, - uint256 minAmountOut - ) external returns (uint256 tokenAmountOut); - - /** - * @notice Exits a pool expecting a specific amount of token out, and providing pool token - * @param tokenOut The address of the token to swap out and exit - * @param tokenAmountOut The amount of token to receive - * @param maxPoolAmountIn The maximum amount of pool token to burn - * @return poolAmountIn The amount of pool token burned - */ - function exitswapExternAmountOut( - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPoolAmountIn - ) external returns (uint256 poolAmountIn); - /** * @notice Gets the spot price of tokenIn in terms of tokenOut * @param tokenIn The address of the token to swap in diff --git a/test/unit/BPool/BPool_ExitswapExternAmountOut.t.sol b/test/unit/BPool/BPool_ExitswapExternAmountOut.t.sol deleted file mode 100644 index 48f7aeb7..00000000 --- a/test/unit/BPool/BPool_ExitswapExternAmountOut.t.sol +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {BPoolBase} from './BPoolBase.t.sol'; - -import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; - -import {BNum} from 'contracts/BNum.sol'; -import {IBPool} from 'interfaces/IBPool.sol'; - -contract BPoolExitSwapExternAmountOut is BPoolBase, BNum { - // Valid scenario: - address public tokenOut; - uint256 public tokenOutWeight = 5e18; - uint256 public tokenOutBalance = 200e18; - uint256 public totalWeight = 10e18; - uint256 public tokenAmountOut = 20e18; - // calcPoolInGivenSingleOut(200,5,100,10,20,0) - uint256 public expectedPoolIn = 5.1316728300798443e18; - uint256 public exitFee = 0; - - function setUp() public virtual override { - super.setUp(); - tokenOut = tokens[1]; - bPool.set__records(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), expectedPoolIn); - bPool.mock_call__burnPoolShare(expectedPoolIn); - bPool.mock_call__pushPoolShare(address(this), exitFee); - bPool.mock_call__pushUnderlying(tokenOut, address(this), tokenAmountOut); - } - - function test_RevertWhen_ReentrancyLockIsSet() external { - bPool.call__setLock(_MUTEX_TAKEN); - // it should revert - vm.expectRevert(IBPool.BPool_Reentrancy.selector); - bPool.exitswapExternAmountOut(tokenOut, tokenAmountOut, expectedPoolIn); - } - - function test_RevertWhen_PoolIsNotFinalized() external { - bPool.set__finalized(false); - // it should revert - vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector); - bPool.exitswapExternAmountOut(tokenOut, tokenAmountOut, expectedPoolIn); - } - - function test_RevertWhen_TokenIsNotBound() external { - // it should revert - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bPool.exitswapExternAmountOut(makeAddr('unknown token'), tokenAmountOut, expectedPoolIn); - } - - function test_RevertWhen_TokenAmountOutExceedsMaxAllowedRatio() external { - // it should revert - vm.expectRevert(IBPool.BPool_TokenAmountOutAboveMaxOut.selector); - // just barely above 1/3rd of tokenOut.balanceOf(bPool) - bPool.exitswapExternAmountOut(tokenOut, bmul(tokenOutBalance, MAX_OUT_RATIO) + 1, expectedPoolIn); - } - - function test_RevertWhen_ComputedPoolAmountInIsZero() external { - // it should revert - vm.expectRevert(abi.encodeWithSelector(IBPool.BPool_InvalidPoolAmountIn.selector)); - bPool.exitswapExternAmountOut(tokenOut, 0, expectedPoolIn); - } - - function test_RevertWhen_ComputedPoolAmountInIsMoreThanMaxPoolAmountIn() external { - // it should revert - vm.expectRevert(abi.encodeWithSelector(IBPool.BPool_PoolAmountInAboveMaxPoolAmountIn.selector)); - bPool.exitswapExternAmountOut(tokenOut, tokenAmountOut, expectedPoolIn - 1); - } - - 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), expectedPoolIn); - // it burns poolAmountIn - exitFee shares - bPool.expectCall__burnPoolShare(expectedPoolIn); - // it sends exitFee to factory - bPool.expectCall__pushPoolShare(address(this), exitFee); - // it calls _pushUnderlying for token out - bPool.expectCall__pushUnderlying(tokenOut, address(this), tokenAmountOut); - // it emits LOG_CALL event - bytes memory _data = abi.encodeCall(IBPool.exitswapExternAmountOut, (tokenOut, tokenAmountOut, expectedPoolIn)); - vm.expectEmit(); - emit IBPool.LOG_CALL(IBPool.exitswapExternAmountOut.selector, address(this), _data); - // it emits LOG_EXIT event for token out - emit IBPool.LOG_EXIT(address(this), tokenOut, expectedPoolIn); - // it returns pool amount in - uint256 poolIn = bPool.exitswapExternAmountOut(tokenOut, tokenAmountOut, expectedPoolIn); - assertEq(expectedPoolIn, poolIn); - // it clears the reentrancy lock - assertEq(bPool.call__getLock(), _MUTEX_FREE); - } -} diff --git a/test/unit/BPool/BPool_ExitswapExternAmountOut.tree b/test/unit/BPool/BPool_ExitswapExternAmountOut.tree deleted file mode 100644 index 7511505e..00000000 --- a/test/unit/BPool/BPool_ExitswapExternAmountOut.tree +++ /dev/null @@ -1,24 +0,0 @@ -BPool::ExitSwapExternAmountOut -├── 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 out exceeds max allowed ratio -│ └── it should revert -├── when computed pool amount in is zero -│ └── it should revert -├── when computed pool amount in is more than maxPoolAmountIn -│ └── 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 pool amount in - └── it clears the reentrancy lock diff --git a/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol b/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol deleted file mode 100644 index e856630c..00000000 --- a/test/unit/BPool/BPool_ExitswapPoolAmountIn.t.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {BPoolBase} from './BPoolBase.t.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]; - bPool.set__records(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(address(this), 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(address(this), 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 deleted file mode 100644 index b4e9d64a..00000000 --- a/test/unit/BPool/BPool_ExitswapPoolAmountIn.tree +++ /dev/null @@ -1,24 +0,0 @@ -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 diff --git a/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol b/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol deleted file mode 100644 index 1ac173e2..00000000 --- a/test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {BPoolBase} from './BPoolBase.t.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); - bPool.set__records(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 deleted file mode 100644 index 6e49f3ab..00000000 --- a/test/unit/BPool/BPool_JoinswapExternAmountIn.tree +++ /dev/null @@ -1,20 +0,0 @@ -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_JoinswapPoolAmountOut.t.sol b/test/unit/BPool/BPool_JoinswapPoolAmountOut.t.sol deleted file mode 100644 index 7707fdb0..00000000 --- a/test/unit/BPool/BPool_JoinswapPoolAmountOut.t.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {BPoolBase} from './BPoolBase.t.sol'; -import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; - -import {BNum} from 'contracts/BNum.sol'; -import {IBPool} from 'interfaces/IBPool.sol'; - -contract BPoolJoinswapPoolAmountOut is BPoolBase, BNum { - address public tokenIn; - - // Valid scenario - uint256 public poolAmountOut = 1e18; - uint256 public tokenInWeight = 8e18; - uint256 public totalWeight = 10e18; - uint256 public tokenInBalance = 300e18; - // ((((INIT_POOL_SUPPLY+poolAmountOut)/INIT_POOL_SUPPLY)^(1/(tokenInWeight/totalWeight)))*tokenInBalance-tokenInBalance)/(1-((1-(tokenInWeight/totalWeight))*MIN_FEE)) - // ((((100+1)/100)^(1/(8/10)))*300-300)/(1-((1-(8/10))*(10^-6))) - // 3.754676583174615979425132956656691 - uint256 public maxTokenIn = 3.754676583181324836e18; - - 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); - bPool.set__records(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.joinswapPoolAmountOut(tokenIn, poolAmountOut, maxTokenIn); - } - - function test_RevertWhen_PoolIsNotFinalized() external { - bPool.call__setLock(_MUTEX_TAKEN); - // it should revert - vm.expectRevert(IBPool.BPool_Reentrancy.selector); - bPool.joinswapPoolAmountOut(tokenIn, poolAmountOut, maxTokenIn); - } - - function test_RevertWhen_TokenInIsNotBound() external { - // it should revert - vm.expectRevert(IBPool.BPool_TokenNotBound.selector); - bPool.joinswapPoolAmountOut(makeAddr('unknown token'), poolAmountOut, maxTokenIn); - } - - function test_RevertWhen_TokenAmountInIsZero() external { - // it should revert - vm.expectRevert(IBPool.BPool_InvalidTokenAmountIn.selector); - // using a small amount that rounds to zero - bPool.joinswapPoolAmountOut(tokenIn, 20, type(uint256).max); - } - - function test_RevertWhen_TokenAmountInExceedsMaxRatio() external { - // it should revert - vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector); - // growing pool supply by 50% -> user has to provide over half of the - // pool's tokenIn (198 in this case, consistent with weight=0.8), while - // MAX_IN_RATIO=0.5 - bPool.joinswapPoolAmountOut(tokenIn, 50e18, type(uint256).max); - } - - function test_RevertWhen_CalculatedTokenAmountInIsMoreThanExpected() external { - // it should revert - vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxAmountIn.selector); - bPool.joinswapPoolAmountOut(tokenIn, poolAmountOut, maxTokenIn - 1); - } - - function test_WhenPreconditionsAreMet() external { - // it sets reentrancy lock - bPool.expectCall__setLock(_MUTEX_TAKEN); - // it queries token in balance - vm.expectCall(tokenIn, abi.encodeCall(IERC20.balanceOf, (address(bPool)))); - // it calls _pullUnderlying for token in - bPool.mock_call__pullUnderlying(tokenIn, address(this), maxTokenIn); - bPool.expectCall__pullUnderlying(tokenIn, address(this), maxTokenIn); - // it mints the pool shares - bPool.expectCall__mintPoolShare(poolAmountOut); - // it sends pool shares to caller - bPool.expectCall__pushPoolShare(address(this), poolAmountOut); - // it emits LOG_CALL event - bytes memory _data = - abi.encodeWithSelector(IBPool.joinswapPoolAmountOut.selector, tokenIn, poolAmountOut, maxTokenIn); - vm.expectEmit(); - emit IBPool.LOG_CALL(IBPool.joinswapPoolAmountOut.selector, address(this), _data); - // it emits LOG_JOIN event for token in - vm.expectEmit(); - emit IBPool.LOG_JOIN(address(this), tokenIn, maxTokenIn); - bPool.joinswapPoolAmountOut(tokenIn, poolAmountOut, maxTokenIn); - - // it clears the reentrancy lock - assertEq(_MUTEX_FREE, bPool.call__getLock()); - } -} diff --git a/test/unit/BPool/BPool_JoinswapPoolAmountOut.tree b/test/unit/BPool/BPool_JoinswapPoolAmountOut.tree deleted file mode 100644 index b795f4b7..00000000 --- a/test/unit/BPool/BPool_JoinswapPoolAmountOut.tree +++ /dev/null @@ -1,22 +0,0 @@ -BPool::JoinswapPoolAmountOut -├── 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 amount in is zero -│ └── it should revert -├── when token amount in exceeds max ratio -│ └── it should revert -├── when calculated token amount in is more than expected -│ └── it should revert -└── when preconditions are met - ├── it emits LOG_CALL event - ├── it sets the reentrancy lock - ├── it queries token in balance - ├── it emits LOG_JOIN event for token in - ├── it mints the pool shares - ├── it sends pool shares to caller - ├── it calls _pullUnderlying for token in - └── it clears the reentrancy lock