From dfbed2610ff249b033f9b938019e647f969750b2 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 22 Jul 2024 06:35:31 -0300 Subject: [PATCH] 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();