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 btt test for joinswap extern amount in #164

Merged
merged 4 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
180 changes: 0 additions & 180 deletions test/unit/BPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -819,186 +819,6 @@ contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest {
}
}

contract BPool_Unit_JoinswapExternAmountIn is BasePoolTest {
address tokenIn;

struct JoinswapExternAmountIn_FuzzScenario {
uint256 tokenAmountIn;
uint256 tokenInBalance;
uint256 tokenInDenorm;
uint256 totalSupply;
uint256 totalWeight;
uint256 swapFee;
}

function _setValues(JoinswapExternAmountIn_FuzzScenario memory _fuzz) internal {
tokenIn = tokens[0];

// Create mocks for tokenIn
_mockTransferFrom(tokenIn);

// Set balances
_setRecord(
tokenIn,
IBPool.Record({
bound: true,
index: 0, // NOTE: irrelevant for this method
denorm: _fuzz.tokenInDenorm
})
);
_mockPoolBalance(tokenIn, _fuzz.tokenInBalance);

// Set swapFee
_setSwapFee(_fuzz.swapFee);
// Set finalize
_setFinalize(true);
// Set totalSupply
_setTotalSupply(_fuzz.totalSupply);
// Set totalWeight
_setTotalWeight(_fuzz.totalWeight);
}

function _assumeHappyPath(JoinswapExternAmountIn_FuzzScenario memory _fuzz) internal pure {
// safe bound assumptions
_fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT);
_fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE);
_fuzz.totalWeight = bound(_fuzz.totalWeight, MIN_WEIGHT * TOKENS_AMOUNT, MAX_TOTAL_WEIGHT);

_fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max);
_fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max);

// max
vm.assume(_fuzz.tokenInBalance < type(uint256).max - _fuzz.tokenAmountIn);

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

// internal calculation for calcPoolOutGivenSingleIn
_assumeCalcPoolOutGivenSingleIn(
_fuzz.tokenInDenorm,
_fuzz.tokenInBalance,
_fuzz.tokenAmountIn,
_fuzz.swapFee,
_fuzz.totalWeight,
_fuzz.totalSupply
);
}

modifier happyPath(JoinswapExternAmountIn_FuzzScenario memory _fuzz) {
_assumeHappyPath(_fuzz);
_setValues(_fuzz);
_;
}

function test_Revert_NotFinalized(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
_setFinalize(false);

vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);
}

function test_Revert_NotBound(
JoinswapExternAmountIn_FuzzScenario memory _fuzz,
address _tokenIn
) public happyPath(_fuzz) {
assumeNotForgeAddress(_tokenIn);

vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
bPool.joinswapExternAmountIn(_tokenIn, _fuzz.tokenAmountIn, 0);
}

function test_Revert_TokenAmountInAboveMaxIn(JoinswapExternAmountIn_FuzzScenario memory _fuzz)
public
happyPath(_fuzz)
{
uint256 _tokenAmountIn = bmul(_fuzz.tokenInBalance, MAX_IN_RATIO);

vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector);
bPool.joinswapExternAmountIn(tokenIn, _tokenAmountIn + 1, 0);
}

function test_Revert_PoolAmountOutBelowMinPoolAmountOut(
JoinswapExternAmountIn_FuzzScenario memory _fuzz,
uint256 _minPoolAmountOut
) public happyPath(_fuzz) {
uint256 _poolAmountIn = calcPoolOutGivenSingleIn(
_fuzz.tokenInBalance,
_fuzz.tokenInDenorm,
_fuzz.totalSupply,
_fuzz.totalWeight,
_fuzz.tokenAmountIn,
_fuzz.swapFee
);
_minPoolAmountOut = bound(_minPoolAmountOut, _poolAmountIn + 1, type(uint256).max);

vm.expectRevert(IBPool.BPool_PoolAmountOutBelowMinPoolAmountOut.selector);
bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, _minPoolAmountOut);
}

function test_Revert_Reentrancy(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public {
_expectRevertByReentrancy();
bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);
}

function test_Emit_LogJoin(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
vm.expectEmit();
emit IBPool.LOG_JOIN(address(this), tokenIn, _fuzz.tokenAmountIn);

bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);
}

