Skip to content

Commit

Permalink
feat: add btt tests for finalize methods (#159)
Browse files Browse the repository at this point in the history
* test: btt tests for bpool.swapExactAmountIn

* chore: delete preexisting unit tests

* test: small renames from feedback

* test: be explicit about untestable code

* test: adding skipped test for unreachable condition

* test: code wasnt so unreachable after all

* refactor: get rid of _setRecord

* test: btt tests for bcowpool.verify

* chore: delete preexisting unit tests

* chore: testcase renaming from review

* chore: get rid of _setTokens altogether

* test: fuzz all possible valid order.sellAmount values

* chore: rename correctOrder -> validOrder

* test: btt tests for bpool.finalize

* test: btt tests for bcowpool.finalize

* chore: remove preexisting unit tests replaced by ones in this pr

* fix: feedback from review

* refactor: make caller==controller default scenario

* fix: reorganize .tree

* fix: feedback from review

* fix: feedback from review, calling internal method directly
  • Loading branch information
0xteddybear authored Jul 22, 2024
1 parent 6c13de8 commit 2a0b429
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 110 deletions.
39 changes: 39 additions & 0 deletions test/manual-smock/MockBCoWPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,45 @@ contract MockBCoWPool is BCoWPool, Test {
vm.expectCall(address(this), abi.encodeWithSignature('_pushUnderlying(address,address,uint256)', token, to, amount));
}

function mock_call__pushPoolShare(address to, uint256 amount) public {
vm.mockCall(address(this), abi.encodeWithSignature('_pushPoolShare(address,uint256)', to, amount), abi.encode());
}

function _pushPoolShare(address to, uint256 amount) internal override {
(bool _success, bytes memory _data) =
address(this).call(abi.encodeWithSignature('_pushPoolShare(address,uint256)', to, amount));

if (_success) return abi.decode(_data, ());
else return super._pushPoolShare(to, amount);
}

function call__pushPoolShare(address to, uint256 amount) public {
return _pushPoolShare(to, amount);
}

function expectCall__pushPoolShare(address to, uint256 amount) public {
vm.expectCall(address(this), abi.encodeWithSignature('_pushPoolShare(address,uint256)', to, amount));
}

function mock_call__mintPoolShare(uint256 amount) public {
vm.mockCall(address(this), abi.encodeWithSignature('_mintPoolShare(uint256)', amount), abi.encode());
}

function _mintPoolShare(uint256 amount) internal override {
(bool _success, bytes memory _data) = address(this).call(abi.encodeWithSignature('_mintPoolShare(uint256)', amount));

if (_success) return abi.decode(_data, ());
else return super._mintPoolShare(amount);
}

function call__mintPoolShare(uint256 amount) public {
return _mintPoolShare(amount);
}

function expectCall__mintPoolShare(uint256 amount) public {
vm.expectCall(address(this), abi.encodeWithSignature('_mintPoolShare(uint256)', amount));
}

function call__afterFinalize() public {
return _afterFinalize();
}
Expand Down
35 changes: 0 additions & 35 deletions test/unit/BCoWPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,41 +78,6 @@ contract BCoWPool_Unit_Constructor is BaseCoWPoolTest {
}
}

contract BCoWPool_Unit_Finalize is BaseCoWPoolTest {
function setUp() public virtual override {
super.setUp();

for (uint256 i = 0; i < TOKENS_AMOUNT; i++) {
vm.mockCall(tokens[i], abi.encodePacked(IERC20.approve.selector), abi.encode(true));
}

vm.mockCall(address(bCoWPool.FACTORY()), abi.encodeWithSelector(IBCoWFactory.logBCoWPool.selector), abi.encode());
}

function test_Set_Approvals() public {
for (uint256 i = 0; i < TOKENS_AMOUNT; i++) {
vm.expectCall(tokens[i], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)), 1);
}
bCoWPool.finalize();
}

