Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding bcowpool verify btt tests #155

Merged
merged 21 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 1 addition & 142 deletions test/unit/BCoWPool.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {BasePoolTest, SwapExactAmountInUtils} from './BPool.t.sol';
import {BasePoolTest} from './BPool.t.sol';
import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';
import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
import {IERC1271} from '@openzeppelin/contracts/interfaces/IERC1271.sol';
Expand Down Expand Up @@ -142,147 +142,6 @@ contract BCoWPool_Unit_Commit is BaseCoWPoolTest {
}
}

contract BCoWPool_Unit_Verify is BaseCoWPoolTest, SwapExactAmountInUtils {
function setUp() public virtual override(BaseCoWPoolTest, BasePoolTest) {
BaseCoWPoolTest.setUp();
}

function _assumeHappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) internal pure override {
// safe bound assumptions
_fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT);
_fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT);
// LP fee when swapping via CoW will always be zero
_fuzz.swapFee = 0;

// min - max - calcSpotPrice (spotPriceBefore)
_fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenInDenorm);
_fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenOutDenorm);

// MAX_IN_RATIO
vm.assume(_fuzz.tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO));

_assumeCalcOutGivenIn(_fuzz.tokenInBalance, _fuzz.tokenAmountIn, _fuzz.swapFee);
uint256 _tokenAmountOut = calcOutGivenIn(
_fuzz.tokenInBalance,
_fuzz.tokenInDenorm,
_fuzz.tokenOutBalance,
_fuzz.tokenOutDenorm,
_fuzz.tokenAmountIn,
_fuzz.swapFee
);
vm.assume(_tokenAmountOut > BONE);
}

modifier assumeNotBoundToken(address _token) {
for (uint256 i = 0; i < TOKENS_AMOUNT; i++) {
vm.assume(tokens[i] != _token);
}
_;
}

function test_Revert_NonBoundBuyToken(address _otherToken) public assumeNotBoundToken(_otherToken) {
GPv2Order.Data memory order = correctOrder;
order.buyToken = IERC20(_otherToken);
vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
bCoWPool.verify(order);
}

function test_Revert_NonBoundSellToken(address _otherToken) public assumeNotBoundToken(_otherToken) {
GPv2Order.Data memory order = correctOrder;
order.sellToken = IERC20(_otherToken);
vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
bCoWPool.verify(order);
}

function test_Revert_ReceiverIsNotBCoWPool(address _receiver) public {
vm.assume(_receiver != GPv2Order.RECEIVER_SAME_AS_OWNER);
GPv2Order.Data memory order = correctOrder;
order.receiver = _receiver;
vm.expectRevert(IBCoWPool.BCoWPool_ReceiverIsNotBCoWPool.selector);
bCoWPool.verify(order);
}

function test_Revert_LargeDurationOrder(uint256 _timeOffset) public {
_timeOffset = bound(_timeOffset, MAX_ORDER_DURATION + 1, type(uint32).max - block.timestamp);
GPv2Order.Data memory order = correctOrder;
order.validTo = uint32(block.timestamp + _timeOffset);
vm.expectRevert(IBCoWPool.BCoWPool_OrderValidityTooLong.selector);
bCoWPool.verify(order);
}

function test_Revert_NonZeroFee(uint256 _fee) public {
_fee = bound(_fee, 1, type(uint256).max);
GPv2Order.Data memory order = correctOrder;
order.feeAmount = _fee;
vm.expectRevert(IBCoWPool.BCoWPool_FeeMustBeZero.selector);
bCoWPool.verify(order);
}

function test_Revert_InvalidOrderKind(bytes32 _orderKind) public {
vm.assume(_orderKind != GPv2Order.KIND_SELL);
GPv2Order.Data memory order = correctOrder;
order.kind = _orderKind;
vm.expectRevert(IBCoWPool.BCoWPool_InvalidOperation.selector);
bCoWPool.verify(order);
}