function test_Set_ReentrancyLock(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
_expectSetReentrancyLock();
bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);
}

function test_Mint_PoolShare(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
(uint256 _poolAmountOut) = bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);

assertEq(bPool.totalSupply(), _fuzz.totalSupply + _poolAmountOut);
}

function test_Push_PoolShare(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
uint256 _balanceBefore = bPool.balanceOf(address(this));

(uint256 _poolAmountOut) = bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);

assertEq(bPool.balanceOf(address(this)), _balanceBefore + _poolAmountOut);
}

function test_Pull_Underlying(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
vm.expectCall(
address(tokenIn),
abi.encodeWithSelector(IERC20.transferFrom.selector, address(this), address(bPool), _fuzz.tokenAmountIn)
);
bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);
}

function test_Returns_PoolAmountOut(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
uint256 _expectedPoolAmountOut = calcPoolOutGivenSingleIn(
_fuzz.tokenInBalance,
_fuzz.tokenInDenorm,
_fuzz.totalSupply,
_fuzz.totalWeight,
_fuzz.tokenAmountIn,
_fuzz.swapFee
);

(uint256 _poolAmountOut) = bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);

assertEq(_poolAmountOut, _expectedPoolAmountOut);
}

function test_Emit_LogCall(JoinswapExternAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
vm.expectEmit();
bytes memory _data = abi.encodeWithSelector(BPool.joinswapExternAmountIn.selector, tokenIn, _fuzz.tokenAmountIn, 0);
emit IBPool.LOG_CALL(BPool.joinswapExternAmountIn.selector, address(this), _data);

bPool.joinswapExternAmountIn(tokenIn, _fuzz.tokenAmountIn, 0);
}
}

contract BPool_Unit_JoinswapPoolAmountOut is BasePoolTest {
address tokenIn;

Expand Down
3 changes: 0 additions & 3 deletions test/unit/BPool/BPoolBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ contract BPoolBase is Test, BConst, Utils {
MockBPool public bPool;
address public deployer = makeAddr('deployer');

uint256 public tokenWeight = 1e18;
uint256 public totalWeight = 10e18;

function setUp() public virtual {
vm.prank(deployer);
bPool = new MockBPool();
Expand Down
2 changes: 2 additions & 0 deletions test/unit/BPool/BPool_Bind.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {IBPool} from 'interfaces/IBPool.sol';

contract BPoolBind is BPoolBase {
uint256 public tokenBindBalance = 100e18;
uint256 public tokenWeight = 1e18;
uint256 public totalWeight = 10e18;

function setUp() public virtual override {
super.setUp();
Expand Down
95 changes: 95 additions & 0 deletions test/unit/BPool/BPool_JoinswapExternAmountIn.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {BPoolBase} from './BPoolBase.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';

import {BNum} from 'contracts/BNum.sol';
import {IBPool} from 'interfaces/IBPool.sol';

contract BPoolJoinswapExternAmountIn is BPoolBase, BNum {
address public tokenIn;

// Valid scenario
uint256 public tokenAmountIn = 1e18;
uint256 public tokenInWeight = 2e18;
uint256 public totalWeight = 10e18;
uint256 public tokenInBalance = 50e18;

// (((tokenAmountIn*(1-(1-tokenInWeight/totalWeight)*MIN_FEE)+tokenInBalance)/tokenInBalance)^(tokenInWeight/totalWeight))*INIT_POOL_SUPPLY - INIT_POOL_SUPPLY
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we reduce this formula to what's described in BMath? like, using wi or wT instead of full variable names, perhaps we can do this accross all tests

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could, but for that we could reference bmath directly. In these comments I explicitly implement the formulae as defined by bmath in a notation that's easily executable, using the same variable names that'll be found in the code, as both a sanity check and the way to independently compute them (this one got me searching for alternatives tobc tho, because of the non-integer exponent)

// (((1*(1-(1-2/10)*(10^-6))+50)/50)^(2/10))*100 - 100
// 0.396837555601045600
uint256 public expectedPoolOut = 0.3968375556010456e18;

function setUp() public virtual override {
super.setUp();
tokenIn = tokens[0];
bPool.set__finalized(true);
// mint an initial amount of pool shares (expected to happen at _finalize)
bPool.call__mintPoolShare(INIT_POOL_SUPPLY);
bPool.set__tokens(_tokensToMemory());
bPool.set__totalWeight(totalWeight);
_setRecord(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight}));
vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenInBalance)));
}

function test_RevertWhen_ReentrancyLockIsSet() external {
bPool.call__setLock(_MUTEX_TAKEN);
// it should revert
vm.expectRevert(IBPool.BPool_Reentrancy.selector);
bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut);
}