function test_Log_IfRevert() public {
vm.mockCallRevert(
address(bCoWPool.FACTORY()), abi.encodeWithSelector(IBCoWFactory.logBCoWPool.selector), abi.encode()
);

vm.expectEmit(address(bCoWPool));
emit IBCoWFactory.COWAMMPoolCreated(address(bCoWPool));

bCoWPool.finalize();
}

function test_Call_LogBCoWPool() public {
vm.expectCall(address(bCoWPool.FACTORY()), abi.encodeWithSelector(IBCoWFactory.logBCoWPool.selector), 1);
bCoWPool.finalize();
}
}

contract BCoWPool_Unit_Commit is BaseCoWPoolTest {
function test_Revert_NonSolutionSettler(address sender, bytes32 orderHash) public {
vm.assume(sender != cowSolutionSettler);
Expand Down
47 changes: 47 additions & 0 deletions test/unit/BCoWPool/BCoWPool.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';

import {BCoWPoolBase} from './BCoWPoolBase.sol';

import {IBCoWFactory} from 'interfaces/IBCoWFactory.sol';
import {IBPool} from 'interfaces/IBPool.sol';

contract BCoWPool_afterFinalize is BCoWPoolBase {
uint256 public tokenWeight = 1e18;

function setUp() public virtual override {
super.setUp();
bCoWPool.set__tokens(tokens);
bCoWPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
bCoWPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: tokenWeight}));

vm.mockCall(address(this), abi.encodeCall(IBCoWFactory.logBCoWPool, ()), abi.encode());

vm.mockCall(tokens[0], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)), abi.encode(true));
vm.mockCall(tokens[1], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)), abi.encode(true));
}

function test_WhenCalled() external {
// it calls approve on every bound token
vm.expectCall(tokens[0], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)));
vm.expectCall(tokens[1], abi.encodeCall(IERC20.approve, (vaultRelayer, type(uint256).max)));
// it calls logBCoWPool on the factory
vm.expectCall(address(this), abi.encodeCall(IBCoWFactory.logBCoWPool, ()));
bCoWPool.call__afterFinalize();
}

function test_WhenFactorysLogBCoWPoolDoesNotRevert() external {
// it returns
bCoWPool.call__afterFinalize();
}

function test_WhenFactorysLogBCoWPoolReverts(bytes memory revertData) external {
vm.mockCallRevert(address(this), abi.encodeCall(IBCoWFactory.logBCoWPool, ()), revertData);
// it emits a COWAMMPoolCreated event
vm.expectEmit(address(bCoWPool));
emit IBCoWFactory.COWAMMPoolCreated(address(bCoWPool));
bCoWPool.call__afterFinalize();
}
}
8 changes: 8 additions & 0 deletions test/unit/BCoWPool/BCoWPool.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
BCoWPool::_afterFinalize
├── when called
│ ├── it calls approve on every bound token
│ └── it calls logBCoWPool on the factory
├── when factorys logBCoWPool does not revert
│ └── it returns
└── when factorys logBCoWPool reverts
└── it emits a COWAMMPoolCreated event
28 changes: 28 additions & 0 deletions test/unit/BCoWPool/BCoWPoolBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {BPoolBase} from '../BPool/BPoolBase.sol';
import {BCoWConst} from 'contracts/BCoWConst.sol';
import {BNum} from 'contracts/BNum.sol';

import {ISettlement} from 'interfaces/ISettlement.sol';
import {MockBCoWPool} from 'test/manual-smock/MockBCoWPool.sol';

contract BCoWPoolBase is BPoolBase, BCoWConst, BNum {
bytes32 public appData = bytes32('appData');
address public cowSolutionSettler = makeAddr('cowSolutionSettler');
bytes32 public domainSeparator = bytes32(bytes2(0xf00b));
address public vaultRelayer = makeAddr('vaultRelayer');
address public tokenIn;
address public tokenOut;
MockBCoWPool bCoWPool;

function setUp() public virtual override {
super.setUp();
tokenIn = tokens[0];
tokenOut = tokens[1];
vm.mockCall(cowSolutionSettler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(domainSeparator));
vm.mockCall(cowSolutionSettler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(vaultRelayer));
bCoWPool = new MockBCoWPool(cowSolutionSettler, appData);
}
}
49 changes: 49 additions & 0 deletions test/unit/BPool/BPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {IBPool} from 'interfaces/IBPool.sol';
import {MockBPool} from 'test/smock/MockBPool.sol';

