From 1a87b424ab9dfff5dfb49d304cc7fcf25d100a3e Mon Sep 17 00:00:00 2001 From: Austrian <114922365+0xAustrian@users.noreply.github.com> Date: Thu, 9 May 2024 09:40:11 -0300 Subject: [PATCH] test: bPool unit tests scaffolding (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: wip initial test structure * test: complete scaffolding * test: add happy path for joinPool * test: wip * test: tweak assume * test: wip, improve fuzzing * test: improve assume * test: assumes are working now * test: increase tokens to 8 * test: finish happyPath for joinPool * fix: lint * test: improve happyPath * fix: avoid empty tests from running * test: small improvements * fix: small compilation error * test: create Utils library * fix: moving happy path to specific test contract (#11) * fix: moving happy path to specific test contract * fix: linter errors * 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 --------- Co-authored-by: Weißer Hase Co-authored-by: Weißer Hase --- .github/workflows/ci.yml | 6 + .gitignore | 3 + .solhint.json | 5 +- foundry.toml | 1 + package.json | 4 +- 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 | 616 +++++++++++++++++++++++++++++++++++ test/unit/Utils.sol | 64 ++++ yarn.lock | 81 ++++- 12 files changed, 788 insertions(+), 45 deletions(-) delete mode 100644 src/contracts/Migrations.sol create mode 100644 test/unit/BPool.t.sol create mode 100644 test/unit/Utils.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/.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/foundry.toml b/foundry.toml index 1e9d2ee4..26cc6e77 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,6 +26,7 @@ src = 'src/interfaces/' [fuzz] runs = 1000 +max_test_rejects = 500000 [rpc_endpoints] mainnet = "${MAINNET_RPC}" diff --git a/package.json b/package.json index 9111d0e1..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", @@ -36,12 +37,13 @@ "package.json": "sort-package-json" }, "dependencies": { - "solmate": "github:transmissions11/solmate#a9e3ea2" + "solmate": "github:transmissions11/solmate#c892309" }, "devDependencies": { "@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 new file mode 100644 index 00000000..58293520 --- /dev/null +++ b/test/unit/BPool.t.sol @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +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'; +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, Utils { + using LibString for *; + + uint256 public constant TOKENS_AMOUNT = 3; + uint256 internal constant _RECORD_MAPPING_SLOT_NUMBER = 10; + uint256 internal constant _TOKENS_ARRAY_SLOT_NUMBER = 9; + + MockBPool public bPool; + address[TOKENS_AMOUNT] public tokens; + + function setUp() public { + bPool = new MockBPool(); + + // Create fake tokens + for (uint256 i = 0; i < tokens.length; i++) { + tokens[i] = makeAddr(i.toString()); + } + } + + function _tokensToMemory() internal view returns (address[] memory _tokens) { + _tokens = new address[](tokens.length); + for (uint256 i = 0; i < tokens.length; i++) { + _tokens[i] = tokens[i]; + } + } + + 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)); + } + + 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 { + bPool.set__tokens(_tokens); + } + + function _setRecord(address _token, BPool.Record memory _record) internal { + bPool.set__records(_token, _record); + } + + function _setPublicSwap(bool _isPublicSwap) internal { + bPool.set__publicSwap(_isPublicSwap); + } + + function _setFinalize(bool _isFinalized) internal { + bPool.set__finalized(_isFinalized); + } + + function _setTotalSupply(uint256 _totalSupply) internal { + // NOTE: not in smock as it uses ERC20.totalSupply() + _writeUintToStorage(address(bPool), 2, _totalSupply); + } +} + +contract BPool_Unit_Constructor is BasePoolTest { + function test_Deploy() private view {} +} + +contract BPool_Unit_IsPublicSwap is BasePoolTest { + function test_Returns_IsPublicSwap() private view {} +} + +contract BPool_Unit_IsFinalized is BasePoolTest { + function test_Returns_IsFinalized() private view {} +} + +contract BPool_Unit_IsBound is BasePoolTest { + function test_Returns_IsBound() private view {} + + function test_Returns_IsNotBound() private view {} +} + +contract BPool_Unit_GetNumTokens is BasePoolTest { + function test_Returns_NumTokens() private view {} +} + +contract BPool_Unit_GetCurrentTokens is BasePoolTest { + function test_Returns_CurrentTokens() private view {} + + function test_Revert_Reentrancy() private view {} +} + +contract BPool_Unit_GetFinalTokens is BasePoolTest { + function test_Returns_FinalTokens() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Revert_NotFinalized() private view {} +} + +contract BPool_Unit_GetDenormalizedWeight is BasePoolTest { + function test_Returns_DenormalizedWeight() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Revert_NotBound() private view {} +} + +contract BPool_Unit_GetTotalDenormalizedWeight is BasePoolTest { + function test_Returns_TotalDenormalizedWeight() private view {} + + function test_Revert_Reentrancy() private view {} +} + +contract BPool_Unit_GetNormalizedWeight is BasePoolTest { + function test_Returns_NormalizedWeight() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Revert_NotBound() private view {} +} + +contract BPool_Unit_GetBalance is BasePoolTest { + function test_Returns_Balance() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Revert_NotBound() private view {} +} + +contract BPool_Unit_GetSwapFee is BasePoolTest { + function test_Returns_SwapFee() private view {} + + function test_Revert_Reentrancy() private view {} +} + +contract BPool_Unit_GetController is BasePoolTest { + function test_Returns_Controller() private view {} + + function test_Revert_Reentrancy() private view {} +} + +contract BPool_Unit_SetSwapFee is BasePoolTest { + function test_Revert_Finalized() private view {} + + function test_Revert_NotController() private view {} + + function test_Revert_MinFee() private view {} + + function test_Revert_MaxFee() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_SwapFee() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_SetController is BasePoolTest { + function test_Revert_NotController() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_Controller() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_SetPublicSwap is BasePoolTest { + function test_Revert_Finalized() private view {} + + function test_Revert_NotController() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_PublicSwap() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_Finalize is BasePoolTest { + function test_Revert_NotController() private view {} + + function test_Revert_Finalized() private view {} + + function test_Revert_MinTokens() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_Finalize() private view {} + + function test_Set_PublicSwap() private view {} + + function test_Mint_InitPoolSupply() private view {} + + function test_Push_InitPoolSupply() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_Bind is BasePoolTest { + function test_Revert_NotController() private view {} + + function test_Revert_IsBound() private view {} + + function test_Revert_Finalized() private view {} + + function test_Revert_MaxPoolTokens() private view {} + + function test_Set_Record() private view {} + + function test_Set_TokenArray() private view {} + + function test_Emit_LogCall() private view {} + + function test_Call_Rebind() private view {} +} + +contract BPool_Unit_Rebind is BasePoolTest { + function test_Revert_NotController() private view {} + + function test_Revert_NotBound() private view {} + + function test_Revert_Finalized() private view {} + + function test_Revert_MinWeight() private view {} + + function test_Revert_MaxWeight() private view {} + + function test_Revert_MinBalance() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_TotalWeightIfDenormMoreThanOldWeight() private view {} + + function test_Set_TotalWeightIfDenormLessThanOldWeight() private view {} + + function test_Revert_MaxTotalWeight() private view {} + + function test_Set_Denorm() private view {} + + function test_Set_Balance() private view {} + + function test_Pull_IfBalanceMoreThanOldBalance() private view {} + + function test_Push_UnderlyingIfBalanceLessThanOldBalance() private view {} + + function test_Push_FeeIfBalanceLessThanOldBalance() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_Unbind is BasePoolTest { + function test_Revert_NotController() private view {} + + function test_Revert_NotBound() private view {} + + function test_Revert_Finalized() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_TotalWeight() private view {} + + function test_Set_TokenArray() private view {} + + function test_Set_Index() private view {} + + function test_Unset_TokenArray() private view {} + + function test_Unset_Record() private view {} + + function test_Push_UnderlyingBalance() private view {} + + function test_Push_UnderlyingFee() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_Gulp is BasePoolTest { + function test_Revert_NotBound() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_Balance() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_GetSpotPrice is BasePoolTest { + function test_Revert_NotBoundTokenIn() private view {} + + function test_Revert_NotBoundTokenOut() private view {} + + function test_Returns_SpotPrice() private view {} + + function test_Revert_Reentrancy() private view {} +} + +contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest { + function test_Revert_NotBoundTokenIn() private view {} + + function test_Revert_NotBoundTokenOut() private view {} + + function test_Returns_SpotPrice() private view {} + + function test_Revert_Reentrancy() private view {} +} + +contract BPool_Unit_JoinPool is BasePoolTest { + 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++) { + _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 + _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; + } // Using max possible amounts + + bPool.joinPool(_fuzz.poolAmountOut, maxAmountsIn); + } + + function test_Revert_NotFinalized() private view {} + + function test_Revert_MathApprox() private view {} + + function test_Revert_TokenArrayMathApprox() private view {} + + function test_Revert_TokenArrayLimitIn() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_TokenArrayBalance() private view {} + + function test_Emit_TokenArrayLogJoin() private view {} + + function test_Pull_TokenArrayTokenAmountIn() private view {} + + function test_Mint_PoolShare() private view {} + + function test_Push_PoolShare() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_ExitPool is BasePoolTest { + function test_Revert_NotFinalized() private view {} + + function test_Revert_MathApprox() private view {} + + function test_Pull_PoolShare() private view {} + + function test_Push_PoolShare() private view {} + + function test_Burn_PoolShare() private view {} + + function test_Revert_TokenArrayMathApprox() private view {} + + function test_Revert_TokenArrayLimitOut() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_TokenArrayBalance() private view {} + + function test_Emit_TokenArrayLogExit() private view {} + + function test_Push_TokenArrayTokenAmountOut() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_SwapExactAmountIn is BasePoolTest { + function test_Revert_NotBoundTokenIn() private view {} + + function test_Revert_NotBoundTokenOut() private view {} + + function test_Revert_NotPublic() private view {} + + function test_Revert_MaxInRatio() private view {} + + function test_Revert_BadLimitPrice() private view {} + + function test_Revert_LimitOut() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_InRecord() private view {} + + function test_Set_OutRecord() private view {} + + function test_Revert_MathApprox() private view {} + + function test_Revert_LimitPrice() private view {} + + function test_Revert_MathApprox2() private view {} + + function test_Emit_LogSwap() private view {} + + function test_Pull_TokenAmountIn() private view {} + + function test_Push_TokenAmountOut() private view {} + + function test_Returns_AmountAndPrice() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_SwapExactAmountOut is BasePoolTest { + function test_Revert_NotBoundTokenIn() private view {} + + function test_Revert_NotBoundTokenOut() private view {} + + function test_Revert_NotPublic() private view {} + + function test_Revert_MaxOutRatio() private view {} + + function test_Revert_BadLimitPrice() private view {} + + function test_Revert_LimitIn() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_InRecord() private view {} + + function test_Set_OutRecord() private view {} + + function test_Revert_MathApprox() private view {} + + function test_Revert_LimitPrice() private view {} + + function test_Revert_MathApprox2() private view {} + + function test_Emit_LogSwap() private view {} + + function test_Pull_TokenAmountIn() private view {} + + function test_Push_TokenAmountOut() private view {} + + function test_Returns_AmountAndPrice() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_JoinswapExternAmountIn is BasePoolTest { + function test_Revert_NotFinalized() private view {} + + function test_Revert_NotBound() private view {} + + function test_Revert_MaxInRatio() private view {} + + function test_Revert_LimitOut() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_Balance() private view {} + + function test_Emit_LogJoin() private view {} + + function test_Mint_PoolShare() private view {} + + function test_Push_PoolShare() private view {} + + function test_Pull_Underlying() private view {} + + function test_Returns_PoolAmountOut() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_JoinswapExternAmountOut is BasePoolTest { + function test_Revert_NotFinalized() private view {} + + function test_Revert_NotBound() private view {} + + function test_Revert_MaxApprox() private view {} + + function test_Revert_LimitIn() private view {} + + function test_Revert_MaxInRatio() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_Balance() private view {} + + function test_Emit_LogJoin() private view {} + + function test_Mint_PoolShare() private view {} + + function test_Push_PoolShare() private view {} + + function test_Pull_Underlying() private view {} + + function test_Returns_TokenAmountIn() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_ExitswapPoolAmountIn is BasePoolTest { + function test_Revert_NotFinalized() private view {} + + function test_Revert_NotBound() private view {} + + function test_Revert_LimitOut() private view {} + + function test_Revert_MaxOutRatio() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_Balance() private view {} + + function test_Emit_LogExit() private view {} + + function test_Pull_PoolShare() private view {} + + function test_Burn_PoolShare() private view {} + + function test_Push_PoolShare() private view {} + + function test_Push_Underlying() private view {} + + function test_Returns_TokenAmountOut() private view {} + + function test_Emit_LogCall() private view {} +} + +contract BPool_Unit_ExitswapPoolAmountOut is BasePoolTest { + function test_Revert_NotFinalized() private view {} + + function test_Revert_NotBound() private view {} + + function test_Revert_MaxOutRatio() private view {} + + function test_Revert_MathApprox() private view {} + + function test_Revert_LimitIn() private view {} + + function test_Revert_Reentrancy() private view {} + + function test_Set_Balance() private view {} + + function test_Emit_LogExit() private view {} + + function test_Pull_PoolShare() private view {} + + function test_Burn_PoolShare() private view {} + + function test_Push_PoolShare() private view {} + + function test_Push_Underlying() private view {} + + function test_Returns_PoolAmountIn() private view {} + + function test_Emit_LogCall() private view {} +} 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); + } +} diff --git a/yarn.lock b/yarn.lock index 14642107..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" @@ -1713,9 +1769,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" @@ -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"