function test_RevertWhen_PoolIsNotFinalized() external {
bPool.set__finalized(false);
// it should revert
vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut);
}

function test_RevertWhen_TokenIsNotBound() external {
// it should revert
vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
bPool.joinswapExternAmountIn(makeAddr('unknown token'), tokenAmountIn, expectedPoolOut);
}

function test_RevertWhen_TokenAmountInExceedsMaxRatio(uint256 amountIn) external {
amountIn = bound(amountIn, bdiv(INIT_POOL_SUPPLY, MAX_IN_RATIO) + 1, type(uint256).max);
// it should revert
vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector);
bPool.joinswapExternAmountIn(tokenIn, amountIn, expectedPoolOut);
}

function test_RevertWhen_CalculatedPoolAmountOutIsLessThanExpected(uint256 expectedPoolOut_) external {
expectedPoolOut_ = bound(expectedPoolOut_, expectedPoolOut + 1, type(uint256).max);
// it should revert
vm.expectRevert(IBPool.BPool_PoolAmountOutBelowMinPoolAmountOut.selector);
bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut_);
}

function test_WhenPreconditionsAreMet() external {
// it sets reentrancy lock
bPool.expectCall__setLock(_MUTEX_TAKEN);
// it queries the contracts token in balance
vm.expectCall(tokenIn, abi.encodeCall(IERC20.balanceOf, (address(bPool))));
// it calls _pullUnderlying for token
bPool.mock_call__pullUnderlying(tokenIn, address(this), tokenAmountIn);
bPool.expectCall__pullUnderlying(tokenIn, address(this), tokenAmountIn);
// it mints the pool shares
bPool.expectCall__mintPoolShare(expectedPoolOut);
// it sends pool shares to caller
bPool.expectCall__pushPoolShare(address(this), expectedPoolOut);
// it emits LOG_CALL event
bytes memory _data =
abi.encodeWithSelector(IBPool.joinswapExternAmountIn.selector, tokenIn, tokenAmountIn, expectedPoolOut);
vm.expectEmit();
emit IBPool.LOG_CALL(IBPool.joinswapExternAmountIn.selector, address(this), _data);
// it emits LOG_JOIN event for token
vm.expectEmit();
emit IBPool.LOG_JOIN(address(this), tokenIn, tokenAmountIn);
bPool.joinswapExternAmountIn(tokenIn, tokenAmountIn, expectedPoolOut);

// it clears the reentrancy lock
assertEq(_MUTEX_FREE, bPool.call__getLock());
}
}
20 changes: 20 additions & 0 deletions test/unit/BPool/BPool_JoinswapExternAmountIn.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
BPool::JoinswapExternAmountIn
├── when reentrancy lock is set
│ └── it should revert
├── when pool is not finalized
│ └── it should revert
├── when token is not bound
│ └── it should revert
├── when token amount in exceeds max ratio
│ └── it should revert
├── when calculated pool amount out is less than expected
│ └── it should revert
└── when preconditions are met
├── it emits LOG_CALL event
├── it sets the reentrancy lock
├── it queries the contracts token in balance
├── it emits LOG_JOIN event for token
├── it calls _pullUnderlying for token
├── it mints the pool shares
├── it sends pool shares to caller
└── it clears the reentrancy lock
2 changes: 2 additions & 0 deletions test/unit/BPool/BPool_Unbind.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {IBPool} from 'interfaces/IBPool.sol';

contract BPoolUnbind is BPoolBase {
uint256 public boundTokenAmount = 100e18;
uint256 public tokenWeight = 1e18;
uint256 public totalWeight = 10e18;

function setUp() public virtual override {
super.setUp();
Expand Down
Loading