contract BPool is BPoolBase {
uint256 public tokenWeight = 1e18;

function test_ConstructorWhenCalled(address _deployer) external {
vm.prank(_deployer);
MockBPool _newBPool = new MockBPool();
Expand Down Expand Up @@ -50,4 +52,51 @@ contract BPool is BPoolBase {
// it returns number of tokens
assertEq(bPool.getNumTokens(), _tokensToAdd);
}

function test_FinalizeRevertWhen_CallerIsNotController(address _caller) external {
vm.assume(_caller != address(this));
vm.prank(_caller);
// it should revert
vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
bPool.finalize();
}

function test_FinalizeRevertWhen_PoolIsFinalized() external {
bPool.set__finalized(true);
// it should revert
vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector);
bPool.finalize();
}

function test_FinalizeRevertWhen_ThereAreTooFewTokensBound() external {
address[] memory tokens_ = new address[](1);
tokens_[0] = tokens[0];
bPool.set__tokens(tokens_);
// it should revert
vm.expectRevert(IBPool.BPool_TokensBelowMinimum.selector);
bPool.finalize();
}

function test_FinalizeWhenPreconditionsAreMet() external {
bPool.set__tokens(tokens);
bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: tokenWeight}));
bPool.mock_call__mintPoolShare(INIT_POOL_SUPPLY);
bPool.mock_call__pushPoolShare(address(this), INIT_POOL_SUPPLY);

// it calls _afterFinalize hook
bPool.expectCall__afterFinalize();
// it mints initial pool shares
bPool.expectCall__mintPoolShare(INIT_POOL_SUPPLY);
// it sends initial pool shares to controller
bPool.expectCall__pushPoolShare(address(this), INIT_POOL_SUPPLY);
// it emits a LOG_CALL event
bytes memory data = abi.encodeCall(IBPool.finalize, ());
vm.expectEmit(address(bPool));
emit IBPool.LOG_CALL(IBPool.finalize.selector, address(this), data);

bPool.finalize();
// it finalizes the pool
assertEq(bPool.call__finalized(), true);
}
}
14 changes: 14 additions & 0 deletions test/unit/BPool/BPool.tree
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,17 @@ BPool::isBound
BPool::getNumTokens
└── when called
└── it returns number of tokens

BPool::finalize
├── when caller is not controller
│ └── it should revert
├── when pool is finalized
│ └── it should revert
├── when there are too few tokens bound
│ └── it should revert
└── when preconditions are met
├── it emits LOG_CALL event
├── it finalizes the pool
├── it mints initial pool shares
├── it sends initial pool shares to controller
└── it calls _afterFinalize hook
2 changes: 0 additions & 2 deletions test/unit/BPool/BPoolBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import {Utils} from 'test/utils/Utils.sol';