function test_Revert_InvalidBuyBalanceKind(bytes32 _balanceKind) public {
vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20);
GPv2Order.Data memory order = correctOrder;
order.buyTokenBalance = _balanceKind;
vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector);
bCoWPool.verify(order);
}

function test_Revert_InvalidSellBalanceKind(bytes32 _balanceKind) public {
vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20);
GPv2Order.Data memory order = correctOrder;
order.sellTokenBalance = _balanceKind;
vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector);
bCoWPool.verify(order);
}

function test_Revert_TokenAmountInAboveMaxIn(
SwapExactAmountIn_FuzzScenario memory _fuzz,
uint256 _offset
) public happyPath(_fuzz) {
_offset = bound(_offset, 1, type(uint256).max - _fuzz.tokenInBalance);
uint256 _tokenAmountIn = bmul(_fuzz.tokenInBalance, MAX_IN_RATIO) + _offset;
GPv2Order.Data memory order = correctOrder;
order.buyAmount = _tokenAmountIn;

vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector);
bCoWPool.verify(order);
}

function test_Revert_InsufficientReturn(
SwapExactAmountIn_FuzzScenario memory _fuzz,
uint256 _offset
) public happyPath(_fuzz) {
uint256 _tokenAmountOut = calcOutGivenIn(
_fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.tokenAmountIn, 0
);
_offset = bound(_offset, 1, _tokenAmountOut);
GPv2Order.Data memory order = correctOrder;
order.buyAmount = _fuzz.tokenAmountIn;
order.sellAmount = _tokenAmountOut + _offset;

vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector);
bCoWPool.verify(order);
}

function test_Success_HappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
uint256 _tokenAmountOut = calcOutGivenIn(
_fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.tokenAmountIn, 0
);
GPv2Order.Data memory order = correctOrder;
order.buyAmount = _fuzz.tokenAmountIn;
order.sellAmount = _tokenAmountOut;

bCoWPool.verify(order);
}
}

contract BCoWPool_Unit_IsValidSignature is BaseCoWPoolTest {
function setUp() public virtual override {
super.setUp();
Expand Down
28 changes: 28 additions & 0 deletions test/unit/BCoWPool/BCoWPoolBase.t.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);
}
}
132 changes: 132 additions & 0 deletions test/unit/BCoWPool/BCoWPool_Verify.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

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

import {BCoWPoolBase} from './BCoWPoolBase.t.sol';
import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
import {IBPool} from 'interfaces/IBPool.sol';

contract BCoWPoolVerify is BCoWPoolBase {
// Valid scenario:
uint256 public tokenAmountIn = 1e18;
uint256 public tokenInBalance = 100e18;
uint256 public tokenOutBalance = 80e18;
// pool is expected to keep 4X the value of tokenIn than tokenOut
uint256 public tokenInWeight = 4e18;
uint256 public tokenOutWeight = 1e18;
// from bmath: (with fee zero) 80*(1-(100/(100+1))^(4))
uint256 public expectedAmountOut = 3.12157244137469736e18;
GPv2Order.Data validOrder;

function setUp() public virtual override {
super.setUp();
bCoWPool.set__tokens(tokens);
bCoWPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight}));
bCoWPool.set__records(tokenOut, IBPool.Record({bound: true, index: 1, denorm: tokenOutWeight}));
vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenInBalance)));
vm.mockCall(tokenOut, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenOutBalance)));

validOrder = GPv2Order.Data({
sellToken: IERC20(tokenOut),
buyToken: IERC20(tokenIn),
receiver: GPv2Order.RECEIVER_SAME_AS_OWNER,
sellAmount: expectedAmountOut,
buyAmount: tokenAmountIn,
validTo: uint32(block.timestamp + 1 minutes),
appData: appData,
feeAmount: 0,
kind: GPv2Order.KIND_SELL,
partiallyFillable: false,
sellTokenBalance: GPv2Order.BALANCE_ERC20,
buyTokenBalance: GPv2Order.BALANCE_ERC20
});
}

