From f1d6d9b29b0ae27efec66665322a261986eff5fb Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Fri, 3 May 2024 16:28:28 -0300 Subject: [PATCH 01/18] test: wip initial test structure --- test/unit/BPool.t.sol | 194 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 test/unit/BPool.t.sol diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol new file mode 100644 index 00000000..0167b687 --- /dev/null +++ b/test/unit/BPool.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {Test} from 'forge-std/Test.sol'; + +abstract contract Base is Test { + function setUp() public {} +} + +contract BPool_Unit_Constructor is Base { + function test_Deploy() public view {} +} + +contract BPool_Unit_IsPublicSwap is Base { + function test_Returns_IsPublicSwap() public view {} +} + +contract BPool_Unit_IsFinalized is Base { + function test_Returns_IsFinalized() public view {} +} + +contract BPool_Unit_IsBound is Base { + function test_Returns_IsBound() public view {} + + function test_Returns_IsNotBound() public view {} +} + +contract BPool_Unit_GetNumTokens is Base { + function test_Returns_NumTokens() public view {} +} + +contract BPool_Unit_GetCurrentTokens is Base { + function test_Returns_CurrentTokens() public view {} + + function test_Revert_Reentrancy() public view {} +} + +contract BPool_Unit_GetFinalTokens is Base { + function test_Returns_FinalTokens() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Revert_NotFinalized() public view {} +} + +contract BPool_Unit_GetDenormalizedWeight is Base { + function test_Returns_DenormalizedWeight() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Revert_NotBound() public view {} +} + +contract BPool_Unit_GetTotalDenormalizedWeight is Base { + function test_Returns_TotalDenormalizedWeight() public view {} + + function test_Revert_Reentrancy() public view {} +} + +contract BPool_Unit_GetNormalizedWeight is Base { + function test_Returns_NormalizedWeight() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Revert_NotBound() public view {} +} + +contract BPool_Unit_GetBalance is Base { + function test_Returns_Balance() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Revert_NotBound() public view {} +} + +contract BPool_Unit_GetSwapFee is Base { + function test_Returns_SwapFee() public view {} + + function test_Revert_Reentrancy() public view {} +} + +contract BPool_Unit_GetController is Base { + function test_Returns_Controller() public view {} + + function test_Revert_Reentrancy() public view {} +} + +contract BPool_Unit_SetSwapFee is Base { + function test_Revert_Finalized() public view {} + + function test_Revert_NotController() public view {} + + function test_Revert_MinFee() public view {} + + function test_Revert_MaxFee() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_SwapFee() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_SetController is Base { + function test_Revert_NotController() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_Controller() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_SetPublicSwap is Base { + function test_Revert_Finalized() public view {} + + function test_Revert_NotController() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_PublicSwap() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_Finalize is Base { + function test_Revert_NotController() public view {} + + function test_Revert_Finalized() public view {} + + function test_Revert_MinTokens() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_Finalize() public view {} + + function test_Set_PublicSwap() public view {} + + function test_Mint_InitPoolSupply() public view {} + + function test_Push_InitPoolSupply() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_Bind is Base { + function test_Revert_NotController() public view {} + + function test_Revert_IsBound() public view {} + + function test_Revert_Finalized() public view {} + + function test_Revert_MaxPoolTokens() public view {} + + function test_Set_Record() public view {} + + function test_Set_TokenArray() public view {} + + function test_Emit_LogCall() public view {} + + function test_Call_Rebind() public view {} +} + +contract BPool_Unit_Rebind is Base { + function test_Revert_NotController() public view {} + + function test_Revert_NotBound() public view {} + + function test_Revert_Finalized() public view {} + + function test_Revert_MinWeight() public view {} + + function test_Revert_MaxWeight() public view {} + + function test_Revert_MinBalance() public view {} + + function test_Set_TotalWeightIfDenormMoreThanOldWeight() public view {} + + function test_Set_TotalWeightIfDenormLessThanOldWeight() public view {} + + function test_Revert_MaxTotalWeight() public view {} + + function test_Set_Denorm() public view {} + + function test_Set_Balance() public view {} + + function test_Pull_IfBalanceMoreThanOldBalance() public view {} + + function test_Push_UnderlyingIfBalanceLessThanOldBalance() public view {} + + function test_Push_FeeIfBalanceLessThanOldBalance() public view {} + + function test_Emit_LogCall() public view {} +} From 20f064b432a2911b7dcae71a0d78c919c3fa2c8f Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Mon, 6 May 2024 10:09:58 -0300 Subject: [PATCH 02/18] test: complete scaffolding --- test/unit/BPool.t.sol | 292 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 0167b687..1c9ecb06 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -174,6 +174,8 @@ contract BPool_Unit_Rebind is Base { function test_Revert_MinBalance() public view {} + function test_Revert_Reentrancy() public view {} + function test_Set_TotalWeightIfDenormMoreThanOldWeight() public view {} function test_Set_TotalWeightIfDenormLessThanOldWeight() public view {} @@ -192,3 +194,293 @@ contract BPool_Unit_Rebind is Base { function test_Emit_LogCall() public view {} } + +contract BPool_Unit_Unbind is Base { + function test_Revert_NotController() public view {} + + function test_Revert_NotBound() public view {} + + function test_Revert_Finalized() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_TotalWeight() public view {} + + function test_Set_TokenArray() public view {} + + function test_Set_Index() public view {} + + function test_Unset_TokenArray() public view {} + + function test_Unset_Record() public view {} + + function test_Push_UnderlyingBalance() public view {} + + function test_Push_UnderlyingFee() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_Gulp is Base { + function test_Revert_NotBound() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_Balance() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_GetSpotPrice is Base { + function test_Revert_NotBoundTokenIn() public view {} + + function test_Revert_NotBoundTokenOut() public view {} + + function test_Returns_SpotPrice() public view {} + + function test_Revert_Reentrancy() public view {} +} + +contract BPool_Unit_GetSpotPriceSansFee is Base { + function test_Revert_NotBoundTokenIn() public view {} + + function test_Revert_NotBoundTokenOut() public view {} + + function test_Returns_SpotPrice() public view {} + + function test_Revert_Reentrancy() public view {} +} + +contract BPool_Unit_JoinPool is Base { + function test_Revert_NotFinalized() public view {} + + function test_Revert_MathApprox() public view {} + + function test_Revert_TokenArrayMathApprox() public view {} + + function test_Revert_TokenArrayLimitIn() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_TokenArrayBalance() public view {} + + function test_Emit_TokenArrayLogJoin() public view {} + + function test_Pull_TokenArrayTokenAmountIn() public view {} + + function test_Mint_PoolShare() public view {} + + function test_Push_PoolShare() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_ExitPool is Base { + function test_Revert_NotFinalized() public view {} + + function test_Revert_MathApprox() public view {} + + function test_Pull_PoolShare() public view {} + + function test_Push_PoolShare() public view {} + + function test_Burn_PoolShare() public view {} + + function test_Revert_TokenArrayMathApprox() public view {} + + function test_Revert_TokenArrayLimitOut() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_TokenArrayBalance() public view {} + + function test_Emit_TokenArrayLogExit() public view {} + + function test_Push_TokenArrayTokenAmountOut() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_SwapExactAmountIn is Base { + function test_Revert_NotBoundTokenIn() public view {} + + function test_Revert_NotBoundTokenOut() public view {} + + function test_Revert_NotPublic() public view {} + + function test_Revert_MaxInRatio() public view {} + + function test_Revert_BadLimitPrice() public view {} + + function test_Revert_LimitOut() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_InRecord() public view {} + + function test_Set_OutRecord() public view {} + + function test_Revert_MathApprox() public view {} + + function test_Revert_LimitPrice() public view {} + + function test_Revert_MathApprox2() public view {} + + function test_Emit_LogSwap() public view {} + + function test_Pull_TokenAmountIn() public view {} + + function test_Push_TokenAmountOut() public view {} + + function test_Returns_AmountAndPrice() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_SwapExactAmountOut is Base { + function test_Revert_NotBoundTokenIn() public view {} + + function test_Revert_NotBoundTokenOut() public view {} + + function test_Revert_NotPublic() public view {} + + function test_Revert_MaxOutRatio() public view {} + + function test_Revert_BadLimitPrice() public view {} + + function test_Revert_LimitIn() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_InRecord() public view {} + + function test_Set_OutRecord() public view {} + + function test_Revert_MathApprox() public view {} + + function test_Revert_LimitPrice() public view {} + + function test_Revert_MathApprox2() public view {} + + function test_Emit_LogSwap() public view {} + + function test_Pull_TokenAmountIn() public view {} + + function test_Push_TokenAmountOut() public view {} + + function test_Returns_AmountAndPrice() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_JoinswapExternAmountIn is Base { + function test_Revert_NotFinalized() public view {} + + function test_Revert_NotBound() public view {} + + function test_Revert_MaxInRatio() public view {} + + function test_Revert_LimitOut() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_Balance() public view {} + + function test_Emit_LogJoin() public view {} + + function test_Mint_PoolShare() public view {} + + function test_Push_PoolShare() public view {} + + function test_Pull_Underlying() public view {} + + function test_Returns_PoolAmountOut() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_JoinswapExternAmountOut is Base { + function test_Revert_NotFinalized() public view {} + + function test_Revert_NotBound() public view {} + + function test_Revert_MaxApprox() public view {} + + function test_Revert_LimitIn() public view {} + + function test_Revert_MaxInRatio() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_Balance() public view {} + + function test_Emit_LogJoin() public view {} + + function test_Mint_PoolShare() public view {} + + function test_Push_PoolShare() public view {} + + function test_Pull_Underlying() public view {} + + function test_Returns_TokenAmountIn() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_ExitswapPoolAmountIn is Base { + function test_Revert_NotFinalized() public view {} + + function test_Revert_NotBound() public view {} + + function test_Revert_LimitOut() public view {} + + function test_Revert_MaxOutRatio() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_Balance() public view {} + + function test_Emit_LogExit() public view {} + + function test_Pull_PoolShare() public view {} + + function test_Burn_PoolShare() public view {} + + function test_Push_PoolShare() public view {} + + function test_Push_Underlying() public view {} + + function test_Returns_TokenAmountOut() public view {} + + function test_Emit_LogCall() public view {} +} + +contract BPool_Unit_ExitswapPoolAmountOut is Base { + function test_Revert_NotFinalized() public view {} + + function test_Revert_NotBound() public view {} + + function test_Revert_MaxOutRatio() public view {} + + function test_Revert_MathApprox() public view {} + + function test_Revert_LimitIn() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_Balance() public view {} + + function test_Emit_LogExit() public view {} + + function test_Pull_PoolShare() public view {} + + function test_Burn_PoolShare() public view {} + + function test_Push_PoolShare() public view {} + + function test_Push_Underlying() public view {} + + function test_Returns_PoolAmountIn() public view {} + + function test_Emit_LogCall() public view {} +} From 84e9c29bca8ef24534bddced1d6c0fb528f39b49 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Mon, 6 May 2024 17:01:48 -0300 Subject: [PATCH 03/18] test: add happy path for joinPool --- package.json | 2 +- test/unit/BPool.t.sol | 81 ++++++++++++++++++++++++++++++++++++++++--- yarn.lock | 6 ++-- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 9111d0e1..c1dc0bba 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "package.json": "sort-package-json" }, "dependencies": { - "solmate": "github:transmissions11/solmate#a9e3ea2" + "solmate": "github:transmissions11/solmate#c892309" }, "devDependencies": { "@commitlint/cli": "19.3.0", diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 1c9ecb06..a741e66b 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -1,10 +1,68 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; -import {Test} from 'forge-std/Test.sol'; - -abstract contract Base is Test { - function setUp() public {} +import {BConst} from 'contracts/BConst.sol'; +import {BPool} from 'contracts/BPool.sol'; +import {IERC20} from 'contracts/BToken.sol'; +import {Test, console} from 'forge-std/Test.sol'; +import {LibString} from 'solmate/utils/LibString.sol'; + +abstract contract Base is Test, BConst { + using LibString for *; + + BPool public bPool; + address[8] public tokens; + + function setUp() public { + bPool = new BPool(); + + // Create fake tokens + for (uint256 i = 0; i < tokens.length; i++) { + tokens[i] = makeAddr(i.toString()); + } + } + + function _preparePool( + BPool _pool, + address[8] memory _tokens, + uint256[8] memory _balance, + uint256[8] memory _denorm + ) internal { + // Create mocks + for (uint256 i = 0; i < _tokens.length; i++) { + vm.mockCall(_tokens[i], abi.encodeWithSelector(IERC20(_tokens[i]).transfer.selector), abi.encode(true)); + vm.mockCall(_tokens[i], abi.encodeWithSelector(IERC20(_tokens[i]).transferFrom.selector), abi.encode(true)); + } + + // Set tokens + for (uint256 i = 0; i < _tokens.length; i++) { + vm.prank(_pool.getController()); + _pool.bind(_tokens[i], _balance[i], _denorm[i]); + } + + // Set public swap + vm.store( + address(_pool), + bytes32(uint256(6)), + bytes32(uint256(0x0000000000000000000000010000000000000000000000000000000000000000)) + ); + // Set finalize + vm.store(address(_pool), bytes32(uint256(8)), bytes32(uint256(1))); + // Set totalSupply + vm.store(address(_pool), bytes32(uint256(2)), bytes32(INIT_POOL_SUPPLY)); + } + + function _assumeHappyPath( + uint256[8] memory _balance, + uint256[8] memory _denorm + ) internal view returns (uint256[8] memory, uint256[8] memory) { + for (uint256 i = 0; i < _balance.length; i++) { + _balance[i] = bound(_balance[i], MIN_BALANCE, MIN_BALANCE * 1000000); // TODO: found a better max + _denorm[i] = bound(_denorm[i], MIN_WEIGHT, MAX_WEIGHT / 8); // Div by the max number of tokens + } + + return (_balance, _denorm); + } } contract BPool_Unit_Constructor is Base { @@ -252,6 +310,19 @@ contract BPool_Unit_GetSpotPriceSansFee is Base { } contract BPool_Unit_JoinPool is Base { + function test_HappyPath(uint256 _poolAmountOut, uint256[8] memory _balance, uint256[8] memory _denorm) public { + vm.assume(_poolAmountOut > INIT_POOL_SUPPLY); + vm.assume(_poolAmountOut < type(uint256).max / BONE); + + uint256[] memory maxAmountsIn = new uint256[](tokens.length); + for (uint256 i = 0; i < tokens.length; i++) { maxAmountsIn[i] = type(uint256).max; } // Using max possible amounts + + (uint256[8] memory __balance, uint256[8] memory __denorm) = _assumeHappyPath(_balance, _denorm); + _preparePool(bPool, tokens, __balance, __denorm); + + bPool.joinPool(_poolAmountOut, maxAmountsIn); + } + function test_Revert_NotFinalized() public view {} function test_Revert_MathApprox() public view {} @@ -463,7 +534,7 @@ contract BPool_Unit_ExitswapPoolAmountOut is Base { function test_Revert_MaxOutRatio() public view {} function test_Revert_MathApprox() public view {} - + function test_Revert_LimitIn() public view {} function test_Revert_Reentrancy() public view {} diff --git a/yarn.lock b/yarn.lock index 14642107..b7c9332e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1713,9 +1713,9 @@ solc@0.8.24: optionalDependencies: prettier "^2.8.3" -"solmate@github:transmissions11/solmate#a9e3ea2": - version "6.0.0" - resolved "https://codeload.github.com/transmissions11/solmate/tar.gz/a9e3ea26a2dc73bfa87f0cb189687d029028e0c5" +"solmate@github:transmissions11/solmate#c892309": + version "6.2.0" + resolved "https://codeload.github.com/transmissions11/solmate/tar.gz/c892309933b25c03d32b1b0d674df7ae292ba925" sort-object-keys@^1.1.3: version "1.1.3" From 9fda7298f23fc69beaf237edc349ffaf268b685c Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Tue, 7 May 2024 16:29:00 -0300 Subject: [PATCH 04/18] test: wip --- foundry.toml | 3 +- test/unit/BPool.t.sol | 133 ++++++++++++++++++++++++++---------------- 2 files changed, 85 insertions(+), 51 deletions(-) diff --git a/foundry.toml b/foundry.toml index 1e9d2ee4..2aed72ce 100644 --- a/foundry.toml +++ b/foundry.toml @@ -25,7 +25,8 @@ out = 'out-via-ir' src = 'src/interfaces/' [fuzz] -runs = 1000 +runs = 500 +max_test_rejects = 1000000 [rpc_endpoints] mainnet = "${MAINNET_RPC}" diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index a741e66b..862bcfc7 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -11,7 +11,7 @@ abstract contract Base is Test, BConst { using LibString for *; BPool public bPool; - address[8] public tokens; + address[2] public tokens; function setUp() public { bPool = new BPool(); @@ -21,48 +21,6 @@ abstract contract Base is Test, BConst { tokens[i] = makeAddr(i.toString()); } } - - function _preparePool( - BPool _pool, - address[8] memory _tokens, - uint256[8] memory _balance, - uint256[8] memory _denorm - ) internal { - // Create mocks - for (uint256 i = 0; i < _tokens.length; i++) { - vm.mockCall(_tokens[i], abi.encodeWithSelector(IERC20(_tokens[i]).transfer.selector), abi.encode(true)); - vm.mockCall(_tokens[i], abi.encodeWithSelector(IERC20(_tokens[i]).transferFrom.selector), abi.encode(true)); - } - - // Set tokens - for (uint256 i = 0; i < _tokens.length; i++) { - vm.prank(_pool.getController()); - _pool.bind(_tokens[i], _balance[i], _denorm[i]); - } - - // Set public swap - vm.store( - address(_pool), - bytes32(uint256(6)), - bytes32(uint256(0x0000000000000000000000010000000000000000000000000000000000000000)) - ); - // Set finalize - vm.store(address(_pool), bytes32(uint256(8)), bytes32(uint256(1))); - // Set totalSupply - vm.store(address(_pool), bytes32(uint256(2)), bytes32(INIT_POOL_SUPPLY)); - } - - function _assumeHappyPath( - uint256[8] memory _balance, - uint256[8] memory _denorm - ) internal view returns (uint256[8] memory, uint256[8] memory) { - for (uint256 i = 0; i < _balance.length; i++) { - _balance[i] = bound(_balance[i], MIN_BALANCE, MIN_BALANCE * 1000000); // TODO: found a better max - _denorm[i] = bound(_denorm[i], MIN_WEIGHT, MAX_WEIGHT / 8); // Div by the max number of tokens - } - - return (_balance, _denorm); - } } contract BPool_Unit_Constructor is Base { @@ -310,17 +268,92 @@ contract BPool_Unit_GetSpotPriceSansFee is Base { } contract BPool_Unit_JoinPool is Base { - function test_HappyPath(uint256 _poolAmountOut, uint256[8] memory _balance, uint256[8] memory _denorm) public { - vm.assume(_poolAmountOut > INIT_POOL_SUPPLY); - vm.assume(_poolAmountOut < type(uint256).max / BONE); + struct FuzzScenario { + uint256 poolAmountOut; + uint256 balance0; + uint256 balance1; + // uint256 balance2; + // uint256 balance3; + // uint256 balance4; + // uint256 balance5; + // uint256 balance6; + // uint256 balance7; + // uint256[3] balance; + } + + function _setValues(FuzzScenario memory _fuzz) internal { + // Create mocks + for (uint256 i = 0; i < tokens.length; i++) { + vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transfer.selector), abi.encode(true)); + vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transferFrom.selector), abi.encode(true)); + } + + // Set tokens + bytes memory _arraySlot = abi.encode(9); + bytes32 _hashArraySlot = keccak256(_arraySlot); + vm.store(address(bPool), bytes32(_arraySlot), bytes32(tokens.length)); // write length + for (uint256 i = 0; i < tokens.length; i++) { + vm.store(address(bPool), bytes32(uint256(_hashArraySlot) + i), bytes32(abi.encode(tokens[i]))); // write token + } + + // Set balances + for (uint256 i = 0; i < tokens.length; i++) { + bytes32 _slot = keccak256(abi.encode(tokens[i], 10)); // mapping is found at slot 10 + vm.store(address(bPool), bytes32(uint256(_slot) + 0), bytes32(abi.encode(69))); // bound + vm.store(address(bPool), bytes32(uint256(_slot) + 3), bytes32(abi.encode(69))); // balance + } + + // Set public swap + vm.store( + address(bPool), + bytes32(uint256(6)), + bytes32(uint256(0x0000000000000000000000010000000000000000000000000000000000000000)) + ); + // Set finalize + vm.store(address(bPool), bytes32(uint256(8)), bytes32(uint256(1))); + // Set totalSupply + vm.store(address(bPool), bytes32(uint256(2)), bytes32(INIT_POOL_SUPPLY)); + } + + function _assumeHappyPath(FuzzScenario memory _fuzz) internal view { + vm.assume(_fuzz.poolAmountOut > INIT_POOL_SUPPLY); + vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); + + // for (uint256 i = 0; i < _fuzz.balance.length; i++) { + // vm.assume(_fuzz.balance[i] > MIN_BALANCE); + // vm.assume(_fuzz.balance[i] < MIN_BALANCE * 1000000); + // } + + vm.assume(_fuzz.balance0 > MIN_BALANCE); + vm.assume(_fuzz.balance0 < MIN_BALANCE * 1000000); // TODO: found a better max + vm.assume(_fuzz.balance1 > MIN_BALANCE); + vm.assume(_fuzz.balance1 < MIN_BALANCE * 1000000); // TODO: found a better max + // vm.assume(_fuzz.balance2 < MIN_BALANCE * 1000000); // TODO: found a better max + // vm.assume(_fuzz.balance2 > MIN_BALANCE); + // vm.assume(_fuzz.balance3 < MIN_BALANCE * 1000000); // TODO: found a better max + // vm.assume(_fuzz.balance3 > MIN_BALANCE); + // vm.assume(_fuzz.balance4 < MIN_BALANCE * 1000000); // TODO: found a better max + // vm.assume(_fuzz.balance4 > MIN_BALANCE); + // vm.assume(_fuzz.balance5 < MIN_BALANCE * 1000000); // TODO: found a better max + // vm.assume(_fuzz.balance5 > MIN_BALANCE); + // vm.assume(_fuzz.balance6 < MIN_BALANCE * 1000000); // TODO: found a better max + // vm.assume(_fuzz.balance6 > MIN_BALANCE); + // vm.assume(_fuzz.balance7 < MIN_BALANCE * 1000000); // TODO: found a better max + // vm.assume(_fuzz.balance7 > MIN_BALANCE); + } + + modifier happyPath(FuzzScenario memory _fuzz) { + _assumeHappyPath(_fuzz); + _setValues(_fuzz); + _; + } + + function test_HappyPath(FuzzScenario memory _fuzz) public happyPath(_fuzz) { uint256[] memory maxAmountsIn = new uint256[](tokens.length); for (uint256 i = 0; i < tokens.length; i++) { maxAmountsIn[i] = type(uint256).max; } // Using max possible amounts - (uint256[8] memory __balance, uint256[8] memory __denorm) = _assumeHappyPath(_balance, _denorm); - _preparePool(bPool, tokens, __balance, __denorm); - - bPool.joinPool(_poolAmountOut, maxAmountsIn); + bPool.joinPool(_fuzz.poolAmountOut, maxAmountsIn); } function test_Revert_NotFinalized() public view {} From a2dad8406a01db2222c35569b9293fae483ab9b5 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Tue, 7 May 2024 16:33:00 -0300 Subject: [PATCH 05/18] test: tweak assume --- foundry.toml | 2 +- test/unit/BPool.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/foundry.toml b/foundry.toml index 2aed72ce..f3a782d6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -25,7 +25,7 @@ out = 'out-via-ir' src = 'src/interfaces/' [fuzz] -runs = 500 +runs = 10000 max_test_rejects = 1000000 [rpc_endpoints] diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 862bcfc7..1b126824 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -326,9 +326,9 @@ contract BPool_Unit_JoinPool is Base { // } vm.assume(_fuzz.balance0 > MIN_BALANCE); - vm.assume(_fuzz.balance0 < MIN_BALANCE * 1000000); // TODO: found a better max + vm.assume(_fuzz.balance0 < type(uint256).max / BONE); // TODO: found a better max vm.assume(_fuzz.balance1 > MIN_BALANCE); - vm.assume(_fuzz.balance1 < MIN_BALANCE * 1000000); // TODO: found a better max + vm.assume(_fuzz.balance1 < type(uint256).max / BONE); // TODO: found a better max // vm.assume(_fuzz.balance2 < MIN_BALANCE * 1000000); // TODO: found a better max // vm.assume(_fuzz.balance2 > MIN_BALANCE); // vm.assume(_fuzz.balance3 < MIN_BALANCE * 1000000); // TODO: found a better max From 79382adc6e6f579a9b01ca892faace8329cbdeef Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Tue, 7 May 2024 20:52:56 -0300 Subject: [PATCH 06/18] test: wip, improve fuzzing --- foundry.toml | 4 ++-- test/unit/BPool.t.sol | 49 ++++++++++++++----------------------------- 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/foundry.toml b/foundry.toml index f3a782d6..71cfb5de 100644 --- a/foundry.toml +++ b/foundry.toml @@ -25,8 +25,8 @@ out = 'out-via-ir' src = 'src/interfaces/' [fuzz] -runs = 10000 -max_test_rejects = 1000000 +runs = 1000 +max_test_rejects = 10000000 [rpc_endpoints] mainnet = "${MAINNET_RPC}" diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 1b126824..b0c6dfff 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -11,7 +11,7 @@ abstract contract Base is Test, BConst { using LibString for *; BPool public bPool; - address[2] public tokens; + address[5] public tokens; function setUp() public { bPool = new BPool(); @@ -271,15 +271,7 @@ contract BPool_Unit_JoinPool is Base { struct FuzzScenario { uint256 poolAmountOut; - uint256 balance0; - uint256 balance1; - // uint256 balance2; - // uint256 balance3; - // uint256 balance4; - // uint256 balance5; - // uint256 balance6; - // uint256 balance7; - // uint256[3] balance; + uint256[5] balance; } function _setValues(FuzzScenario memory _fuzz) internal { @@ -300,8 +292,8 @@ contract BPool_Unit_JoinPool is Base { // Set balances for (uint256 i = 0; i < tokens.length; i++) { bytes32 _slot = keccak256(abi.encode(tokens[i], 10)); // mapping is found at slot 10 - vm.store(address(bPool), bytes32(uint256(_slot) + 0), bytes32(abi.encode(69))); // bound - vm.store(address(bPool), bytes32(uint256(_slot) + 3), bytes32(abi.encode(69))); // balance + vm.store(address(bPool), bytes32(uint256(_slot) + 0), bytes32(abi.encode(1))); // bound + vm.store(address(bPool), bytes32(uint256(_slot) + 3), bytes32(abi.encode(_fuzz.balance[i]))); // balance } // Set public swap @@ -320,27 +312,18 @@ contract BPool_Unit_JoinPool is Base { vm.assume(_fuzz.poolAmountOut > INIT_POOL_SUPPLY); vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); - // for (uint256 i = 0; i < _fuzz.balance.length; i++) { - // vm.assume(_fuzz.balance[i] > MIN_BALANCE); - // vm.assume(_fuzz.balance[i] < MIN_BALANCE * 1000000); - // } - - vm.assume(_fuzz.balance0 > MIN_BALANCE); - vm.assume(_fuzz.balance0 < type(uint256).max / BONE); // TODO: found a better max - vm.assume(_fuzz.balance1 > MIN_BALANCE); - vm.assume(_fuzz.balance1 < type(uint256).max / BONE); // TODO: found a better max - // vm.assume(_fuzz.balance2 < MIN_BALANCE * 1000000); // TODO: found a better max - // vm.assume(_fuzz.balance2 > MIN_BALANCE); - // vm.assume(_fuzz.balance3 < MIN_BALANCE * 1000000); // TODO: found a better max - // vm.assume(_fuzz.balance3 > MIN_BALANCE); - // vm.assume(_fuzz.balance4 < MIN_BALANCE * 1000000); // TODO: found a better max - // vm.assume(_fuzz.balance4 > MIN_BALANCE); - // vm.assume(_fuzz.balance5 < MIN_BALANCE * 1000000); // TODO: found a better max - // vm.assume(_fuzz.balance5 > MIN_BALANCE); - // vm.assume(_fuzz.balance6 < MIN_BALANCE * 1000000); // TODO: found a better max - // vm.assume(_fuzz.balance6 > MIN_BALANCE); - // vm.assume(_fuzz.balance7 < MIN_BALANCE * 1000000); // TODO: found a better max - // vm.assume(_fuzz.balance7 > MIN_BALANCE); + uint _ratio = _fuzz.poolAmountOut / INIT_POOL_SUPPLY; + + for (uint256 i = 0; i < _fuzz.balance.length; i++) { + vm.assume(_fuzz.balance[i] > MIN_BALANCE); + + uint _maxTokenAmountIn = type(uint256).max / _ratio; + vm.assume(_fuzz.balance[i] < _maxTokenAmountIn); // L272 + + uint _tokenAmountIn = _ratio * _fuzz.balance[i]; + // vm.assume(_tokenAmountIn < _maxTokenAmountIn); + vm.assume(_fuzz.balance[i] < type(uint256).max - _maxTokenAmountIn); + } } modifier happyPath(FuzzScenario memory _fuzz) { From 7ddb7005ea792357c4736c55a7ad4d85a98655b7 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 09:19:13 -0300 Subject: [PATCH 07/18] test: improve assume --- test/unit/BPool.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index b0c6dfff..daba306a 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -321,8 +321,8 @@ contract BPool_Unit_JoinPool is Base { vm.assume(_fuzz.balance[i] < _maxTokenAmountIn); // L272 uint _tokenAmountIn = _ratio * _fuzz.balance[i]; - // vm.assume(_tokenAmountIn < _maxTokenAmountIn); - vm.assume(_fuzz.balance[i] < type(uint256).max - _maxTokenAmountIn); + vm.assume(_tokenAmountIn < type(uint256).max - _fuzz.balance[i]); + vm.assume(_fuzz.balance[i] < type(uint256).max - _tokenAmountIn); } } From ea089f2eb3759912a87684c9abc590e0548f3ee7 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 11:35:29 -0300 Subject: [PATCH 08/18] test: assumes are working now --- test/unit/BPool.t.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index daba306a..27a49048 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -312,17 +312,15 @@ contract BPool_Unit_JoinPool is Base { vm.assume(_fuzz.poolAmountOut > INIT_POOL_SUPPLY); vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); - uint _ratio = _fuzz.poolAmountOut / INIT_POOL_SUPPLY; + uint _poolAmountOutTimesBONE = _fuzz.poolAmountOut * BONE; // bdiv uses '* BONE' + + uint _ratio = _poolAmountOutTimesBONE / INIT_POOL_SUPPLY; for (uint256 i = 0; i < _fuzz.balance.length; i++) { vm.assume(_fuzz.balance[i] > MIN_BALANCE); uint _maxTokenAmountIn = type(uint256).max / _ratio; vm.assume(_fuzz.balance[i] < _maxTokenAmountIn); // L272 - - uint _tokenAmountIn = _ratio * _fuzz.balance[i]; - vm.assume(_tokenAmountIn < type(uint256).max - _fuzz.balance[i]); - vm.assume(_fuzz.balance[i] < type(uint256).max - _tokenAmountIn); } } From bfbb34be21de549b6de8306686af5d0ac9f59c81 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 11:41:32 -0300 Subject: [PATCH 09/18] test: increase tokens to 8 --- test/unit/BPool.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 27a49048..663fdcc3 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -11,7 +11,7 @@ abstract contract Base is Test, BConst { using LibString for *; BPool public bPool; - address[5] public tokens; + address[8] public tokens; function setUp() public { bPool = new BPool(); @@ -271,7 +271,7 @@ contract BPool_Unit_JoinPool is Base { struct FuzzScenario { uint256 poolAmountOut; - uint256[5] balance; + uint256[8] balance; } function _setValues(FuzzScenario memory _fuzz) internal { From 8d467ddb4a1d1175013e3dc218dceba9ec03f83f Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 13:19:43 -0300 Subject: [PATCH 10/18] test: finish happyPath for joinPool --- test/unit/BPool.t.sol | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 663fdcc3..cd50b0df 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -10,8 +10,10 @@ import {LibString} from 'solmate/utils/LibString.sol'; abstract contract Base is Test, BConst { using LibString for *; + uint256 public constant TOKENS_AMOUNT = 5; + BPool public bPool; - address[8] public tokens; + address[TOKENS_AMOUNT] public tokens; function setUp() public { bPool = new BPool(); @@ -268,10 +270,10 @@ contract BPool_Unit_GetSpotPriceSansFee is Base { } contract BPool_Unit_JoinPool is Base { - struct FuzzScenario { uint256 poolAmountOut; - uint256[8] balance; + uint256 initPoolSupply; + uint256[TOKENS_AMOUNT] balance; } function _setValues(FuzzScenario memory _fuzz) internal { @@ -305,22 +307,21 @@ contract BPool_Unit_JoinPool is Base { // Set finalize vm.store(address(bPool), bytes32(uint256(8)), bytes32(uint256(1))); // Set totalSupply - vm.store(address(bPool), bytes32(uint256(2)), bytes32(INIT_POOL_SUPPLY)); + vm.store(address(bPool), bytes32(uint256(2)), bytes32(_fuzz.initPoolSupply)); } function _assumeHappyPath(FuzzScenario memory _fuzz) internal view { - vm.assume(_fuzz.poolAmountOut > INIT_POOL_SUPPLY); + vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY); + vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply); vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); - uint _poolAmountOutTimesBONE = _fuzz.poolAmountOut * BONE; // bdiv uses '* BONE' - - uint _ratio = _poolAmountOutTimesBONE / INIT_POOL_SUPPLY; + uint256 _poolAmountOutTimesBONE = _fuzz.poolAmountOut * BONE; // bdiv uses '* BONE' + uint256 _ratio = _poolAmountOutTimesBONE / _fuzz.initPoolSupply; + uint _maxTokenAmountIn = type(uint256).max / _ratio; for (uint256 i = 0; i < _fuzz.balance.length; i++) { - vm.assume(_fuzz.balance[i] > MIN_BALANCE); - - uint _maxTokenAmountIn = type(uint256).max / _ratio; - vm.assume(_fuzz.balance[i] < _maxTokenAmountIn); // L272 + vm.assume(_fuzz.balance[i] >= MIN_BALANCE); + vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272 } } @@ -332,7 +333,9 @@ contract BPool_Unit_JoinPool is Base { function test_HappyPath(FuzzScenario memory _fuzz) public happyPath(_fuzz) { uint256[] memory maxAmountsIn = new uint256[](tokens.length); - for (uint256 i = 0; i < tokens.length; i++) { maxAmountsIn[i] = type(uint256).max; } // Using max possible amounts + for (uint256 i = 0; i < tokens.length; i++) { + maxAmountsIn[i] = type(uint256).max; + } // Using max possible amounts bPool.joinPool(_fuzz.poolAmountOut, maxAmountsIn); } From 92cdff712ce88fb3e3f30738d2f57bc53a8889f9 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 13:23:51 -0300 Subject: [PATCH 11/18] fix: lint --- test/unit/BPool.t.sol | 78 +++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index cd50b0df..9e99ed7b 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.23; import {BConst} from 'contracts/BConst.sol'; import {BPool} from 'contracts/BPool.sol'; import {IERC20} from 'contracts/BToken.sol'; -import {Test, console} from 'forge-std/Test.sol'; +import {Test} from 'forge-std/Test.sol'; import {LibString} from 'solmate/utils/LibString.sol'; abstract contract Base is Test, BConst { @@ -276,6 +276,43 @@ contract BPool_Unit_JoinPool is Base { uint256[TOKENS_AMOUNT] balance; } + function test_HappyPath(FuzzScenario memory _fuzz) public happyPath(_fuzz) { + uint256[] memory maxAmountsIn = new uint256[](tokens.length); + for (uint256 i = 0; i < tokens.length; i++) { + maxAmountsIn[i] = type(uint256).max; + } // Using max possible amounts + + bPool.joinPool(_fuzz.poolAmountOut, maxAmountsIn); + } + + function test_Revert_NotFinalized() public view {} + + function test_Revert_MathApprox() public view {} + + function test_Revert_TokenArrayMathApprox() public view {} + + function test_Revert_TokenArrayLimitIn() public view {} + + function test_Revert_Reentrancy() public view {} + + function test_Set_TokenArrayBalance() public view {} + + function test_Emit_TokenArrayLogJoin() public view {} + + function test_Pull_TokenArrayTokenAmountIn() public view {} + + function test_Mint_PoolShare() public view {} + + function test_Push_PoolShare() public view {} + + function test_Emit_LogCall() public view {} + + modifier happyPath(FuzzScenario memory _fuzz) { + _assumeHappyPath(_fuzz); + _setValues(_fuzz); + _; + } + function _setValues(FuzzScenario memory _fuzz) internal { // Create mocks for (uint256 i = 0; i < tokens.length; i++) { @@ -317,50 +354,13 @@ contract BPool_Unit_JoinPool is Base { uint256 _poolAmountOutTimesBONE = _fuzz.poolAmountOut * BONE; // bdiv uses '* BONE' uint256 _ratio = _poolAmountOutTimesBONE / _fuzz.initPoolSupply; - uint _maxTokenAmountIn = type(uint256).max / _ratio; + uint256 _maxTokenAmountIn = type(uint256).max / _ratio; for (uint256 i = 0; i < _fuzz.balance.length; i++) { vm.assume(_fuzz.balance[i] >= MIN_BALANCE); vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272 } } - - modifier happyPath(FuzzScenario memory _fuzz) { - _assumeHappyPath(_fuzz); - _setValues(_fuzz); - _; - } - - function test_HappyPath(FuzzScenario memory _fuzz) public happyPath(_fuzz) { - uint256[] memory maxAmountsIn = new uint256[](tokens.length); - for (uint256 i = 0; i < tokens.length; i++) { - maxAmountsIn[i] = type(uint256).max; - } // Using max possible amounts - - bPool.joinPool(_fuzz.poolAmountOut, maxAmountsIn); - } - - function test_Revert_NotFinalized() public view {} - - function test_Revert_MathApprox() public view {} - - function test_Revert_TokenArrayMathApprox() public view {} - - function test_Revert_TokenArrayLimitIn() public view {} - - function test_Revert_Reentrancy() public view {} - - function test_Set_TokenArrayBalance() public view {} - - function test_Emit_TokenArrayLogJoin() public view {} - - function test_Pull_TokenArrayTokenAmountIn() public view {} - - function test_Mint_PoolShare() public view {} - - function test_Push_PoolShare() public view {} - - function test_Emit_LogCall() public view {} } contract BPool_Unit_ExitPool is Base { From 0929672f07823c6c9b4beb6e407db4eb3bc5a7c3 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 16:40:26 -0300 Subject: [PATCH 12/18] test: improve happyPath --- foundry.toml | 2 +- test/unit/BPool.t.sol | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/foundry.toml b/foundry.toml index 71cfb5de..8599d1c0 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,7 +26,7 @@ src = 'src/interfaces/' [fuzz] runs = 1000 -max_test_rejects = 10000000 +max_test_rejects = 400000 [rpc_endpoints] mainnet = "${MAINNET_RPC}" diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 9e99ed7b..544964c0 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -10,7 +10,7 @@ import {LibString} from 'solmate/utils/LibString.sol'; abstract contract Base is Test, BConst { using LibString for *; - uint256 public constant TOKENS_AMOUNT = 5; + uint256 public constant TOKENS_AMOUNT = 3; BPool public bPool; address[TOKENS_AMOUNT] public tokens; @@ -352,8 +352,7 @@ contract BPool_Unit_JoinPool is Base { vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply); vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); - uint256 _poolAmountOutTimesBONE = _fuzz.poolAmountOut * BONE; // bdiv uses '* BONE' - uint256 _ratio = _poolAmountOutTimesBONE / _fuzz.initPoolSupply; + uint256 _ratio = (_fuzz.poolAmountOut * BONE) / _fuzz.initPoolSupply; // bdiv uses '* BONE' uint256 _maxTokenAmountIn = type(uint256).max / _ratio; for (uint256 i = 0; i < _fuzz.balance.length; i++) { From 08fd10661b4493f223b54725ad9078263ec13d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wei=C3=9Fer=20Hase?= Date: Wed, 8 May 2024 21:41:52 +0200 Subject: [PATCH 13/18] fix: avoid empty tests from running --- test/unit/BPool.t.sol | 416 +++++++++++++++++++++--------------------- 1 file changed, 208 insertions(+), 208 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 9e99ed7b..c5955121 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -26,247 +26,247 @@ abstract contract Base is Test, BConst { } contract BPool_Unit_Constructor is Base { - function test_Deploy() public view {} + function test_Deploy() private view {} } contract BPool_Unit_IsPublicSwap is Base { - function test_Returns_IsPublicSwap() public view {} + function test_Returns_IsPublicSwap() private view {} } contract BPool_Unit_IsFinalized is Base { - function test_Returns_IsFinalized() public view {} + function test_Returns_IsFinalized() private view {} } contract BPool_Unit_IsBound is Base { - function test_Returns_IsBound() public view {} + function test_Returns_IsBound() private view {} - function test_Returns_IsNotBound() public view {} + function test_Returns_IsNotBound() private view {} } contract BPool_Unit_GetNumTokens is Base { - function test_Returns_NumTokens() public view {} + function test_Returns_NumTokens() private view {} } contract BPool_Unit_GetCurrentTokens is Base { - function test_Returns_CurrentTokens() public view {} + function test_Returns_CurrentTokens() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} } contract BPool_Unit_GetFinalTokens is Base { - function test_Returns_FinalTokens() public view {} + function test_Returns_FinalTokens() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Revert_NotFinalized() public view {} + function test_Revert_NotFinalized() private view {} } contract BPool_Unit_GetDenormalizedWeight is Base { - function test_Returns_DenormalizedWeight() public view {} + function test_Returns_DenormalizedWeight() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} } contract BPool_Unit_GetTotalDenormalizedWeight is Base { - function test_Returns_TotalDenormalizedWeight() public view {} + function test_Returns_TotalDenormalizedWeight() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} } contract BPool_Unit_GetNormalizedWeight is Base { - function test_Returns_NormalizedWeight() public view {} + function test_Returns_NormalizedWeight() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} } contract BPool_Unit_GetBalance is Base { - function test_Returns_Balance() public view {} + function test_Returns_Balance() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} } contract BPool_Unit_GetSwapFee is Base { - function test_Returns_SwapFee() public view {} + function test_Returns_SwapFee() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} } contract BPool_Unit_GetController is Base { - function test_Returns_Controller() public view {} + function test_Returns_Controller() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} } contract BPool_Unit_SetSwapFee is Base { - function test_Revert_Finalized() public view {} + function test_Revert_Finalized() private view {} - function test_Revert_NotController() public view {} + function test_Revert_NotController() private view {} - function test_Revert_MinFee() public view {} + function test_Revert_MinFee() private view {} - function test_Revert_MaxFee() public view {} + function test_Revert_MaxFee() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_SwapFee() public view {} + function test_Set_SwapFee() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_SetController is Base { - function test_Revert_NotController() public view {} + function test_Revert_NotController() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_Controller() public view {} + function test_Set_Controller() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_SetPublicSwap is Base { - function test_Revert_Finalized() public view {} + function test_Revert_Finalized() private view {} - function test_Revert_NotController() public view {} + function test_Revert_NotController() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_PublicSwap() public view {} + function test_Set_PublicSwap() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_Finalize is Base { - function test_Revert_NotController() public view {} + function test_Revert_NotController() private view {} - function test_Revert_Finalized() public view {} + function test_Revert_Finalized() private view {} - function test_Revert_MinTokens() public view {} + function test_Revert_MinTokens() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_Finalize() public view {} + function test_Set_Finalize() private view {} - function test_Set_PublicSwap() public view {} + function test_Set_PublicSwap() private view {} - function test_Mint_InitPoolSupply() public view {} + function test_Mint_InitPoolSupply() private view {} - function test_Push_InitPoolSupply() public view {} + function test_Push_InitPoolSupply() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_Bind is Base { - function test_Revert_NotController() public view {} + function test_Revert_NotController() private view {} - function test_Revert_IsBound() public view {} + function test_Revert_IsBound() private view {} - function test_Revert_Finalized() public view {} + function test_Revert_Finalized() private view {} - function test_Revert_MaxPoolTokens() public view {} + function test_Revert_MaxPoolTokens() private view {} - function test_Set_Record() public view {} + function test_Set_Record() private view {} - function test_Set_TokenArray() public view {} + function test_Set_TokenArray() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} - function test_Call_Rebind() public view {} + function test_Call_Rebind() private view {} } contract BPool_Unit_Rebind is Base { - function test_Revert_NotController() public view {} + function test_Revert_NotController() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} - function test_Revert_Finalized() public view {} + function test_Revert_Finalized() private view {} - function test_Revert_MinWeight() public view {} + function test_Revert_MinWeight() private view {} - function test_Revert_MaxWeight() public view {} + function test_Revert_MaxWeight() private view {} - function test_Revert_MinBalance() public view {} + function test_Revert_MinBalance() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_TotalWeightIfDenormMoreThanOldWeight() public view {} + function test_Set_TotalWeightIfDenormMoreThanOldWeight() private view {} - function test_Set_TotalWeightIfDenormLessThanOldWeight() public view {} + function test_Set_TotalWeightIfDenormLessThanOldWeight() private view {} - function test_Revert_MaxTotalWeight() public view {} + function test_Revert_MaxTotalWeight() private view {} - function test_Set_Denorm() public view {} + function test_Set_Denorm() private view {} - function test_Set_Balance() public view {} + function test_Set_Balance() private view {} - function test_Pull_IfBalanceMoreThanOldBalance() public view {} + function test_Pull_IfBalanceMoreThanOldBalance() private view {} - function test_Push_UnderlyingIfBalanceLessThanOldBalance() public view {} + function test_Push_UnderlyingIfBalanceLessThanOldBalance() private view {} - function test_Push_FeeIfBalanceLessThanOldBalance() public view {} + function test_Push_FeeIfBalanceLessThanOldBalance() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_Unbind is Base { - function test_Revert_NotController() public view {} + function test_Revert_NotController() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} - function test_Revert_Finalized() public view {} + function test_Revert_Finalized() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_TotalWeight() public view {} + function test_Set_TotalWeight() private view {} - function test_Set_TokenArray() public view {} + function test_Set_TokenArray() private view {} - function test_Set_Index() public view {} + function test_Set_Index() private view {} - function test_Unset_TokenArray() public view {} + function test_Unset_TokenArray() private view {} - function test_Unset_Record() public view {} + function test_Unset_Record() private view {} - function test_Push_UnderlyingBalance() public view {} + function test_Push_UnderlyingBalance() private view {} - function test_Push_UnderlyingFee() public view {} + function test_Push_UnderlyingFee() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_Gulp is Base { - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_Balance() public view {} + function test_Set_Balance() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_GetSpotPrice is Base { - function test_Revert_NotBoundTokenIn() public view {} + function test_Revert_NotBoundTokenIn() private view {} - function test_Revert_NotBoundTokenOut() public view {} + function test_Revert_NotBoundTokenOut() private view {} - function test_Returns_SpotPrice() public view {} + function test_Returns_SpotPrice() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} } contract BPool_Unit_GetSpotPriceSansFee is Base { - function test_Revert_NotBoundTokenIn() public view {} + function test_Revert_NotBoundTokenIn() private view {} - function test_Revert_NotBoundTokenOut() public view {} + function test_Revert_NotBoundTokenOut() private view {} - function test_Returns_SpotPrice() public view {} + function test_Returns_SpotPrice() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} } contract BPool_Unit_JoinPool is Base { @@ -285,27 +285,27 @@ contract BPool_Unit_JoinPool is Base { bPool.joinPool(_fuzz.poolAmountOut, maxAmountsIn); } - function test_Revert_NotFinalized() public view {} + function test_Revert_NotFinalized() private view {} - function test_Revert_MathApprox() public view {} + function test_Revert_MathApprox() private view {} - function test_Revert_TokenArrayMathApprox() public view {} + function test_Revert_TokenArrayMathApprox() private view {} - function test_Revert_TokenArrayLimitIn() public view {} + function test_Revert_TokenArrayLimitIn() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_TokenArrayBalance() public view {} + function test_Set_TokenArrayBalance() private view {} - function test_Emit_TokenArrayLogJoin() public view {} + function test_Emit_TokenArrayLogJoin() private view {} - function test_Pull_TokenArrayTokenAmountIn() public view {} + function test_Pull_TokenArrayTokenAmountIn() private view {} - function test_Mint_PoolShare() public view {} + function test_Mint_PoolShare() private view {} - function test_Push_PoolShare() public view {} + function test_Push_PoolShare() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} modifier happyPath(FuzzScenario memory _fuzz) { _assumeHappyPath(_fuzz); @@ -364,211 +364,211 @@ contract BPool_Unit_JoinPool is Base { } contract BPool_Unit_ExitPool is Base { - function test_Revert_NotFinalized() public view {} + function test_Revert_NotFinalized() private view {} - function test_Revert_MathApprox() public view {} + function test_Revert_MathApprox() private view {} - function test_Pull_PoolShare() public view {} + function test_Pull_PoolShare() private view {} - function test_Push_PoolShare() public view {} + function test_Push_PoolShare() private view {} - function test_Burn_PoolShare() public view {} + function test_Burn_PoolShare() private view {} - function test_Revert_TokenArrayMathApprox() public view {} + function test_Revert_TokenArrayMathApprox() private view {} - function test_Revert_TokenArrayLimitOut() public view {} + function test_Revert_TokenArrayLimitOut() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_TokenArrayBalance() public view {} + function test_Set_TokenArrayBalance() private view {} - function test_Emit_TokenArrayLogExit() public view {} + function test_Emit_TokenArrayLogExit() private view {} - function test_Push_TokenArrayTokenAmountOut() public view {} + function test_Push_TokenArrayTokenAmountOut() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_SwapExactAmountIn is Base { - function test_Revert_NotBoundTokenIn() public view {} + function test_Revert_NotBoundTokenIn() private view {} - function test_Revert_NotBoundTokenOut() public view {} + function test_Revert_NotBoundTokenOut() private view {} - function test_Revert_NotPublic() public view {} + function test_Revert_NotPublic() private view {} - function test_Revert_MaxInRatio() public view {} + function test_Revert_MaxInRatio() private view {} - function test_Revert_BadLimitPrice() public view {} + function test_Revert_BadLimitPrice() private view {} - function test_Revert_LimitOut() public view {} + function test_Revert_LimitOut() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_InRecord() public view {} + function test_Set_InRecord() private view {} - function test_Set_OutRecord() public view {} + function test_Set_OutRecord() private view {} - function test_Revert_MathApprox() public view {} + function test_Revert_MathApprox() private view {} - function test_Revert_LimitPrice() public view {} + function test_Revert_LimitPrice() private view {} - function test_Revert_MathApprox2() public view {} + function test_Revert_MathApprox2() private view {} - function test_Emit_LogSwap() public view {} + function test_Emit_LogSwap() private view {} - function test_Pull_TokenAmountIn() public view {} + function test_Pull_TokenAmountIn() private view {} - function test_Push_TokenAmountOut() public view {} + function test_Push_TokenAmountOut() private view {} - function test_Returns_AmountAndPrice() public view {} + function test_Returns_AmountAndPrice() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_SwapExactAmountOut is Base { - function test_Revert_NotBoundTokenIn() public view {} + function test_Revert_NotBoundTokenIn() private view {} - function test_Revert_NotBoundTokenOut() public view {} + function test_Revert_NotBoundTokenOut() private view {} - function test_Revert_NotPublic() public view {} + function test_Revert_NotPublic() private view {} - function test_Revert_MaxOutRatio() public view {} + function test_Revert_MaxOutRatio() private view {} - function test_Revert_BadLimitPrice() public view {} + function test_Revert_BadLimitPrice() private view {} - function test_Revert_LimitIn() public view {} + function test_Revert_LimitIn() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_InRecord() public view {} + function test_Set_InRecord() private view {} - function test_Set_OutRecord() public view {} + function test_Set_OutRecord() private view {} - function test_Revert_MathApprox() public view {} + function test_Revert_MathApprox() private view {} - function test_Revert_LimitPrice() public view {} + function test_Revert_LimitPrice() private view {} - function test_Revert_MathApprox2() public view {} + function test_Revert_MathApprox2() private view {} - function test_Emit_LogSwap() public view {} + function test_Emit_LogSwap() private view {} - function test_Pull_TokenAmountIn() public view {} + function test_Pull_TokenAmountIn() private view {} - function test_Push_TokenAmountOut() public view {} + function test_Push_TokenAmountOut() private view {} - function test_Returns_AmountAndPrice() public view {} + function test_Returns_AmountAndPrice() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_JoinswapExternAmountIn is Base { - function test_Revert_NotFinalized() public view {} + function test_Revert_NotFinalized() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} - function test_Revert_MaxInRatio() public view {} + function test_Revert_MaxInRatio() private view {} - function test_Revert_LimitOut() public view {} + function test_Revert_LimitOut() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_Balance() public view {} + function test_Set_Balance() private view {} - function test_Emit_LogJoin() public view {} + function test_Emit_LogJoin() private view {} - function test_Mint_PoolShare() public view {} + function test_Mint_PoolShare() private view {} - function test_Push_PoolShare() public view {} + function test_Push_PoolShare() private view {} - function test_Pull_Underlying() public view {} + function test_Pull_Underlying() private view {} - function test_Returns_PoolAmountOut() public view {} + function test_Returns_PoolAmountOut() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_JoinswapExternAmountOut is Base { - function test_Revert_NotFinalized() public view {} + function test_Revert_NotFinalized() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} - function test_Revert_MaxApprox() public view {} + function test_Revert_MaxApprox() private view {} - function test_Revert_LimitIn() public view {} + function test_Revert_LimitIn() private view {} - function test_Revert_MaxInRatio() public view {} + function test_Revert_MaxInRatio() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_Balance() public view {} + function test_Set_Balance() private view {} - function test_Emit_LogJoin() public view {} + function test_Emit_LogJoin() private view {} - function test_Mint_PoolShare() public view {} + function test_Mint_PoolShare() private view {} - function test_Push_PoolShare() public view {} + function test_Push_PoolShare() private view {} - function test_Pull_Underlying() public view {} + function test_Pull_Underlying() private view {} - function test_Returns_TokenAmountIn() public view {} + function test_Returns_TokenAmountIn() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_ExitswapPoolAmountIn is Base { - function test_Revert_NotFinalized() public view {} + function test_Revert_NotFinalized() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} - function test_Revert_LimitOut() public view {} + function test_Revert_LimitOut() private view {} - function test_Revert_MaxOutRatio() public view {} + function test_Revert_MaxOutRatio() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_Balance() public view {} + function test_Set_Balance() private view {} - function test_Emit_LogExit() public view {} + function test_Emit_LogExit() private view {} - function test_Pull_PoolShare() public view {} + function test_Pull_PoolShare() private view {} - function test_Burn_PoolShare() public view {} + function test_Burn_PoolShare() private view {} - function test_Push_PoolShare() public view {} + function test_Push_PoolShare() private view {} - function test_Push_Underlying() public view {} + function test_Push_Underlying() private view {} - function test_Returns_TokenAmountOut() public view {} + function test_Returns_TokenAmountOut() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } contract BPool_Unit_ExitswapPoolAmountOut is Base { - function test_Revert_NotFinalized() public view {} + function test_Revert_NotFinalized() private view {} - function test_Revert_NotBound() public view {} + function test_Revert_NotBound() private view {} - function test_Revert_MaxOutRatio() public view {} + function test_Revert_MaxOutRatio() private view {} - function test_Revert_MathApprox() public view {} + function test_Revert_MathApprox() private view {} - function test_Revert_LimitIn() public view {} + function test_Revert_LimitIn() private view {} - function test_Revert_Reentrancy() public view {} + function test_Revert_Reentrancy() private view {} - function test_Set_Balance() public view {} + function test_Set_Balance() private view {} - function test_Emit_LogExit() public view {} + function test_Emit_LogExit() private view {} - function test_Pull_PoolShare() public view {} + function test_Pull_PoolShare() private view {} - function test_Burn_PoolShare() public view {} + function test_Burn_PoolShare() private view {} - function test_Push_PoolShare() public view {} + function test_Push_PoolShare() private view {} - function test_Push_Underlying() public view {} + function test_Push_Underlying() private view {} - function test_Returns_PoolAmountIn() public view {} + function test_Returns_PoolAmountIn() private view {} - function test_Emit_LogCall() public view {} + function test_Emit_LogCall() private view {} } From 589518b009f8f2e650cc15c772b4da4b3905857b Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 17:03:22 -0300 Subject: [PATCH 14/18] test: small improvements --- test/unit/BPool.t.sol | 187 +++++++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 92 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index e5abcc94..808ad56e 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -7,14 +7,29 @@ import {IERC20} from 'contracts/BToken.sol'; import {Test} from 'forge-std/Test.sol'; import {LibString} from 'solmate/utils/LibString.sol'; -abstract contract Base is Test, BConst { +// TODO: remove once `private` keyword is removed in all test cases +/* solhint-disable */ + +abstract contract BasePoolTest is Test, BConst { using LibString for *; + struct FuzzScenario { + uint256 poolAmountOut; + uint256 initPoolSupply; + uint256[TOKENS_AMOUNT] balance; + } + uint256 public constant TOKENS_AMOUNT = 3; BPool public bPool; address[TOKENS_AMOUNT] public tokens; + modifier happyPath(FuzzScenario memory _fuzz) { + _assumeHappyPath(_fuzz); + _setValues(_fuzz); + _; + } + function setUp() public { bPool = new BPool(); @@ -23,37 +38,85 @@ abstract contract Base is Test, BConst { tokens[i] = makeAddr(i.toString()); } } + + function _setValues(FuzzScenario memory _fuzz) internal { + // Create mocks + for (uint256 i = 0; i < tokens.length; i++) { + vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transfer.selector), abi.encode(true)); + vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transferFrom.selector), abi.encode(true)); + } + + // Set tokens + bytes memory _arraySlot = abi.encode(9); + bytes32 _hashArraySlot = keccak256(_arraySlot); + vm.store(address(bPool), bytes32(_arraySlot), bytes32(tokens.length)); // write length + for (uint256 i = 0; i < tokens.length; i++) { + vm.store(address(bPool), bytes32(uint256(_hashArraySlot) + i), bytes32(abi.encode(tokens[i]))); // write token + } + + // Set balances + for (uint256 i = 0; i < tokens.length; i++) { + bytes32 _slot = keccak256(abi.encode(tokens[i], 10)); // mapping is found at slot 10 + vm.store(address(bPool), bytes32(uint256(_slot) + 0), bytes32(abi.encode(1))); // bound + vm.store(address(bPool), bytes32(uint256(_slot) + 3), bytes32(abi.encode(_fuzz.balance[i]))); // balance + } + + // Set public swap + vm.store( + address(bPool), + bytes32(uint256(6)), + bytes32(uint256(0x0000000000000000000000010000000000000000000000000000000000000000)) + ); + // Set finalize + vm.store(address(bPool), bytes32(uint256(8)), bytes32(uint256(1))); + // Set totalSupply + vm.store(address(bPool), bytes32(uint256(2)), bytes32(_fuzz.initPoolSupply)); + } + + function _assumeHappyPath(FuzzScenario memory _fuzz) internal view { + vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY); + vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply); + vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); + + uint256 _ratio = (_fuzz.poolAmountOut * BONE) / _fuzz.initPoolSupply; // bdiv uses '* BONE' + uint256 _maxTokenAmountIn = type(uint256).max / _ratio; + + for (uint256 i = 0; i < _fuzz.balance.length; i++) { + vm.assume(_fuzz.balance[i] >= MIN_BALANCE); + vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272 + } + } } -contract BPool_Unit_Constructor is Base { +contract BPool_Unit_Constructor is BasePoolTest { function test_Deploy() private view {} } -contract BPool_Unit_IsPublicSwap is Base { +contract BPool_Unit_IsPublicSwap is BasePoolTest { function test_Returns_IsPublicSwap() private view {} } -contract BPool_Unit_IsFinalized is Base { +contract BPool_Unit_IsFinalized is BasePoolTest { function test_Returns_IsFinalized() private view {} } -contract BPool_Unit_IsBound is Base { +contract BPool_Unit_IsBound is BasePoolTest { function test_Returns_IsBound() private view {} function test_Returns_IsNotBound() private view {} } -contract BPool_Unit_GetNumTokens is Base { +contract BPool_Unit_GetNumTokens is BasePoolTest { function test_Returns_NumTokens() private view {} } -contract BPool_Unit_GetCurrentTokens is Base { +contract BPool_Unit_GetCurrentTokens is BasePoolTest { function test_Returns_CurrentTokens() private view {} function test_Revert_Reentrancy() private view {} } -contract BPool_Unit_GetFinalTokens is Base { +contract BPool_Unit_GetFinalTokens is BasePoolTest { function test_Returns_FinalTokens() private view {} function test_Revert_Reentrancy() private view {} @@ -61,7 +124,7 @@ contract BPool_Unit_GetFinalTokens is Base { function test_Revert_NotFinalized() private view {} } -contract BPool_Unit_GetDenormalizedWeight is Base { +contract BPool_Unit_GetDenormalizedWeight is BasePoolTest { function test_Returns_DenormalizedWeight() private view {} function test_Revert_Reentrancy() private view {} @@ -69,13 +132,13 @@ contract BPool_Unit_GetDenormalizedWeight is Base { function test_Revert_NotBound() private view {} } -contract BPool_Unit_GetTotalDenormalizedWeight is Base { +contract BPool_Unit_GetTotalDenormalizedWeight is BasePoolTest { function test_Returns_TotalDenormalizedWeight() private view {} function test_Revert_Reentrancy() private view {} } -contract BPool_Unit_GetNormalizedWeight is Base { +contract BPool_Unit_GetNormalizedWeight is BasePoolTest { function test_Returns_NormalizedWeight() private view {} function test_Revert_Reentrancy() private view {} @@ -83,7 +146,7 @@ contract BPool_Unit_GetNormalizedWeight is Base { function test_Revert_NotBound() private view {} } -contract BPool_Unit_GetBalance is Base { +contract BPool_Unit_GetBalance is BasePoolTest { function test_Returns_Balance() private view {} function test_Revert_Reentrancy() private view {} @@ -91,19 +154,19 @@ contract BPool_Unit_GetBalance is Base { function test_Revert_NotBound() private view {} } -contract BPool_Unit_GetSwapFee is Base { +contract BPool_Unit_GetSwapFee is BasePoolTest { function test_Returns_SwapFee() private view {} function test_Revert_Reentrancy() private view {} } -contract BPool_Unit_GetController is Base { +contract BPool_Unit_GetController is BasePoolTest { function test_Returns_Controller() private view {} function test_Revert_Reentrancy() private view {} } -contract BPool_Unit_SetSwapFee is Base { +contract BPool_Unit_SetSwapFee is BasePoolTest { function test_Revert_Finalized() private view {} function test_Revert_NotController() private view {} @@ -119,7 +182,7 @@ contract BPool_Unit_SetSwapFee is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_SetController is Base { +contract BPool_Unit_SetController is BasePoolTest { function test_Revert_NotController() private view {} function test_Revert_Reentrancy() private view {} @@ -129,7 +192,7 @@ contract BPool_Unit_SetController is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_SetPublicSwap is Base { +contract BPool_Unit_SetPublicSwap is BasePoolTest { function test_Revert_Finalized() private view {} function test_Revert_NotController() private view {} @@ -141,7 +204,7 @@ contract BPool_Unit_SetPublicSwap is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_Finalize is Base { +contract BPool_Unit_Finalize is BasePoolTest { function test_Revert_NotController() private view {} function test_Revert_Finalized() private view {} @@ -161,7 +224,7 @@ contract BPool_Unit_Finalize is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_Bind is Base { +contract BPool_Unit_Bind is BasePoolTest { function test_Revert_NotController() private view {} function test_Revert_IsBound() private view {} @@ -179,7 +242,7 @@ contract BPool_Unit_Bind is Base { function test_Call_Rebind() private view {} } -contract BPool_Unit_Rebind is Base { +contract BPool_Unit_Rebind is BasePoolTest { function test_Revert_NotController() private view {} function test_Revert_NotBound() private view {} @@ -213,7 +276,7 @@ contract BPool_Unit_Rebind is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_Unbind is Base { +contract BPool_Unit_Unbind is BasePoolTest { function test_Revert_NotController() private view {} function test_Revert_NotBound() private view {} @@ -239,7 +302,7 @@ contract BPool_Unit_Unbind is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_Gulp is Base { +contract BPool_Unit_Gulp is BasePoolTest { function test_Revert_NotBound() private view {} function test_Revert_Reentrancy() private view {} @@ -249,7 +312,7 @@ contract BPool_Unit_Gulp is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_GetSpotPrice is Base { +contract BPool_Unit_GetSpotPrice is BasePoolTest { function test_Revert_NotBoundTokenIn() private view {} function test_Revert_NotBoundTokenOut() private view {} @@ -259,7 +322,7 @@ contract BPool_Unit_GetSpotPrice is Base { function test_Revert_Reentrancy() private view {} } -contract BPool_Unit_GetSpotPriceSansFee is Base { +contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest { function test_Revert_NotBoundTokenIn() private view {} function test_Revert_NotBoundTokenOut() private view {} @@ -269,13 +332,7 @@ contract BPool_Unit_GetSpotPriceSansFee is Base { function test_Revert_Reentrancy() private view {} } -contract BPool_Unit_JoinPool is Base { - struct FuzzScenario { - uint256 poolAmountOut; - uint256 initPoolSupply; - uint256[TOKENS_AMOUNT] balance; - } - +contract BPool_Unit_JoinPool is BasePoolTest { function test_HappyPath(FuzzScenario memory _fuzz) public happyPath(_fuzz) { uint256[] memory maxAmountsIn = new uint256[](tokens.length); for (uint256 i = 0; i < tokens.length; i++) { @@ -306,63 +363,9 @@ contract BPool_Unit_JoinPool is Base { function test_Push_PoolShare() private view {} function test_Emit_LogCall() private view {} - - modifier happyPath(FuzzScenario memory _fuzz) { - _assumeHappyPath(_fuzz); - _setValues(_fuzz); - _; - } - - function _setValues(FuzzScenario memory _fuzz) internal { - // Create mocks - for (uint256 i = 0; i < tokens.length; i++) { - vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transfer.selector), abi.encode(true)); - vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transferFrom.selector), abi.encode(true)); - } - - // Set tokens - bytes memory _arraySlot = abi.encode(9); - bytes32 _hashArraySlot = keccak256(_arraySlot); - vm.store(address(bPool), bytes32(_arraySlot), bytes32(tokens.length)); // write length - for (uint256 i = 0; i < tokens.length; i++) { - vm.store(address(bPool), bytes32(uint256(_hashArraySlot) + i), bytes32(abi.encode(tokens[i]))); // write token - } - - // Set balances - for (uint256 i = 0; i < tokens.length; i++) { - bytes32 _slot = keccak256(abi.encode(tokens[i], 10)); // mapping is found at slot 10 - vm.store(address(bPool), bytes32(uint256(_slot) + 0), bytes32(abi.encode(1))); // bound - vm.store(address(bPool), bytes32(uint256(_slot) + 3), bytes32(abi.encode(_fuzz.balance[i]))); // balance - } - - // Set public swap - vm.store( - address(bPool), - bytes32(uint256(6)), - bytes32(uint256(0x0000000000000000000000010000000000000000000000000000000000000000)) - ); - // Set finalize - vm.store(address(bPool), bytes32(uint256(8)), bytes32(uint256(1))); - // Set totalSupply - vm.store(address(bPool), bytes32(uint256(2)), bytes32(_fuzz.initPoolSupply)); - } - - function _assumeHappyPath(FuzzScenario memory _fuzz) internal view { - vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY); - vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply); - vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); - - uint256 _ratio = (_fuzz.poolAmountOut * BONE) / _fuzz.initPoolSupply; // bdiv uses '* BONE' - uint256 _maxTokenAmountIn = type(uint256).max / _ratio; - - for (uint256 i = 0; i < _fuzz.balance.length; i++) { - vm.assume(_fuzz.balance[i] >= MIN_BALANCE); - vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272 - } - } } -contract BPool_Unit_ExitPool is Base { +contract BPool_Unit_ExitPool is BasePoolTest { function test_Revert_NotFinalized() private view {} function test_Revert_MathApprox() private view {} @@ -388,7 +391,7 @@ contract BPool_Unit_ExitPool is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_SwapExactAmountIn is Base { +contract BPool_Unit_SwapExactAmountIn is BasePoolTest { function test_Revert_NotBoundTokenIn() private view {} function test_Revert_NotBoundTokenOut() private view {} @@ -424,7 +427,7 @@ contract BPool_Unit_SwapExactAmountIn is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_SwapExactAmountOut is Base { +contract BPool_Unit_SwapExactAmountOut is BasePoolTest { function test_Revert_NotBoundTokenIn() private view {} function test_Revert_NotBoundTokenOut() private view {} @@ -460,7 +463,7 @@ contract BPool_Unit_SwapExactAmountOut is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_JoinswapExternAmountIn is Base { +contract BPool_Unit_JoinswapExternAmountIn is BasePoolTest { function test_Revert_NotFinalized() private view {} function test_Revert_NotBound() private view {} @@ -486,7 +489,7 @@ contract BPool_Unit_JoinswapExternAmountIn is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_JoinswapExternAmountOut is Base { +contract BPool_Unit_JoinswapExternAmountOut is BasePoolTest { function test_Revert_NotFinalized() private view {} function test_Revert_NotBound() private view {} @@ -514,7 +517,7 @@ contract BPool_Unit_JoinswapExternAmountOut is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_ExitswapPoolAmountIn is Base { +contract BPool_Unit_ExitswapPoolAmountIn is BasePoolTest { function test_Revert_NotFinalized() private view {} function test_Revert_NotBound() private view {} @@ -542,7 +545,7 @@ contract BPool_Unit_ExitswapPoolAmountIn is Base { function test_Emit_LogCall() private view {} } -contract BPool_Unit_ExitswapPoolAmountOut is Base { +contract BPool_Unit_ExitswapPoolAmountOut is BasePoolTest { function test_Revert_NotFinalized() private view {} function test_Revert_NotBound() private view {} From 7a4031db5008f70159c7cd57d0c4cbd78ab6abd9 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 17:03:53 -0300 Subject: [PATCH 15/18] fix: small compilation error --- test/unit/BPool.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 808ad56e..7a4329f9 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -13,14 +13,14 @@ import {LibString} from 'solmate/utils/LibString.sol'; abstract contract BasePoolTest is Test, BConst { using LibString for *; + uint256 public constant TOKENS_AMOUNT = 3; + struct FuzzScenario { uint256 poolAmountOut; uint256 initPoolSupply; uint256[TOKENS_AMOUNT] balance; } - uint256 public constant TOKENS_AMOUNT = 3; - BPool public bPool; address[TOKENS_AMOUNT] public tokens; From f7d4a744e870d59b24afba5d11efdf7612f96e74 Mon Sep 17 00:00:00 2001 From: 0xAustrian Date: Wed, 8 May 2024 21:33:37 -0300 Subject: [PATCH 16/18] test: create Utils library --- foundry.toml | 2 +- test/unit/BPool.t.sol | 28 ++++++++----------- test/unit/Utils.sol | 64 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 test/unit/Utils.sol diff --git a/foundry.toml b/foundry.toml index 8599d1c0..26cc6e77 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,7 +26,7 @@ src = 'src/interfaces/' [fuzz] runs = 1000 -max_test_rejects = 400000 +max_test_rejects = 500000 [rpc_endpoints] mainnet = "${MAINNET_RPC}" diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 7a4329f9..5d55e6fd 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -6,11 +6,12 @@ import {BPool} from 'contracts/BPool.sol'; import {IERC20} from 'contracts/BToken.sol'; import {Test} from 'forge-std/Test.sol'; import {LibString} from 'solmate/utils/LibString.sol'; +import {Utils} from 'test/unit/Utils.sol'; // TODO: remove once `private` keyword is removed in all test cases /* solhint-disable */ -abstract contract BasePoolTest is Test, BConst { +abstract contract BasePoolTest is Test, BConst, Utils { using LibString for *; uint256 public constant TOKENS_AMOUNT = 3; @@ -47,33 +48,28 @@ abstract contract BasePoolTest is Test, BConst { } // Set tokens - bytes memory _arraySlot = abi.encode(9); - bytes32 _hashArraySlot = keccak256(_arraySlot); - vm.store(address(bPool), bytes32(_arraySlot), bytes32(tokens.length)); // write length + uint256 _arraySlotNumber = 9; + _writeArrayLengthToStorage(address(bPool), _arraySlotNumber, tokens.length); // write length for (uint256 i = 0; i < tokens.length; i++) { - vm.store(address(bPool), bytes32(uint256(_hashArraySlot) + i), bytes32(abi.encode(tokens[i]))); // write token + _writeAddressArrayItemToStorage(address(bPool), _arraySlotNumber, i, tokens[i]); // write token } // Set balances + uint256 _mappingSlotNumber = 10; for (uint256 i = 0; i < tokens.length; i++) { - bytes32 _slot = keccak256(abi.encode(tokens[i], 10)); // mapping is found at slot 10 - vm.store(address(bPool), bytes32(uint256(_slot) + 0), bytes32(abi.encode(1))); // bound - vm.store(address(bPool), bytes32(uint256(_slot) + 3), bytes32(abi.encode(_fuzz.balance[i]))); // balance + _writeStructPropertyAtAddressMapping(address(bPool), _mappingSlotNumber, tokens[i], 0, 1); // bound (1 == true) + _writeStructPropertyAtAddressMapping(address(bPool), _mappingSlotNumber, tokens[i], 3, _fuzz.balance[i]); // balance } // Set public swap - vm.store( - address(bPool), - bytes32(uint256(6)), - bytes32(uint256(0x0000000000000000000000010000000000000000000000000000000000000000)) - ); + _writeUintToStorage(address(bPool), 6, 0x0000000000000000000000010000000000000000000000000000000000000000); // Set finalize - vm.store(address(bPool), bytes32(uint256(8)), bytes32(uint256(1))); + _writeUintToStorage(address(bPool), 8, 1); // Set totalSupply - vm.store(address(bPool), bytes32(uint256(2)), bytes32(_fuzz.initPoolSupply)); + _writeUintToStorage(address(bPool), 2, _fuzz.initPoolSupply); } - function _assumeHappyPath(FuzzScenario memory _fuzz) internal view { + function _assumeHappyPath(FuzzScenario memory _fuzz) internal pure { vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY); vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply); vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); diff --git a/test/unit/Utils.sol b/test/unit/Utils.sol new file mode 100644 index 00000000..ba1c5e25 --- /dev/null +++ b/test/unit/Utils.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {Test} from 'forge-std/Test.sol'; + +contract Utils is Test { + /** + * @dev Write a uint256 value to a storage slot. + * @param _target The address of the contract. + * @param _slotNumber The slot number to write to. + * @param _value The value to write. + */ + function _writeUintToStorage(address _target, uint256 _slotNumber, uint256 _value) internal { + vm.store(_target, bytes32(_slotNumber), bytes32(_value)); + } + + /** + * @dev Write the length of an array in storage. + * @dev This must be performed before writing any items to the array. + * @param _target The address of the contract. + * @param _arraySlotNumber The slot number of the array. + * @param _arrayLength The length of the array. + */ + function _writeArrayLengthToStorage(address _target, uint256 _arraySlotNumber, uint256 _arrayLength) internal { + _writeUintToStorage(_target, _arraySlotNumber, _arrayLength); + } + + /** + * @dev Write an address array item to a storage slot. + * @param _target The address of the contract. + * @param _arraySlotNumber The slot number of the array. + * @param _index The index of the item in the array. + * @param _value The address value to write. + */ + function _writeAddressArrayItemToStorage( + address _target, + uint256 _arraySlotNumber, + uint256 _index, + address _value + ) internal { + bytes memory _arraySlot = abi.encode(_arraySlotNumber); + bytes32 _hashArraySlot = keccak256(_arraySlot); + vm.store(_target, bytes32(uint256(_hashArraySlot) + _index), bytes32(abi.encode(_value))); + } + + /** + * @dev Write a struct property to a mapping in storage. + * @param _target The address of the contract. + * @param _mappingSlotNumber The slot number of the mapping. + * @param _mappingKey The address key of the mapping. + * @param _propertySlotNumber The slot number of the property in the struct. + * @param _value The value to write. + */ + function _writeStructPropertyAtAddressMapping( + address _target, + uint256 _mappingSlotNumber, + address _mappingKey, + uint256 _propertySlotNumber, + uint256 _value + ) internal { + bytes32 _slot = keccak256(abi.encode(_mappingKey, _mappingSlotNumber)); + _writeUintToStorage(_target, uint256(_slot) + _propertySlotNumber, _value); + } +} From 4d317933b6ea0260ee005283e6c48eb5ab20ef12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wei=C3=9Fer=20Hase?= Date: Thu, 9 May 2024 14:27:00 +0200 Subject: [PATCH 17/18] fix: moving happy path to specific test contract (#11) * fix: moving happy path to specific test contract * fix: linter errors --- .solhint.json | 5 +- test/unit/BPool.t.sol | 131 ++++++++++++++++++++++++++++-------------- 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/.solhint.json b/.solhint.json index 94f58b24..a8b002b7 100644 --- a/.solhint.json +++ b/.solhint.json @@ -13,6 +13,9 @@ "immutable-name-snakecase": "warn", "avoid-low-level-calls": "off", "no-console": "off", - "max-line-length": ["warn", 120] + "max-line-length": ["warn", 120], + "TODO": "REMOVE_TEMPORARY_LINTER_SETTINGS_BELOW", + "custom-errors": "warn", + "definition-name-capwords": "warn" } } diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 5d55e6fd..64038c18 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -15,22 +15,12 @@ abstract contract BasePoolTest is Test, BConst, Utils { using LibString for *; uint256 public constant TOKENS_AMOUNT = 3; - - struct FuzzScenario { - uint256 poolAmountOut; - uint256 initPoolSupply; - uint256[TOKENS_AMOUNT] balance; - } + uint256 internal constant _RECORD_MAPPING_SLOT_NUMBER = 10; + uint256 internal constant _TOKENS_ARRAY_SLOT_NUMBER = 9; BPool public bPool; address[TOKENS_AMOUNT] public tokens; - modifier happyPath(FuzzScenario memory _fuzz) { - _assumeHappyPath(_fuzz); - _setValues(_fuzz); - _; - } - function setUp() public { bPool = new BPool(); @@ -40,47 +30,50 @@ abstract contract BasePoolTest is Test, BConst, Utils { } } - function _setValues(FuzzScenario memory _fuzz) internal { - // Create mocks + function _tokensToMemory() internal view returns (address[] memory _tokens) { + _tokens = new address[](tokens.length); for (uint256 i = 0; i < tokens.length; i++) { - vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transfer.selector), abi.encode(true)); - vm.mockCall(tokens[i], abi.encodeWithSelector(IERC20(tokens[i]).transferFrom.selector), abi.encode(true)); + _tokens[i] = tokens[i]; } + } - // Set tokens - uint256 _arraySlotNumber = 9; - _writeArrayLengthToStorage(address(bPool), _arraySlotNumber, tokens.length); // write length - for (uint256 i = 0; i < tokens.length; i++) { - _writeAddressArrayItemToStorage(address(bPool), _arraySlotNumber, i, tokens[i]); // write token - } + function _mockTransfer(address _token) internal { + // TODO: add amount to transfer to check that it's called with the right amount + vm.mockCall(_token, abi.encodeWithSelector(IERC20(_token).transfer.selector), abi.encode(true)); + } - // Set balances - uint256 _mappingSlotNumber = 10; - for (uint256 i = 0; i < tokens.length; i++) { - _writeStructPropertyAtAddressMapping(address(bPool), _mappingSlotNumber, tokens[i], 0, 1); // bound (1 == true) - _writeStructPropertyAtAddressMapping(address(bPool), _mappingSlotNumber, tokens[i], 3, _fuzz.balance[i]); // balance + function _mockTransferFrom(address _token) internal { + // TODO: add from and amount to transfer to check that it's called with the right params + vm.mockCall(_token, abi.encodeWithSelector(IERC20(_token).transferFrom.selector), abi.encode(true)); + } + + function _setTokens(address[] memory _tokens) internal { + _writeArrayLengthToStorage(address(bPool), _TOKENS_ARRAY_SLOT_NUMBER, _tokens.length); // write length + for (uint256 i = 0; i < _tokens.length; i++) { + _writeAddressArrayItemToStorage(address(bPool), _TOKENS_ARRAY_SLOT_NUMBER, i, _tokens[i]); // write token } + } - // Set public swap - _writeUintToStorage(address(bPool), 6, 0x0000000000000000000000010000000000000000000000000000000000000000); - // Set finalize - _writeUintToStorage(address(bPool), 8, 1); - // Set totalSupply - _writeUintToStorage(address(bPool), 2, _fuzz.initPoolSupply); + function _setRecordBound(address _token) internal { + _writeStructPropertyAtAddressMapping(address(bPool), _RECORD_MAPPING_SLOT_NUMBER, _token, 0, 1); // bound (1 == true) } - function _assumeHappyPath(FuzzScenario memory _fuzz) internal pure { - vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY); - vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply); - vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); + function _setRecordBalance(address _token, uint256 _balance) internal { + _writeStructPropertyAtAddressMapping(address(bPool), _RECORD_MAPPING_SLOT_NUMBER, _token, 3, _balance); // balance + } - uint256 _ratio = (_fuzz.poolAmountOut * BONE) / _fuzz.initPoolSupply; // bdiv uses '* BONE' - uint256 _maxTokenAmountIn = type(uint256).max / _ratio; + function _setPublicSwap(bool _isPublicSwap) internal { + // TODO: make it depend on the bool value + _writeUintToStorage(address(bPool), 6, 0x0000000000000000000000010000000000000000000000000000000000000000); + } - for (uint256 i = 0; i < _fuzz.balance.length; i++) { - vm.assume(_fuzz.balance[i] >= MIN_BALANCE); - vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272 - } + function _setFinalize(bool _isFinalized) internal { + // TODO: make it depend on the bool value + _writeUintToStorage(address(bPool), 8, 1); + } + + function _setTotalSupply(uint256 _totalSupply) internal { + _writeUintToStorage(address(bPool), 2, _totalSupply); } } @@ -329,7 +322,57 @@ contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest { } contract BPool_Unit_JoinPool is BasePoolTest { - function test_HappyPath(FuzzScenario memory _fuzz) public happyPath(_fuzz) { + struct JoinPool_FuzzScenario { + uint256 poolAmountOut; + uint256 initPoolSupply; + uint256[TOKENS_AMOUNT] balance; + } + + function _setValues(JoinPool_FuzzScenario memory _fuzz) internal { + // Create mocks + for (uint256 i = 0; i < tokens.length; i++) { + _mockTransfer(tokens[i]); + _mockTransferFrom(tokens[i]); + } + + // Set tokens + _setTokens(_tokensToMemory()); + + // Set balances + for (uint256 i = 0; i < tokens.length; i++) { + _setRecordBound(tokens[i]); + _setRecordBalance(tokens[i], _fuzz.balance[i]); + } + + // Set public swap + _setPublicSwap(true); + // Set finalize + _setFinalize(true); + // Set totalSupply + _setTotalSupply(_fuzz.initPoolSupply); + } + + function _assumeHappyPath(JoinPool_FuzzScenario memory _fuzz) internal pure { + vm.assume(_fuzz.initPoolSupply >= INIT_POOL_SUPPLY); + vm.assume(_fuzz.poolAmountOut >= _fuzz.initPoolSupply); + vm.assume(_fuzz.poolAmountOut < type(uint256).max / BONE); + + uint256 _ratio = (_fuzz.poolAmountOut * BONE) / _fuzz.initPoolSupply; // bdiv uses '* BONE' + uint256 _maxTokenAmountIn = type(uint256).max / _ratio; + + for (uint256 i = 0; i < _fuzz.balance.length; i++) { + vm.assume(_fuzz.balance[i] >= MIN_BALANCE); + vm.assume(_fuzz.balance[i] <= _maxTokenAmountIn); // L272 + } + } + + modifier happyPath(JoinPool_FuzzScenario memory _fuzz) { + _assumeHappyPath(_fuzz); + _setValues(_fuzz); + _; + } + + function test_HappyPath(JoinPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) { uint256[] memory maxAmountsIn = new uint256[](tokens.length); for (uint256 i = 0; i < tokens.length; i++) { maxAmountsIn[i] = type(uint256).max; From 922e19c444d7cc2dd9a47b86235a0ff8c6d4a8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wei=C3=9Fer=20Hase?= Date: Thu, 9 May 2024 14:38:38 +0200 Subject: [PATCH 18/18] feat: adding smock package (#10) * feat: adding smock package * fix: moving happy path to specific test contract * fix: linter errors * feat: implementing smock to unit test * fix: adding smock to ci * fix: ci * fix: addressing comments in pr --- .github/workflows/ci.yml | 6 +++ .gitignore | 3 ++ package.json | 2 + src/contracts/BFactory.sol | 4 +- src/contracts/BPool.sol | 20 +++++----- src/contracts/BToken.sol | 6 +-- src/contracts/Migrations.sol | 23 ----------- test/unit/BPool.t.sol | 39 ++++++++++--------- yarn.lock | 75 +++++++++++++++++++++++++++++++++++- 9 files changed, 119 insertions(+), 59 deletions(-) delete mode 100644 src/contracts/Migrations.sol diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7b79eeb..300f519c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,9 @@ jobs: - name: Install dependencies run: yarn --frozen-lockfile --network-concurrency 1 + - name: Create mock files with smock + run: yarn smock + - name: Precompile using 0.8.14 and via-ir=false run: yarn build @@ -58,6 +61,9 @@ jobs: - name: Install dependencies run: yarn --frozen-lockfile --network-concurrency 1 + - name: Create mock files with smock + run: yarn smock + - name: Precompile using 0.8.14 and via-ir=false run: yarn build diff --git a/.gitignore b/.gitignore index 9209344d..c6c30df9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ broadcast/*/*/* # Out dir out + +# Smock dir +test/smock diff --git a/package.json b/package.json index c1dc0bba..eb4e08f9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' 'script/**/*.sol'", "lint:sol-tests": "solhint -c .solhint.tests.json 'test/**/*.sol'", "prepare": "husky install", + "smock": "smock-foundry --contracts src/contracts", "test": "forge test -vvv", "test:integration": "forge test --match-contract Integration -vvv", "test:unit": "forge test --match-contract Unit -vvv", @@ -42,6 +43,7 @@ "@commitlint/cli": "19.3.0", "@commitlint/config-conventional": "19.2.2", "@defi-wonderland/natspec-smells": "1.1.1", + "@defi-wonderland/smock-foundry": "1.5.0", "forge-std": "github:foundry-rs/forge-std#5475f85", "husky": ">=8", "lint-staged": ">=10", diff --git a/src/contracts/BFactory.sol b/src/contracts/BFactory.sol index b96e7a78..bdf561f6 100644 --- a/src/contracts/BFactory.sol +++ b/src/contracts/BFactory.sol @@ -22,7 +22,7 @@ contract BFactory is BBronze { event LOG_BLABS(address indexed caller, address indexed blabs); - mapping(address => bool) private _isBPool; + mapping(address => bool) internal _isBPool; function isBPool(address b) external view returns (bool) { return _isBPool[b]; @@ -36,7 +36,7 @@ contract BFactory is BBronze { return bpool; } - address private _blabs; + address internal _blabs; constructor() { _blabs = msg.sender; diff --git a/src/contracts/BPool.sol b/src/contracts/BPool.sol index dc0f326a..33aa0e13 100644 --- a/src/contracts/BPool.sol +++ b/src/contracts/BPool.sol @@ -19,7 +19,7 @@ import './BToken.sol'; contract BPool is BBronze, BToken, BMath { struct Record { bool bound; // is token bound to pool - uint256 index; // private + uint256 index; // internal uint256 denorm; // denormalized weight uint256 balance; } @@ -55,20 +55,20 @@ contract BPool is BBronze, BToken, BMath { _; } - bool private _mutex; + bool internal _mutex; - address private _factory; // BFactory address to push token exitFee to - address private _controller; // has CONTROL role - bool private _publicSwap; // true if PUBLIC can call SWAP functions + address internal _factory; // BFactory address to push token exitFee to + address internal _controller; // has CONTROL role + bool internal _publicSwap; // true if PUBLIC can call SWAP functions // `setSwapFee` and `finalize` require CONTROL // `finalize` sets `PUBLIC can SWAP`, `PUBLIC can JOIN` - uint256 private _swapFee; - bool private _finalized; + uint256 internal _swapFee; + bool internal _finalized; - address[] private _tokens; - mapping(address => Record) private _records; - uint256 private _totalWeight; + address[] internal _tokens; + mapping(address => Record) internal _records; + uint256 internal _totalWeight; constructor() { _controller = msg.sender; diff --git a/src/contracts/BToken.sol b/src/contracts/BToken.sol index 11e0a808..7fcdd028 100644 --- a/src/contracts/BToken.sol +++ b/src/contracts/BToken.sol @@ -65,9 +65,9 @@ abstract contract BTokenBase is BNum, IERC20 { } contract BToken is BTokenBase { - string private _name = 'Balancer Pool Token'; - string private _symbol = 'BPT'; - uint8 private _decimals = 18; + string internal _name = 'Balancer Pool Token'; + string internal _symbol = 'BPT'; + uint8 internal _decimals = 18; function name() public view returns (string memory) { return _name; diff --git a/src/contracts/Migrations.sol b/src/contracts/Migrations.sol deleted file mode 100644 index d55802c1..00000000 --- a/src/contracts/Migrations.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity 0.8.23; - -contract Migrations { - address public owner; - uint256 public lastCompletedMigration; - - constructor() { - owner = msg.sender; - } - - modifier restricted() { - if (msg.sender == owner) _; - } - - function setCompleted(uint256 completed) external restricted { - lastCompletedMigration = completed; - } - - function upgrade(address new_address) external restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(lastCompletedMigration); - } -} diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol index 64038c18..58293520 100644 --- a/test/unit/BPool.t.sol +++ b/test/unit/BPool.t.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; -import {BConst} from 'contracts/BConst.sol'; import {BPool} from 'contracts/BPool.sol'; +import {MockBPool} from 'test/smock/MockBPool.sol'; + +import {BConst} from 'contracts/BConst.sol'; import {IERC20} from 'contracts/BToken.sol'; import {Test} from 'forge-std/Test.sol'; import {LibString} from 'solmate/utils/LibString.sol'; @@ -18,11 +20,11 @@ abstract contract BasePoolTest is Test, BConst, Utils { uint256 internal constant _RECORD_MAPPING_SLOT_NUMBER = 10; uint256 internal constant _TOKENS_ARRAY_SLOT_NUMBER = 9; - BPool public bPool; + MockBPool public bPool; address[TOKENS_AMOUNT] public tokens; function setUp() public { - bPool = new BPool(); + bPool = new MockBPool(); // Create fake tokens for (uint256 i = 0; i < tokens.length; i++) { @@ -48,31 +50,23 @@ abstract contract BasePoolTest is Test, BConst, Utils { } function _setTokens(address[] memory _tokens) internal { - _writeArrayLengthToStorage(address(bPool), _TOKENS_ARRAY_SLOT_NUMBER, _tokens.length); // write length - for (uint256 i = 0; i < _tokens.length; i++) { - _writeAddressArrayItemToStorage(address(bPool), _TOKENS_ARRAY_SLOT_NUMBER, i, _tokens[i]); // write token - } - } - - function _setRecordBound(address _token) internal { - _writeStructPropertyAtAddressMapping(address(bPool), _RECORD_MAPPING_SLOT_NUMBER, _token, 0, 1); // bound (1 == true) + bPool.set__tokens(_tokens); } - function _setRecordBalance(address _token, uint256 _balance) internal { - _writeStructPropertyAtAddressMapping(address(bPool), _RECORD_MAPPING_SLOT_NUMBER, _token, 3, _balance); // balance + function _setRecord(address _token, BPool.Record memory _record) internal { + bPool.set__records(_token, _record); } function _setPublicSwap(bool _isPublicSwap) internal { - // TODO: make it depend on the bool value - _writeUintToStorage(address(bPool), 6, 0x0000000000000000000000010000000000000000000000000000000000000000); + bPool.set__publicSwap(_isPublicSwap); } function _setFinalize(bool _isFinalized) internal { - // TODO: make it depend on the bool value - _writeUintToStorage(address(bPool), 8, 1); + bPool.set__finalized(_isFinalized); } function _setTotalSupply(uint256 _totalSupply) internal { + // NOTE: not in smock as it uses ERC20.totalSupply() _writeUintToStorage(address(bPool), 2, _totalSupply); } } @@ -340,8 +334,15 @@ contract BPool_Unit_JoinPool is BasePoolTest { // Set balances for (uint256 i = 0; i < tokens.length; i++) { - _setRecordBound(tokens[i]); - _setRecordBalance(tokens[i], _fuzz.balance[i]); + _setRecord( + tokens[i], + BPool.Record({ + bound: true, + index: 0, // NOTE: irrelevant for this method + denorm: 0, // NOTE: irrelevant for this method + balance: _fuzz.balance[i] + }) + ); } // Set public swap diff --git a/yarn.lock b/yarn.lock index b7c9332e..8c16eda5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -183,6 +183,16 @@ solc-typed-ast "18.1.2" yargs "17.7.2" +"@defi-wonderland/smock-foundry@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@defi-wonderland/smock-foundry/-/smock-foundry-1.5.0.tgz#e0f91cb40a1805644fbe1bcec8bbb6556d72253c" + integrity sha512-GjMc8Tgg+jBzV0zp00WTSlExptthocrnE3FY58Zm4Li+jWwrphKRflGRf4F65f1t976SNIW63mleWJViFa4mRA== + dependencies: + fast-glob "3.3.2" + handlebars "4.7.7" + solc-typed-ast "18.1.3" + yargs "17.7.2" + "@noble/curves@1.3.0", "@noble/curves@~1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" @@ -371,7 +381,7 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@^1.6.7: +axios@^1.6.7, axios@^1.6.8: version "1.6.8" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== @@ -952,6 +962,18 @@ graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +handlebars@4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -1387,7 +1409,7 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.8: +minimist@^1.2.5, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -1397,6 +1419,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + npm-run-path@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" @@ -1676,6 +1703,22 @@ solc-typed-ast@18.1.2: src-location "^1.1.0" web3-eth-abi "^4.2.0" +solc-typed-ast@18.1.3: + version "18.1.3" + resolved "https://registry.yarnpkg.com/solc-typed-ast/-/solc-typed-ast-18.1.3.tgz#243cc0c5a4f701445ac10341224bf8c18a6ed252" + integrity sha512-11iBtavJJtkzrmQdlAiZ7sz3C3WOQ8MaN7+r4b9C6B/3ORqg4oTUW5/ANyGyus5ppXDXzPyT90BYCfP73df3HA== + dependencies: + axios "^1.6.8" + commander "^12.0.0" + decimal.js "^10.4.3" + findup-sync "^5.0.0" + fs-extra "^11.2.0" + jsel "^1.1.6" + semver "^7.6.0" + solc "0.8.25" + src-location "^1.1.0" + web3-eth-abi "^4.2.0" + solc@0.8.24: version "0.8.24" resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.24.tgz#6e5693d28208d00a20ff2bdabc1dec85a5329bbb" @@ -1689,6 +1732,19 @@ solc@0.8.24: semver "^5.5.0" tmp "0.0.33" +solc@0.8.25: + version "0.8.25" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.25.tgz#393f3101617388fb4ba2a58c5b03ab02678e375c" + integrity sha512-7P0TF8gPeudl1Ko3RGkyY6XVCxe2SdD/qQhtns1vl3yAbK/PDifKDLHGtx1t7mX3LgR7ojV7Fg/Kc6Q9D2T8UQ== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + "solhint@github:solhint-community/solhint-community#v4.0.0-rc01": version "4.0.0-rc01" resolved "https://codeload.github.com/solhint-community/solhint-community/tar.gz/b04088d3d2bb6ceb8c9bf77783e71195d19fb653" @@ -1736,6 +1792,11 @@ sort-package-json@2.10.0: semver "^7.6.0" sort-object-keys "^1.1.3" +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + split2@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" @@ -1858,6 +1919,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + unicorn-magic@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" @@ -1956,6 +2022,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"