contract BPoolBase is Test, BConst, Utils {
MockBPool public bPool;
address public deployer = makeAddr('deployer');

function setUp() public virtual {
vm.prank(deployer);
bPool = new MockBPool();
tokens.push(makeAddr('token0'));
tokens.push(makeAddr('token1'));
Expand Down
27 changes: 11 additions & 16 deletions test/unit/BPool/BPool_Bind.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,76 +26,71 @@ contract BPoolBind is BPoolBase {

function test_RevertWhen_CallerIsNOTController(address _caller) external {
// it should revert
vm.assume(_caller != deployer);
vm.assume(_caller != address(this));
vm.prank(_caller);
vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
bPool.bind(tokens[0], tokenBindBalance, tokenWeight);
}

modifier whenCallerIsController() {
vm.startPrank(deployer);
_;
}

function test_RevertWhen_TokenIsAlreadyBound() external whenCallerIsController {
function test_RevertWhen_TokenIsAlreadyBound() external {
bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
// it should revert
vm.expectRevert(IBPool.BPool_TokenAlreadyBound.selector);
bPool.bind(tokens[0], tokenBindBalance, tokenWeight);
}

function test_RevertWhen_PoolIsFinalized() external whenCallerIsController {
function test_RevertWhen_PoolIsFinalized() external {
bPool.set__finalized(true);
// it should revert
vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector);
bPool.bind(tokens[0], tokenBindBalance, tokenWeight);
}

function test_RevertWhen_MAX_BOUND_TOKENSTokensAreAlreadyBound() external whenCallerIsController {
function test_RevertWhen_MAX_BOUND_TOKENSTokensAreAlreadyBound() external {
_setRandomTokens(MAX_BOUND_TOKENS);
// it should revert
vm.expectRevert(IBPool.BPool_TokensAboveMaximum.selector);
bPool.bind(tokens[0], tokenBindBalance, tokenWeight);
}

function test_RevertWhen_TokenWeightIsTooLow() external whenCallerIsController {
function test_RevertWhen_TokenWeightIsTooLow() external {
// it should revert
vm.expectRevert(IBPool.BPool_WeightBelowMinimum.selector);
bPool.bind(tokens[0], tokenBindBalance, MIN_WEIGHT - 1);
}

function test_RevertWhen_TokenWeightIsTooHigh() external whenCallerIsController {
function test_RevertWhen_TokenWeightIsTooHigh() external {
// it should revert
vm.expectRevert(IBPool.BPool_WeightAboveMaximum.selector);
bPool.bind(tokens[0], tokenBindBalance, MAX_WEIGHT + 1);
}

function test_RevertWhen_TooLittleBalanceIsProvided() external whenCallerIsController {
function test_RevertWhen_TooLittleBalanceIsProvided() external {
// it should revert
vm.expectRevert(IBPool.BPool_BalanceBelowMinimum.selector);
bPool.bind(tokens[0], MIN_BALANCE - 1, tokenWeight);
}

function test_RevertWhen_WeightSumExceedsMAX_TOTAL_WEIGHT() external whenCallerIsController {
function test_RevertWhen_WeightSumExceedsMAX_TOTAL_WEIGHT() external {
bPool.set__totalWeight(2 * MAX_TOTAL_WEIGHT / 3);
// it should revert
vm.expectRevert(IBPool.BPool_TotalWeightAboveMaximum.selector);
bPool.bind(tokens[0], tokenBindBalance, MAX_TOTAL_WEIGHT / 2);
}

function test_WhenTokenCanBeBound(uint256 _existingTokens) external whenCallerIsController {
function test_WhenTokenCanBeBound(uint256 _existingTokens) external {
_existingTokens = bound(_existingTokens, 0, MAX_BOUND_TOKENS - 1);
bPool.set__tokens(_getDeterministicTokenArray(_existingTokens));

bPool.set__totalWeight(totalWeight);
// it calls _pullUnderlying
bPool.expectCall__pullUnderlying(tokens[0], deployer, tokenBindBalance);
bPool.expectCall__pullUnderlying(tokens[0], address(this), tokenBindBalance);
// it sets the reentrancy lock
bPool.expectCall__setLock(_MUTEX_TAKEN);
// it emits LOG_CALL event
vm.expectEmit();
bytes memory _data = abi.encodeWithSelector(IBPool.bind.selector, tokens[0], tokenBindBalance, tokenWeight);
emit IBPool.LOG_CALL(IBPool.bind.selector, deployer, _data);
emit IBPool.LOG_CALL(IBPool.bind.selector, address(this), _data);

bPool.bind(tokens[0], tokenBindBalance, tokenWeight);

Expand Down
Loading

0 comments on commit 2a0b429

Please sign in to comment.