function test_RevertWhen_BuyTokenIsNotBound() external {
validOrder.buyToken = IERC20(makeAddr('unknown token'));
// it should revert
vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_SellTokenIsNotBound() external {
validOrder.sellToken = IERC20(makeAddr('unknown token'));
// it should revert
vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_OrderReceiverFlagIsNotSameAsOwner() external {
validOrder.receiver = makeAddr('somebodyElse');
// it should revert
vm.expectRevert(IBCoWPool.BCoWPool_ReceiverIsNotBCoWPool.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_OrderValidityIsTooLong(uint256 _timeOffset) external {
_timeOffset = bound(_timeOffset, MAX_ORDER_DURATION + 1, type(uint32).max - block.timestamp);
validOrder.validTo = uint32(block.timestamp + _timeOffset);
// it should revert
vm.expectRevert(IBCoWPool.BCoWPool_OrderValidityTooLong.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_FeeAmountIsNotZero(uint256 _fee) external {
_fee = bound(_fee, 1, type(uint256).max);
validOrder.feeAmount = _fee;
// it should revert
vm.expectRevert(IBCoWPool.BCoWPool_FeeMustBeZero.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_OrderKindIsNotKIND_SELL(bytes32 _orderKind) external {
vm.assume(_orderKind != GPv2Order.KIND_SELL);
validOrder.kind = _orderKind;
// it should revert
vm.expectRevert(IBCoWPool.BCoWPool_InvalidOperation.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_BuyTokenBalanceFlagIsNotERC20Balances(bytes32 _balanceKind) external {
vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20);
validOrder.buyTokenBalance = _balanceKind;
// it should revert
vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_SellTokenBalanceFlagIsNotERC20Balances(bytes32 _balanceKind) external {
vm.assume(_balanceKind != GPv2Order.BALANCE_ERC20);
validOrder.sellTokenBalance = _balanceKind;
// it should revert
vm.expectRevert(IBCoWPool.BCoWPool_InvalidBalanceMarker.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_OrderBuyAmountExceedsMaxRatio(uint256 _buyAmount) external {
_buyAmount = bound(_buyAmount, bmul(tokenInBalance, MAX_IN_RATIO) + 1, type(uint256).max);
validOrder.buyAmount = _buyAmount;
// it should revert
vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector);
bCoWPool.verify(validOrder);
}

function test_RevertWhen_CalculatedTokenAmountOutIsLessThanOrderSellAmount() external {
validOrder.sellAmount += 1;
// it should revert
vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector);
bCoWPool.verify(validOrder);
}

function test_WhenPreconditionsAreMet(uint256 _sellAmount) external {
_sellAmount = bound(_sellAmount, 0, validOrder.sellAmount);
validOrder.sellAmount = _sellAmount;
// it should query the balance of the buy token
vm.expectCall(tokenIn, abi.encodeCall(IERC20.balanceOf, (address(bCoWPool))));
// it should query the balance of the sell token
vm.expectCall(tokenOut, abi.encodeCall(IERC20.balanceOf, (address(bCoWPool))));
bCoWPool.verify(validOrder);
}
}
24 changes: 24 additions & 0 deletions test/unit/BCoWPool/BCoWPool_Verify.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BCoWPool::Verify
├── when buyToken is not bound
│ └── it should revert
├── when sellToken is not bound
│ └── it should revert
├── when order receiver flag is not same as owner
│ └── it should revert
├── when order validity is too long
│ └── it should revert
├── when fee amount is not zero
│ └── it should revert
├── when order kind is not KIND_SELL
│ └── it should revert
├── when buy token balance flag is not ERC20 balances
│ └── it should revert
├── when sell token balance flag is not ERC20 balances
│ └── it should revert
├── when order buy amount exceeds max ratio
│ └── it should revert
├── when calculated token amount out is less than order sell amount
│ └── it should revert
└── when preconditions are met
├── it should query the balance of the buy token
└── it should query the balance of the sell token
Loading
Loading