diff --git a/.env.example b/.env.example
index fe295b73..ed757eec 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,9 @@
MAINNET_RPC=
MAINNET_DEPLOYER_PK=
+GNOSIS_RPC=
+GNOSIS_DEPLOYER_PK=
+
SEPOLIA_RPC=
SEPOLIA_DEPLOYER_PK=
diff --git a/.forge-snapshots/exitPool.snap b/.forge-snapshots/exitPool.snap
index a39bedae..741b1af0 100644
--- a/.forge-snapshots/exitPool.snap
+++ b/.forge-snapshots/exitPool.snap
@@ -1 +1 @@
-174743
\ No newline at end of file
+174820
\ No newline at end of file
diff --git a/.forge-snapshots/joinPool.snap b/.forge-snapshots/joinPool.snap
index f586e9ee..44abd9c6 100644
--- a/.forge-snapshots/joinPool.snap
+++ b/.forge-snapshots/joinPool.snap
@@ -1 +1 @@
-138985
\ No newline at end of file
+138974
\ No newline at end of file
diff --git a/.forge-snapshots/newBCoWFactory.snap b/.forge-snapshots/newBCoWFactory.snap
new file mode 100644
index 00000000..ae0b3b91
--- /dev/null
+++ b/.forge-snapshots/newBCoWFactory.snap
@@ -0,0 +1 @@
+4200675
\ No newline at end of file
diff --git a/.forge-snapshots/newBCoWPool.snap b/.forge-snapshots/newBCoWPool.snap
new file mode 100644
index 00000000..319c0adc
--- /dev/null
+++ b/.forge-snapshots/newBCoWPool.snap
@@ -0,0 +1 @@
+3397437
\ No newline at end of file
diff --git a/.forge-snapshots/newBFactory.snap b/.forge-snapshots/newBFactory.snap
index 95e984ca..56f18daf 100644
--- a/.forge-snapshots/newBFactory.snap
+++ b/.forge-snapshots/newBFactory.snap
@@ -1 +1 @@
-4130621
\ No newline at end of file
+3442127
\ No newline at end of file
diff --git a/.forge-snapshots/newBPool.snap b/.forge-snapshots/newBPool.snap
index 0f67b99b..88d3594d 100644
--- a/.forge-snapshots/newBPool.snap
+++ b/.forge-snapshots/newBPool.snap
@@ -1 +1 @@
-3477592
\ No newline at end of file
+2841995
\ No newline at end of file
diff --git a/.forge-snapshots/settlementCoWSwap.snap b/.forge-snapshots/settlementCoWSwap.snap
index 0e09cffc..7742ce52 100644
--- a/.forge-snapshots/settlementCoWSwap.snap
+++ b/.forge-snapshots/settlementCoWSwap.snap
@@ -1 +1 @@
-215793
\ No newline at end of file
+215771
\ No newline at end of file
diff --git a/.forge-snapshots/settlementCoWSwapInverse.snap b/.forge-snapshots/settlementCoWSwapInverse.snap
index a7aae089..f28a2b44 100644
--- a/.forge-snapshots/settlementCoWSwapInverse.snap
+++ b/.forge-snapshots/settlementCoWSwapInverse.snap
@@ -1 +1 @@
-225641
\ No newline at end of file
+225619
\ No newline at end of file
diff --git a/.forge-snapshots/swapExactAmountIn.snap b/.forge-snapshots/swapExactAmountIn.snap
index 6273edbd..d800720e 100644
--- a/.forge-snapshots/swapExactAmountIn.snap
+++ b/.forge-snapshots/swapExactAmountIn.snap
@@ -1 +1 @@
-104914
\ No newline at end of file
+104964
\ No newline at end of file
diff --git a/.forge-snapshots/swapExactAmountInInverse.snap b/.forge-snapshots/swapExactAmountInInverse.snap
index e2f5743c..6ad7203f 100644
--- a/.forge-snapshots/swapExactAmountInInverse.snap
+++ b/.forge-snapshots/swapExactAmountInInverse.snap
@@ -1 +1 @@
-114583
\ No newline at end of file
+114787
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0b0cee1d..fb56bc60 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,20 +18,20 @@ jobs:
- uses: actions/checkout@v3
- name: Install Foundry
- uses: foundry-rs/foundry-toolchain@v1
+ uses: foundry-rs/foundry-toolchain@v1.2.0
with:
version: nightly
- name: Use Node.js
uses: actions/setup-node@v3
with:
- node-version: 18.x
+ node-version: 20.x
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1
- - name: Precompile using 0.8.14 and via-ir=false
+ - name: Precompile contracts
run: yarn build
- name: Run tests
@@ -45,20 +45,20 @@ jobs:
- uses: actions/checkout@v3
- name: Install Foundry
- uses: foundry-rs/foundry-toolchain@v1
+ uses: foundry-rs/foundry-toolchain@v1.2.0
with:
version: nightly
- name: Use Node.js
uses: actions/setup-node@v3
with:
- node-version: 18.x
+ node-version: 20.x
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1
- - name: Precompile using 0.8.14 and via-ir=false
+ - name: Precompile contracts
run: yarn build
- name: Run tests
@@ -77,14 +77,14 @@ jobs:
- uses: wagoid/commitlint-github-action@v5
- name: Install Foundry
- uses: foundry-rs/foundry-toolchain@v1
+ uses: foundry-rs/foundry-toolchain@v1.2.0
with:
version: nightly
- name: Use Node.js
uses: actions/setup-node@v3
with:
- node-version: 18.x
+ node-version: 20.x
cache: 'yarn'
- name: Install bulloak
diff --git a/README.md b/README.md
index 5a1eac83..6d3f80ac 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ yarn test # run the tests
- Deprecated `Migrations` contract (not needed)
- Added an `_afterFinalize` hook (to be called at the end of the finalize routine)
- Implemented reentrancy locks using transient storage.
+- Deprecated `joinswap` and `exitswap` methods (avoid single-token math precision issues)
## Features on BCoWPool (added via inheritance to BPool)
- Immutably stores CoW Protocol's `SolutionSettler` and `VaultRelayer` addresses at deployment
@@ -57,3 +58,17 @@ yarn test # run the tests
- The weight represents the intended distribution of value between the tokens in the pool
- Modify the pool's swap fee by calling `IBPool.setSwapFee(fee)`
- Finalize the pool by calling `IBPool.finalize()`
+
+# Deployments
+Ethereum Mainnet:
+ - BCoWFactory: [0x23fcC2166F991B8946D195de53745E1b804C91B7](https://etherscan.io/address/0x23fcC2166F991B8946D195de53745E1b804C91B7)
+ - BCoWHelper: [0x5F6e7D3ef6e9aedD21C107BF8faA610f1215C730 ](https://etherscan.io/address/0x5F6e7D3ef6e9aedD21C107BF8faA610f1215C730 )
+
+Ethereum Sepolia:
+ - BCoWFactory: [0xF3F089AF5FaAF8784B445593B3bd8A514EaA3433 ](https://sepolia.etherscan.io/address/0xF3F089AF5FaAF8784B445593B3bd8A514EaA3433 )
+ - BCoWHelper: [0x07E7E9e3f4E715Ef1434b7f865fedBCE82Dd41Ba](https://sepolia.etherscan.io/address/0x07E7E9e3f4E715Ef1434b7f865fedBCE82Dd41Ba)
+ - BCoWPool: [0x4Cc911897fFCC5553627d454533D944F1D78CBdE](https://sepolia.etherscan.io/address/0x4Cc911897fFCC5553627d454533D944F1D78CBdE)
+
+ Gnosis Mainnet:
+ - BCoWFactory: [0x7573B99BC09c11Dc0427fb9c6662bc603E008304](https://gnosisscan.io/address/0x7573B99BC09c11Dc0427fb9c6662bc603E008304)
+ - BCoWHelper: [0x85315994492E88D6faCd3B0E3585c68A4720627e](https://gnosisscan.io/address/0x85315994492E88D6faCd3B0E3585c68A4720627e)
\ No newline at end of file
diff --git a/foundry.toml b/foundry.toml
index 1adc6fc7..e14b11ab 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -38,8 +38,10 @@ max_test_rejects = 2_500_000
[rpc_endpoints]
mainnet = "${MAINNET_RPC}"
+gnosis = "${GNOSIS_RPC}"
sepolia = "${SEPOLIA_RPC}"
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}", chain = "mainnet" }
+gnosis = { key = "${ETHERSCAN_API_KEY}", chain = "gnosis" }
sepolia = { key = "${ETHERSCAN_API_KEY}", chain = "sepolia" }
diff --git a/package.json b/package.json
index 12f99f4e..873c604a 100644
--- a/package.json
+++ b/package.json
@@ -1,30 +1,32 @@
{
- "name": "balancer-core",
- "version": "0.0.7",
+ "name": "balancer-cow-amm",
+ "version": "1.4.0",
"private": true,
- "description": "Balancer Core Contracts and ABI",
- "homepage": "https://github.com/balancer-labs/balancer-core#readme",
+ "description": "Balancer CoW AMM",
+ "homepage": "https://github.com/balancer/cow-amm#readme",
"bugs": {
- "url": "https://github.com/balancer-labs/balancer-core/issues"
+ "url": "https://github.com/balancer/cow-amm/issues"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/balancer-labs/balancer-core.git"
+ "url": "git+https://github.com/balancer/cow-amm.git"
},
- "license": "GPL-3.0-only",
+ "license": "GPL-3.0-or-later",
"scripts": {
"build": "forge build",
"build:optimized": "FOUNDRY_PROFILE=optimized forge build",
- "coverage": "forge coverage --match-contract Unit",
- "deploy:bcowfactory:mainnet": "bash -c 'source .env && forge script DeployBCoWFactory -vvvvv --rpc-url $MAINNET_RPC --broadcast --chain mainnet --private-key $MAINNET_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'",
- "deploy:bcowfactory:testnet": "bash -c 'source .env && forge script DeployBCoWFactory -vvvvv --rpc-url $SEPOLIA_RPC --broadcast --chain sepolia --private-key $SEPOLIA_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'",
- "deploy:bfactory:mainnet": "bash -c 'source .env && forge script DeployBFactory -vvvvv --rpc-url $MAINNET_RPC --broadcast --chain mainnet --private-key $MAINNET_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'",
- "deploy:bfactory:testnet": "bash -c 'source .env && forge script DeployBFactory -vvvvv --rpc-url $SEPOLIA_RPC --broadcast --chain sepolia --private-key $SEPOLIA_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'",
+ "coverage": "forge coverage --match-path 'test/unit/**'",
+ "deploy:gnosis": "forge script DeployBCoWFactory -vvvvv --rpc-url $GNOSIS_RPC --broadcast --chain gnosis --private-key $GNOSIS_DEPLOYER_PK --verify",
+ "deploy:mainnet": "forge script DeployBCoWFactory -vvvvv --rpc-url $MAINNET_RPC --broadcast --chain mainnet --private-key $MAINNET_DEPLOYER_PK --verify",
+ "deploy:testnet": "forge script DeployBCoWFactory -vvvvv --rpc-url $SEPOLIA_RPC --broadcast --chain sepolia --private-key $SEPOLIA_DEPLOYER_PK --verify",
"lint:bulloak": "find test/unit -name '*.tree' | xargs bulloak check",
"lint:check": "solhint 'src/**/*.sol' 'test/**/*.sol' 'script/**/*.sol' && forge fmt --check",
"lint:fix": "solhint --fix 'src/**/*.sol' 'test/**/*.sol' 'script/**/*.sol' && sort-package-json && forge fmt",
"lint:natspec": "npx @defi-wonderland/natspec-smells --config natspec-smells.config.js",
"prepare": "husky install",
+ "script:gnosis": "forge script MainnetScript -vvvvv --rpc-url $GNOSIS_RPC --broadcast --chain gnosis --private-key $GNOSIS_DEPLOYER_PK --verify",
+ "script:mainnet": "forge script MainnetScript -vvvvv --rpc-url $MAINNET_RPC --broadcast --chain mainnet --private-key $MAINNET_DEPLOYER_PK --verify",
+ "script:testnet": "forge script TestnetScript -vvvvv --rpc-url $SEPOLIA_RPC --broadcast --chain sepolia --private-key $SEPOLIA_DEPLOYER_PK --verify",
"smock": "smock-foundry --contracts src/contracts",
"test": "yarn test:integration && yarn test:unit",
"test:echidna": "find test/invariants/fuzz -regex '.*\\.t\\.sol$' |cut -d '/' -f 4 | cut -d . -f 1 |xargs -I{} echidna test/invariants/fuzz/{}.t.sol --contract Fuzz{} --config test/invariants/fuzz/{}.yaml",
@@ -44,6 +46,7 @@
"@crytic/properties": "https://github.com/crytic/properties.git",
"@openzeppelin/contracts": "5.0.2",
"composable-cow": "github:cowprotocol/composable-cow.git#24d556b",
+ "cow-amm": "github:cowprotocol/cow-amm.git#6566128",
"solmate": "github:transmissions11/solmate#c892309"
},
"devDependencies": {
diff --git a/remappings.txt b/remappings.txt
index 2a9f46d0..b057bf47 100644
--- a/remappings.txt
+++ b/remappings.txt
@@ -5,8 +5,11 @@ solmate/=node_modules/solmate/src
@cowprotocol/=node_modules/@cowprotocol/contracts/src/contracts
cowprotocol/=node_modules/@cowprotocol/contracts/src/
@composable-cow/=node_modules/composable-cow/
+@cow-amm/=node_modules/cow-amm/src
+lib/openzeppelin/=node_modules/@openzeppelin
halmos-cheatcodes=node_modules/halmos-cheatcodes
@crytic/=node_modules/@crytic/
contracts/=src/contracts
interfaces/=src/interfaces
+libraries/=src/libraries
diff --git a/script/.solhint.json b/script/.solhint.json
new file mode 100644
index 00000000..c82a6256
--- /dev/null
+++ b/script/.solhint.json
@@ -0,0 +1,6 @@
+{
+ "rules": {
+ "one-contract-per-file": "off",
+ "custom-errors": "off"
+ }
+}
diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol
new file mode 100644
index 00000000..c697f26a
--- /dev/null
+++ b/script/Deploy.s.sol
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {BCoWFactory} from 'contracts/BCoWFactory.sol';
+import {BCoWHelper} from 'contracts/BCoWHelper.sol';
+import {BFactory} from 'contracts/BFactory.sol';
+import {IBFactory} from 'interfaces/IBFactory.sol';
+
+import {Script} from 'forge-std/Script.sol';
+import {Params} from 'script/Params.s.sol';
+
+/// @notice This base script is shared across `yarn script:{b|bcow}factory:{mainnet|testnet}`
+abstract contract DeployBaseFactory is Script, Params {
+ constructor() Params(block.chainid) {}
+
+ function run() public {
+ vm.startBroadcast();
+ IBFactory bFactory = _deployFactory();
+ bFactory.setBDao(_bFactoryDeploymentParams.bDao);
+ vm.stopBroadcast();
+ }
+
+ function _deployFactory() internal virtual returns (IBFactory);
+}
+
+/// @notice This script will be executed by `yarn script:bfactory:{mainnet|testnet}`
+contract DeployBFactory is DeployBaseFactory {
+ function _deployFactory() internal override returns (IBFactory bFactory) {
+ bFactory = new BFactory();
+ }
+}
+
+/// @notice This script will be executed by `yarn script:bcowfactory:{mainnet|testnet}`
+contract DeployBCoWFactory is DeployBaseFactory {
+ function _deployFactory() internal override returns (IBFactory bFactory) {
+ bFactory = new BCoWFactory({
+ solutionSettler: _bCoWFactoryDeploymentParams.settlement,
+ appData: _bCoWFactoryDeploymentParams.appData
+ });
+
+ new BCoWHelper(address(bFactory));
+ }
+}
diff --git a/script/DeployBCoWFactory.s.sol b/script/DeployBCoWFactory.s.sol
deleted file mode 100644
index a2abfbdd..00000000
--- a/script/DeployBCoWFactory.s.sol
+++ /dev/null
@@ -1,17 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.25;
-
-import {BCoWFactory} from 'contracts/BCoWFactory.sol';
-import {Script} from 'forge-std/Script.sol';
-import {Params} from 'script/Params.s.sol';
-
-contract DeployBCoWFactory is Script, Params {
- function run() public {
- BCoWFactoryDeploymentParams memory params = _bCoWFactoryDeploymentParams[block.chainid];
-
- vm.startBroadcast();
- BCoWFactory bCoWFactory = new BCoWFactory(params.settlement, params.appData);
- bCoWFactory.setBLabs(params.bLabs);
- vm.stopBroadcast();
- }
-}
diff --git a/script/DeployBFactory.s.sol b/script/DeployBFactory.s.sol
deleted file mode 100644
index 5d0c33bc..00000000
--- a/script/DeployBFactory.s.sol
+++ /dev/null
@@ -1,17 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.25;
-
-import {BFactory} from 'contracts/BFactory.sol';
-import {Script} from 'forge-std/Script.sol';
-import {Params} from 'script/Params.s.sol';
-
-contract DeployBFactory is Script, Params {
- function run() public {
- BFactoryDeploymentParams memory params = _bFactoryDeploymentParams[block.chainid];
-
- vm.startBroadcast();
- BFactory bFactory = new BFactory();
- bFactory.setBLabs(params.bLabs);
- vm.stopBroadcast();
- }
-}
diff --git a/script/Params.s.sol b/script/Params.s.sol
index b2f0eeab..f60e1750 100644
--- a/script/Params.s.sol
+++ b/script/Params.s.sol
@@ -1,36 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
-contract Params {
+/// @notice Deployment parameters
+abstract contract Params {
struct BFactoryDeploymentParams {
- address bLabs;
+ address bDao;
}
struct BCoWFactoryDeploymentParams {
- address bLabs;
address settlement;
bytes32 appData;
}
/// @notice Settlement address
address internal constant _GPV2_SETTLEMENT = 0x9008D19f58AAbD9eD0D60971565AA8510560ab41;
-
- /// @notice AppData identifier
- bytes32 internal constant _APP_DATA = bytes32('appData');
-
- /// @notice BFactory deployment parameters for each chain
- mapping(uint256 _chainId => BFactoryDeploymentParams _params) internal _bFactoryDeploymentParams;
-
- /// @notice BCoWFactory deployment parameters for each chain
- mapping(uint256 _chainId => BCoWFactoryDeploymentParams _params) internal _bCoWFactoryDeploymentParams;
-
- constructor() {
- // Mainnet
- _bFactoryDeploymentParams[1] = BFactoryDeploymentParams(address(this));
- _bCoWFactoryDeploymentParams[1] = BCoWFactoryDeploymentParams(address(this), _GPV2_SETTLEMENT, _APP_DATA);
-
- // Sepolia
- _bFactoryDeploymentParams[11_155_111] = BFactoryDeploymentParams(address(this));
- _bCoWFactoryDeploymentParams[11_155_111] = BCoWFactoryDeploymentParams(address(this), _GPV2_SETTLEMENT, _APP_DATA);
+ /// @notice Balancer DAO address (has controller permission to collect fees from BFactory pools)
+ address internal constant _B_DAO = 0xce88686553686DA562CE7Cea497CE749DA109f9F;
+
+ /**
+ * @notice AppData identifier
+ * @dev Value obtained from https://explorer.cow.fi/appdata?tab=encode
+ * - appCode: "CoW AMM Balancer"
+ * - metadata:hooks:version: 0.1.0
+ * - version: 1.1.0
+ */
+ bytes32 internal constant _APP_DATA = 0x362e5182440b52aa8fffe70a251550fbbcbca424740fe5a14f59bf0c1b06fe1d;
+
+ /// @notice BFactory deployment parameters
+ BFactoryDeploymentParams internal _bFactoryDeploymentParams;
+
+ /// @notice BCoWFactory deployment parameters
+ BCoWFactoryDeploymentParams internal _bCoWFactoryDeploymentParams;
+
+ constructor(uint256 chainId) {
+ if (chainId == 1 || chainId == 100 || chainId == 11_155_111) {
+ // Ethereum Mainnet & Ethereum Sepolia [Testnet]
+ _bFactoryDeploymentParams = BFactoryDeploymentParams({bDao: _B_DAO});
+ _bCoWFactoryDeploymentParams = BCoWFactoryDeploymentParams({settlement: _GPV2_SETTLEMENT, appData: _APP_DATA});
+ } else {
+ revert('Params: unknown chain ID');
+ }
}
}
diff --git a/script/Registry.s.sol b/script/Registry.s.sol
new file mode 100644
index 00000000..8934e155
--- /dev/null
+++ b/script/Registry.s.sol
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {BCoWFactory} from 'contracts/BCoWFactory.sol';
+import {BCoWHelper} from 'contracts/BCoWHelper.sol';
+
+import {Params} from 'script/Params.s.sol';
+
+/// @notice Registry of deployed contracts
+abstract contract Registry is Params {
+ /// @notice Balancer CoW Pool Factory
+ BCoWFactory public bCoWFactory;
+ /// @notice Balancer CoW Helper
+ BCoWHelper public bCoWHelper;
+
+ constructor(uint256 chainId) Params(chainId) {
+ if (chainId == 1) {
+ // Ethereum Mainnet
+ bCoWFactory = BCoWFactory(0x23fcC2166F991B8946D195de53745E1b804C91B7);
+ bCoWHelper = BCoWHelper(0x5F6e7D3ef6e9aedD21C107BF8faA610f1215C730);
+ } else if (chainId == 100) {
+ // Gnosis Mainnet
+ bCoWFactory = BCoWFactory(0x7573B99BC09c11Dc0427fb9c6662bc603E008304);
+ bCoWHelper = BCoWHelper(0x85315994492E88D6faCd3B0E3585c68A4720627e);
+ } else if (chainId == 11_155_111) {
+ // Ethereum Sepolia [Testnet]
+ bCoWFactory = BCoWFactory(0xF3F089AF5FaAF8784B445593B3bd8A514EaA3433);
+ bCoWHelper = BCoWHelper(0x07E7E9e3f4E715Ef1434b7f865fedBCE82Dd41Ba);
+ } else {
+ revert('Registry: unknown chain ID');
+ }
+ }
+}
diff --git a/script/Script.s.sol b/script/Script.s.sol
new file mode 100644
index 00000000..6d86aa49
--- /dev/null
+++ b/script/Script.s.sol
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+import {IBPool} from 'contracts/BPool.sol';
+import {IFaucet} from 'interfaces/IFaucet.sol';
+
+import {Script} from 'forge-std/Script.sol';
+import {Registry} from 'script/Registry.s.sol';
+
+/// @notice This base script is shared across `yarn script:{mainnet|testnet}`
+abstract contract BaseScript is Registry, Script {
+ constructor() Registry(block.chainid) {}
+}
+
+/// @notice This script will be executed by `yarn script:mainnet`
+contract MainnetScript is BaseScript {
+ function run() public {
+ assert(block.chainid == 1 || block.chainid == 100);
+ vm.startBroadcast();
+
+ // script logic here
+
+ vm.stopBroadcast();
+ }
+}
+
+/// @notice This script will be executed by `yarn script:testnet`
+contract TestnetScript is BaseScript {
+ /// @notice ERC20 and Faucet addresses
+ address internal constant _SEPOLIA_FAUCET = 0x26bfAecAe4D5fa93eE1737ce1Ce7D53F2a0E9b2d;
+ address internal constant _SEPOLIA_BAL_TOKEN = 0xb19382073c7A0aDdbb56Ac6AF1808Fa49e377B75;
+ address internal constant _SEPOLIA_DAI_TOKEN = 0xB77EB1A70A96fDAAeB31DB1b42F2b8b5846b2613;
+ address internal constant _SEPOLIA_USDC_TOKEN = 0x80D6d3946ed8A1Da4E226aa21CCdDc32bd127d1A;
+
+ /// @dev The following is an example of a script that deploys a Balancer CoW pool
+ function run() public {
+ assert(block.chainid == 11_155_111);
+ vm.startBroadcast();
+
+ // NOTE: dripping can be called by anyone but only once a day (per address)
+ IFaucet(_SEPOLIA_FAUCET).drip(_SEPOLIA_BAL_TOKEN);
+ IFaucet(_SEPOLIA_FAUCET).drip(_SEPOLIA_DAI_TOKEN);
+ IFaucet(_SEPOLIA_FAUCET).drip(_SEPOLIA_USDC_TOKEN);
+
+ IBPool bPool = bCoWFactory.newBPool();
+
+ IERC20(_SEPOLIA_BAL_TOKEN).approve(address(bPool), type(uint256).max);
+ IERC20(_SEPOLIA_DAI_TOKEN).approve(address(bPool), type(uint256).max);
+ IERC20(_SEPOLIA_USDC_TOKEN).approve(address(bPool), type(uint256).max);
+
+ bPool.bind(_SEPOLIA_BAL_TOKEN, 40e18, 1e18);
+ bPool.bind(_SEPOLIA_DAI_TOKEN, 10e18, 1e18);
+ bPool.bind(_SEPOLIA_USDC_TOKEN, 10e6, 1e18);
+
+ bPool.finalize();
+ vm.stopBroadcast();
+ }
+}
diff --git a/src/contracts/BCoWHelper.sol b/src/contracts/BCoWHelper.sol
new file mode 100644
index 00000000..4562bcda
--- /dev/null
+++ b/src/contracts/BCoWHelper.sol
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity 0.8.25;
+
+import {IBCoWFactory} from 'interfaces/IBCoWFactory.sol';
+import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
+
+import {ICOWAMMPoolHelper} from '@cow-amm/interfaces/ICOWAMMPoolHelper.sol';
+import {GetTradeableOrder} from '@cow-amm/libraries/GetTradeableOrder.sol';
+
+import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';
+import {GPv2Interaction} from '@cowprotocol/libraries/GPv2Interaction.sol';
+import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
+
+import {BMath} from 'contracts/BMath.sol';
+
+/**
+ * @title BCoWHelper
+ * @notice Helper contract that allows to trade on CoW Swap Protocol.
+ * @dev This contract supports only 2-token equal-weights pools.
+ */
+contract BCoWHelper is ICOWAMMPoolHelper, BMath {
+ using GPv2Order for GPv2Order.Data;
+
+ /// @notice The app data used by this helper's factory.
+ bytes32 internal immutable _APP_DATA;
+
+ /// @inheritdoc ICOWAMMPoolHelper
+ // solhint-disable-next-line style-guide-casing
+ address public immutable factory;
+
+ constructor(address factory_) {
+ factory = factory_;
+ _APP_DATA = IBCoWFactory(factory_).APP_DATA();
+ }
+
+ /// @inheritdoc ICOWAMMPoolHelper
+ function order(
+ address pool,
+ uint256[] calldata prices
+ )
+ external
+ view
+ returns (
+ GPv2Order.Data memory order_,
+ GPv2Interaction.Data[] memory preInteractions,
+ GPv2Interaction.Data[] memory postInteractions,
+ bytes memory sig
+ )
+ {
+ address[] memory tokens_ = tokens(pool);
+
+ GetTradeableOrder.GetTradeableOrderParams memory params = GetTradeableOrder.GetTradeableOrderParams({
+ pool: pool,
+ token0: IERC20(tokens_[0]),
+ token1: IERC20(tokens_[1]),
+ // The price of this function is expressed as amount of
+ // token1 per amount of token0. The `prices` vector is
+ // expressed the other way around.
+ priceNumerator: prices[1],
+ priceDenominator: prices[0],
+ appData: _APP_DATA
+ });
+
+ order_ = GetTradeableOrder.getTradeableOrder(params);
+
+ {
+ // NOTE: Using calcOutGivenIn for the sell amount in order to avoid possible rounding
+ // issues that may cause invalid orders. This prevents CoW Protocol back-end from generating
+ // orders that may be ignored due to rounding-induced reverts.
+
+ uint256 balanceToken0 = IERC20(tokens_[0]).balanceOf(pool);
+ uint256 balanceToken1 = IERC20(tokens_[1]).balanceOf(pool);
+ (uint256 balanceIn, uint256 balanceOut) =
+ address(order_.buyToken) == tokens_[0] ? (balanceToken0, balanceToken1) : (balanceToken1, balanceToken0);
+
+ order_.sellAmount = calcOutGivenIn({
+ tokenBalanceIn: balanceIn,
+ tokenWeightIn: 1e18,
+ tokenBalanceOut: balanceOut,
+ tokenWeightOut: 1e18,
+ tokenAmountIn: order_.buyAmount,
+ swapFee: 0
+ });
+ }
+
+ // A ERC-1271 signature on CoW Protocol is composed of two parts: the
+ // signer address and the valid ERC-1271 signature data for that signer.
+ bytes memory eip1271sig;
+ eip1271sig = abi.encode(order_);
+ sig = abi.encodePacked(pool, eip1271sig);
+
+ // Generate the order commitment pre-interaction
+ bytes32 domainSeparator = IBCoWPool(pool).SOLUTION_SETTLER_DOMAIN_SEPARATOR();
+ bytes32 orderCommitment = order_.hash(domainSeparator);
+
+ preInteractions = new GPv2Interaction.Data[](1);
+ preInteractions[0] = GPv2Interaction.Data({
+ target: pool,
+ value: 0,
+ callData: abi.encodeWithSelector(IBCoWPool.commit.selector, orderCommitment)
+ });
+
+ return (order_, preInteractions, postInteractions, sig);
+ }
+
+ /// @inheritdoc ICOWAMMPoolHelper
+ function tokens(address pool) public view virtual returns (address[] memory tokens_) {
+ // reverts in case pool is not deployed by the helper's factory
+ if (!IBCoWFactory(factory).isBPool(pool)) {
+ revert PoolDoesNotExist();
+ }
+
+ // call reverts with `BPool_PoolNotFinalized()` in case pool is not finalized
+ tokens_ = IBCoWPool(pool).getFinalTokens();
+
+ // reverts in case pool is not supported (non-2-token pool)
+ if (tokens_.length != 2) {
+ revert PoolDoesNotExist();
+ }
+ // reverts in case pool is not supported (non-equal weights)
+ if (IBCoWPool(pool).getNormalizedWeight(tokens_[0]) != IBCoWPool(pool).getNormalizedWeight(tokens_[1])) {
+ revert PoolDoesNotExist();
+ }
+ }
+}
diff --git a/src/contracts/BConst.sol b/src/contracts/BConst.sol
index 3a184a01..5e42302c 100644
--- a/src/contracts/BConst.sol
+++ b/src/contracts/BConst.sol
@@ -17,7 +17,7 @@ contract BConst {
/// @notice The minimum swap fee that can be set.
uint256 public constant MIN_FEE = BONE / 10 ** 6;
/// @notice The maximum swap fee that can be set.
- uint256 public constant MAX_FEE = BONE / 10;
+ uint256 public constant MAX_FEE = BONE - MIN_FEE;
/// @notice The immutable exit fee percentage
uint256 public constant EXIT_FEE = 0;
diff --git a/src/contracts/BFactory.sol b/src/contracts/BFactory.sol
index 60619bb6..19450439 100644
--- a/src/contracts/BFactory.sol
+++ b/src/contracts/BFactory.sol
@@ -13,11 +13,11 @@ import {IBPool} from 'interfaces/IBPool.sol';
contract BFactory is IBFactory {
/// @dev Mapping indicating whether the address is a BPool.
mapping(address => bool) internal _isBPool;
- /// @dev bLabs address.
- address internal _bLabs;
+ /// @dev bDao address.
+ address internal _bDao;
constructor() {
- _bLabs = msg.sender;
+ _bDao = msg.sender;
}
/// @inheritdoc IBFactory
@@ -29,25 +29,25 @@ contract BFactory is IBFactory {
}
/// @inheritdoc IBFactory
- function setBLabs(address bLabs) external {
- if (bLabs == address(0)) {
+ function setBDao(address bDao) external {
+ if (bDao == address(0)) {
revert BFactory_AddressZero();
}
- if (msg.sender != _bLabs) {
- revert BFactory_NotBLabs();
+ if (msg.sender != _bDao) {
+ revert BFactory_NotBDao();
}
- emit LOG_BLABS(msg.sender, bLabs);
- _bLabs = bLabs;
+ emit LOG_BDAO(msg.sender, bDao);
+ _bDao = bDao;
}
/// @inheritdoc IBFactory
function collect(IBPool bPool) external {
- if (msg.sender != _bLabs) {
- revert BFactory_NotBLabs();
+ if (msg.sender != _bDao) {
+ revert BFactory_NotBDao();
}
uint256 collected = bPool.balanceOf(address(this));
- SafeERC20.safeTransfer(bPool, _bLabs, collected);
+ SafeERC20.safeTransfer(bPool, _bDao, collected);
}
/// @inheritdoc IBFactory
@@ -56,8 +56,8 @@ contract BFactory is IBFactory {
}
/// @inheritdoc IBFactory
- function getBLabs() external view returns (address) {
- return _bLabs;
+ function getBDao() external view returns (address) {
+ return _bDao;
}
/**
diff --git a/src/contracts/BMath.sol b/src/contracts/BMath.sol
index 25d7e123..f942ad45 100644
--- a/src/contracts/BMath.sol
+++ b/src/contracts/BMath.sol
@@ -113,189 +113,4 @@ contract BMath is BConst, BNum {
tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn);
return tokenAmountIn;
}
-
- /**
- * @notice Calculate the amount of pool tokens that should be minted,
- * given a single token in when joining a pool
- * @param tokenBalanceIn The balance of the input token in the pool
- * @param tokenWeightIn The weight of the input token in the pool
- * @param poolSupply The total supply of the pool tokens
- * @param totalWeight The total weight of the pool
- * @param tokenAmountIn The amount of the input token
- * @param swapFee The swap fee of the pool
- * @return poolAmountOut The amount of balancer pool tokens that will be minted
- * @dev Formula:
- * pAo = poolAmountOut / \
- * tAi = tokenAmountIn /// / // wI \ \\ \ wI \
- * wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \
- * tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS
- * tBi = tokenBalanceIn \\ ------------------------------------- / /
- * pS = poolSupply \\ tBi / /
- * sF = swapFee \ /
- */
- function calcPoolOutGivenSingleIn(
- uint256 tokenBalanceIn,
- uint256 tokenWeightIn,
- uint256 poolSupply,
- uint256 totalWeight,
- uint256 tokenAmountIn,
- uint256 swapFee
- ) public pure returns (uint256 poolAmountOut) {
- // Charge the trading fee for the proportion of tokenAi
- // which is implicitly traded to the other pool tokens.
- // That proportion is (1- weightTokenIn)
- // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee);
- uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight);
- uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee);
- uint256 tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz));
-
- uint256 newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee);
- uint256 tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn);
-
- // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply;
- uint256 poolRatio = bpow(tokenInRatio, normalizedWeight);
- uint256 newPoolSupply = bmul(poolRatio, poolSupply);
- poolAmountOut = bsub(newPoolSupply, poolSupply);
- return poolAmountOut;
- }
-
- /**
- * @notice Given amount of pool tokens out, calculate the amount of tokens in that should be sent
- * @param tokenBalanceIn The balance of the input token in the pool
- * @param tokenWeightIn The weight of the input token in the pool
- * @param poolSupply The current total supply
- * @param totalWeight The sum of the weight of all tokens in the pool
- * @param poolAmountOut The expected amount of pool tokens
- * @param swapFee The swap fee of the pool
- * @return tokenAmountIn The amount of token in requred to mint poolAmountIn token pools
- * @dev Formula:
- * tAi = tokenAmountIn //(pS + pAo)\ / 1 \\
- * pS = poolSupply || --------- | ^ | --------- || * bI - bI
- * pAo = poolAmountOut \\ pS / \(wI / tW)//
- * bI = balanceIn tAi = --------------------------------------------
- * wI = weightIn / wI \
- * tW = totalWeight 1 - | 1 - ---- | * sF
- * sF = swapFee \ tW /
- */
- function calcSingleInGivenPoolOut(
- uint256 tokenBalanceIn,
- uint256 tokenWeightIn,
- uint256 poolSupply,
- uint256 totalWeight,
- uint256 poolAmountOut,
- uint256 swapFee
- ) public pure returns (uint256 tokenAmountIn) {
- uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight);
- uint256 newPoolSupply = badd(poolSupply, poolAmountOut);
- uint256 poolRatio = bdiv(newPoolSupply, poolSupply);
-
- //uint newBalTi = poolRatio^(1/weightTi) * balTi;
- uint256 boo = bdiv(BONE, normalizedWeight);
- uint256 tokenInRatio = bpow(poolRatio, boo);
- uint256 newTokenBalanceIn = bmul(tokenInRatio, tokenBalanceIn);
- uint256 tokenAmountInAfterFee = bsub(newTokenBalanceIn, tokenBalanceIn);
- // Do reverse order of fees charged in joinswap_ExternAmountIn, this way
- // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ```
- //uint tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ;
- uint256 zar = bmul(bsub(BONE, normalizedWeight), swapFee);
- tokenAmountIn = bdiv(tokenAmountInAfterFee, bsub(BONE, zar));
- return tokenAmountIn;
- }
-
- /**
- * @notice Calculate the amount of token out given the amount of pool tokens in
- * @param tokenBalanceOut The balance of the output token in the pool
- * @param tokenWeightOut The weight of the output token in the pool
- * @param poolSupply The total supply of the pool tokens
- * @param totalWeight The total weight of the pool
- * @param poolAmountIn The amount of pool tokens
- * @param swapFee The swap fee of the pool
- * @return tokenAmountOut The amount of underlying token out from burning
- * poolAmountIn pool tokens
- * @dev Formula:
- * tAo = tokenAmountOut / / \\
- * bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\
- * pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 ||
- * ps = poolSupply \ \\ pS / \(wO / tW)/ //
- * wI = tokenWeightIn tAo = \ \ //
- * tW = totalWeight / / wO \ \
- * sF = swapFee * | 1 - | 1 - ---- | * sF |
- * eF = exitFee \ \ tW / /
- */
- function calcSingleOutGivenPoolIn(
- uint256 tokenBalanceOut,
- uint256 tokenWeightOut,
- uint256 poolSupply,
- uint256 totalWeight,
- uint256 poolAmountIn,
- uint256 swapFee
- ) public pure returns (uint256 tokenAmountOut) {
- uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight);
- // charge exit fee on the pool token side
- // pAiAfterExitFee = pAi*(1-exitFee)
- uint256 poolAmountInAfterExitFee = bmul(poolAmountIn, bsub(BONE, EXIT_FEE));
- uint256 newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee);
- uint256 poolRatio = bdiv(newPoolSupply, poolSupply);
-
- // newBalTo = poolRatio^(1/weightTo) * balTo;
- uint256 tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight));
- uint256 newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut);
-
- uint256 tokenAmountOutBeforeSwapFee = bsub(tokenBalanceOut, newTokenBalanceOut);
-
- // charge swap fee on the output token side
- //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee)
- uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee);
- tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz));
- return tokenAmountOut;
- }
-
- /**
- * @notice Calculate the amount of pool tokens in given an amount of single token out
- * @param tokenBalanceOut The balance of the output token in the pool
- * @param tokenWeightOut The weight of the output token in the pool
- * @param poolSupply The total supply of the pool tokens
- * @param totalWeight The total weight of the pool
- * @param tokenAmountOut The amount of the output token
- * @param swapFee The swap fee of the pool
- * @return poolAmountIn The amount of pool tokens to burn in order to receive
- * `tokeAmountOut` underlying tokens
- * @dev Formula:
- * pAi = poolAmountIn // / tAo \\ / wO \ \
- * bO = tokenBalanceOut // | bO - -------------------------- |\ | ---- | \
- * tAo = tokenAmountOut pS - || \ 1 - ((1 - (wO / tW)) * sF)/ | ^ \ tW / * pS |
- * ps = poolSupply \\ -----------------------------------/ /
- * wO = tokenWeightOut pAi = \\ bO / /
- * tW = totalWeight -------------------------------------------------------------
- * sF = swapFee ( 1 - eF )
- * eF = exitFee
- */
- function calcPoolInGivenSingleOut(
- uint256 tokenBalanceOut,
- uint256 tokenWeightOut,
- uint256 poolSupply,
- uint256 totalWeight,
- uint256 tokenAmountOut,
- uint256 swapFee
- ) public pure returns (uint256 poolAmountIn) {
- // charge swap fee on the output token side
- uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight);
- //uint tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ;
- uint256 zoo = bsub(BONE, normalizedWeight);
- uint256 zar = bmul(zoo, swapFee);
- uint256 tokenAmountOutBeforeSwapFee = bdiv(tokenAmountOut, bsub(BONE, zar));
-
- uint256 newTokenBalanceOut = bsub(tokenBalanceOut, tokenAmountOutBeforeSwapFee);
- uint256 tokenOutRatio = bdiv(newTokenBalanceOut, tokenBalanceOut);
-
- //uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply;
- uint256 poolRatio = bpow(tokenOutRatio, normalizedWeight);
- uint256 newPoolSupply = bmul(poolRatio, poolSupply);
- uint256 poolAmountInAfterExitFee = bsub(poolSupply, newPoolSupply);
-
- // charge exit fee on the pool token side
- // pAi = pAiAfterExitFee/(1-exitFee)
- poolAmountIn = bdiv(poolAmountInAfterExitFee, bsub(BONE, EXIT_FEE));
- return poolAmountIn;
- }
}
diff --git a/src/contracts/BPool.sol b/src/contracts/BPool.sol
index 2af48596..3c3b8bc4 100644
--- a/src/contracts/BPool.sol
+++ b/src/contracts/BPool.sol
@@ -280,8 +280,6 @@ contract BPool is BToken, BMath, IBPool {
_pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
_pushUnderlying(tokenOut, msg.sender, tokenAmountOut);
-
- return (tokenAmountOut, spotPriceAfter);
}
/// @inheritdoc IBPool
@@ -339,150 +337,10 @@ contract BPool is BToken, BMath, IBPool {
_pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
_pushUnderlying(tokenOut, msg.sender, tokenAmountOut);
-
- return (tokenAmountIn, spotPriceAfter);
- }
-
- /// @inheritdoc IBPool
- function joinswapExternAmountIn(
- address tokenIn,
- uint256 tokenAmountIn,
- uint256 minPoolAmountOut
- ) external _logs_ _lock_ _finalized_ returns (uint256 poolAmountOut) {
- if (!_records[tokenIn].bound) {
- revert BPool_TokenNotBound();
- }
-
- Record storage inRecord = _records[tokenIn];
- uint256 tokenInBalance = IERC20(tokenIn).balanceOf(address(this));
- if (tokenAmountIn > bmul(tokenInBalance, MAX_IN_RATIO)) {
- revert BPool_TokenAmountInAboveMaxRatio();
- }
-
- poolAmountOut =
- calcPoolOutGivenSingleIn(tokenInBalance, inRecord.denorm, totalSupply(), _totalWeight, tokenAmountIn, _swapFee);
- if (poolAmountOut < minPoolAmountOut) {
- revert BPool_PoolAmountOutBelowMinPoolAmountOut();
- }
-
- emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn);
-
- _mintPoolShare(poolAmountOut);
- _pushPoolShare(msg.sender, poolAmountOut);
- _pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
-
- return poolAmountOut;
}
/// @inheritdoc IBPool
- function joinswapPoolAmountOut(
- address tokenIn,
- uint256 poolAmountOut,
- uint256 maxAmountIn
- ) external _logs_ _lock_ _finalized_ returns (uint256 tokenAmountIn) {
- if (!_records[tokenIn].bound) {
- revert BPool_TokenNotBound();
- }
-
- Record storage inRecord = _records[tokenIn];
- uint256 tokenInBalance = IERC20(tokenIn).balanceOf(address(this));
-
- tokenAmountIn =
- calcSingleInGivenPoolOut(tokenInBalance, inRecord.denorm, totalSupply(), _totalWeight, poolAmountOut, _swapFee);
-
- if (tokenAmountIn == 0) {
- revert BPool_InvalidTokenAmountIn();
- }
- if (tokenAmountIn > maxAmountIn) {
- revert BPool_TokenAmountInAboveMaxAmountIn();
- }
- if (tokenAmountIn > bmul(tokenInBalance, MAX_IN_RATIO)) {
- revert BPool_TokenAmountInAboveMaxRatio();
- }
-
- emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn);
-
- _mintPoolShare(poolAmountOut);
- _pushPoolShare(msg.sender, poolAmountOut);
- _pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
-
- return tokenAmountIn;
- }
-
- /// @inheritdoc IBPool
- function exitswapPoolAmountIn(
- address tokenOut,
- uint256 poolAmountIn,
- uint256 minAmountOut
- ) external _logs_ _lock_ _finalized_ returns (uint256 tokenAmountOut) {
- if (!_records[tokenOut].bound) {
- revert BPool_TokenNotBound();
- }
-
- Record storage outRecord = _records[tokenOut];
- uint256 tokenOutBalance = IERC20(tokenOut).balanceOf(address(this));
-
- tokenAmountOut =
- calcSingleOutGivenPoolIn(tokenOutBalance, outRecord.denorm, totalSupply(), _totalWeight, poolAmountIn, _swapFee);
-
- if (tokenAmountOut < minAmountOut) {
- revert BPool_TokenAmountOutBelowMinAmountOut();
- }
- if (tokenAmountOut > bmul(tokenOutBalance, MAX_OUT_RATIO)) {
- revert BPool_TokenAmountOutAboveMaxOut();
- }
-
- uint256 exitFee = bmul(poolAmountIn, EXIT_FEE);
-
- emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut);
-
- _pullPoolShare(msg.sender, poolAmountIn);
- _burnPoolShare(bsub(poolAmountIn, exitFee));
- _pushPoolShare(FACTORY, exitFee);
- _pushUnderlying(tokenOut, msg.sender, tokenAmountOut);
-
- return tokenAmountOut;
- }
-
- /// @inheritdoc IBPool
- function exitswapExternAmountOut(
- address tokenOut,
- uint256 tokenAmountOut,
- uint256 maxPoolAmountIn
- ) external _logs_ _lock_ _finalized_ returns (uint256 poolAmountIn) {
- if (!_records[tokenOut].bound) {
- revert BPool_TokenNotBound();
- }
-
- Record storage outRecord = _records[tokenOut];
- uint256 tokenOutBalance = IERC20(tokenOut).balanceOf(address(this));
- if (tokenAmountOut > bmul(tokenOutBalance, MAX_OUT_RATIO)) {
- revert BPool_TokenAmountOutAboveMaxOut();
- }
-
- poolAmountIn =
- calcPoolInGivenSingleOut(tokenOutBalance, outRecord.denorm, totalSupply(), _totalWeight, tokenAmountOut, _swapFee);
- if (poolAmountIn == 0) {
- revert BPool_InvalidPoolAmountIn();
- }
- if (poolAmountIn > maxPoolAmountIn) {
- revert BPool_PoolAmountInAboveMaxPoolAmountIn();
- }
-
- uint256 exitFee = bmul(poolAmountIn, EXIT_FEE);
-
- emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut);
-
- _pullPoolShare(msg.sender, poolAmountIn);
- _burnPoolShare(bsub(poolAmountIn, exitFee));
- _pushPoolShare(FACTORY, exitFee);
- _pushUnderlying(tokenOut, msg.sender, tokenAmountOut);
-
- return poolAmountIn;
- }
-
- /// @inheritdoc IBPool
- function getSpotPrice(address tokenIn, address tokenOut) external view _viewlock_ returns (uint256 spotPrice) {
+ function getSpotPrice(address tokenIn, address tokenOut) external view _viewlock_ returns (uint256) {
if (!_records[tokenIn].bound) {
revert BPool_TokenNotBound();
}
@@ -492,19 +350,17 @@ contract BPool is BToken, BMath, IBPool {
Record storage inRecord = _records[tokenIn];
Record storage outRecord = _records[tokenOut];
- spotPrice = calcSpotPrice(
+ return calcSpotPrice(
IERC20(tokenIn).balanceOf(address(this)),
inRecord.denorm,
IERC20(tokenOut).balanceOf(address(this)),
outRecord.denorm,
_swapFee
);
-
- return spotPrice;
}
/// @inheritdoc IBPool
- function getSpotPriceSansFee(address tokenIn, address tokenOut) external view _viewlock_ returns (uint256 spotPrice) {
+ function getSpotPriceSansFee(address tokenIn, address tokenOut) external view _viewlock_ returns (uint256) {
if (!_records[tokenIn].bound) {
revert BPool_TokenNotBound();
}
@@ -514,15 +370,13 @@ contract BPool is BToken, BMath, IBPool {
Record storage inRecord = _records[tokenIn];
Record storage outRecord = _records[tokenOut];
- spotPrice = calcSpotPrice(
+ return calcSpotPrice(
IERC20(tokenIn).balanceOf(address(this)),
inRecord.denorm,
IERC20(tokenOut).balanceOf(address(this)),
outRecord.denorm,
0
);
-
- return spotPrice;
}
/// @inheritdoc IBPool
@@ -531,8 +385,8 @@ contract BPool is BToken, BMath, IBPool {
}
/// @inheritdoc IBPool
- function isBound(address t) external view returns (bool) {
- return _records[t].bound;
+ function isBound(address token) external view returns (bool) {
+ return _records[token].bound;
}
/// @inheritdoc IBPool
@@ -541,12 +395,12 @@ contract BPool is BToken, BMath, IBPool {
}
/// @inheritdoc IBPool
- function getCurrentTokens() external view _viewlock_ returns (address[] memory tokens) {
+ function getCurrentTokens() external view _viewlock_ returns (address[] memory) {
return _tokens;
}
/// @inheritdoc IBPool
- function getFinalTokens() external view _viewlock_ _finalized_ returns (address[] memory tokens) {
+ function getFinalTokens() external view _viewlock_ _finalized_ returns (address[] memory) {
return _tokens;
}
diff --git a/src/contracts/BToken.sol b/src/contracts/BToken.sol
index 0491b17d..157a4440 100644
--- a/src/contracts/BToken.sol
+++ b/src/contracts/BToken.sol
@@ -18,7 +18,7 @@ contract BToken is ERC20 {
*/
function increaseApproval(address spender, uint256 amount) external returns (bool success) {
_approve(msg.sender, spender, allowance(msg.sender, spender) + amount);
- return true;
+ success = true;
}
/**
@@ -34,7 +34,7 @@ contract BToken is ERC20 {
} else {
_approve(msg.sender, spender, oldValue - amount);
}
- return true;
+ success = true;
}
/**
diff --git a/src/interfaces/IBFactory.sol b/src/interfaces/IBFactory.sol
index edaff060..672ba7ca 100644
--- a/src/interfaces/IBFactory.sol
+++ b/src/interfaces/IBFactory.sol
@@ -12,11 +12,11 @@ interface IBFactory {
event LOG_NEW_POOL(address indexed caller, address indexed bPool);
/**
- * @notice Emitted when setting the BLabs address
- * @param caller The caller of the set BLabs function
- * @param bLabs The address of the new BLabs
+ * @notice Emitted when setting the BDao address
+ * @param caller The caller of the set BDao function
+ * @param bDao The address of the new BDao
*/
- event LOG_BLABS(address indexed caller, address indexed bLabs);
+ event LOG_BDAO(address indexed caller, address indexed bDao);
/**
* @notice Thrown when setting a variable to address zero
@@ -24,9 +24,9 @@ interface IBFactory {
error BFactory_AddressZero();
/**
- * @notice Thrown when caller is not BLabs address
+ * @notice Thrown when caller is not BDao address
*/
- error BFactory_NotBLabs();
+ error BFactory_NotBDao();
/**
* @notice Creates a new BPool, assigning the caller as the pool controller
@@ -35,13 +35,13 @@ interface IBFactory {
function newBPool() external returns (IBPool bPool);
/**
- * @notice Sets the BLabs address in the factory
- * @param bLabs The new BLabs address
+ * @notice Sets the BDao address in the factory
+ * @param bDao The new BDao address
*/
- function setBLabs(address bLabs) external;
+ function setBDao(address bDao) external;
/**
- * @notice Collects the fees of a pool and transfers it to BLabs address
+ * @notice Collects the fees of a pool and transfers it to BDao address
* @param bPool The address of the pool to collect fees from
*/
function collect(IBPool bPool) external;
@@ -54,8 +54,8 @@ interface IBFactory {
function isBPool(address bPool) external view returns (bool isBPool);
/**
- * @notice Gets the BLabs address
- * @return bLabs The address of the BLabs
+ * @notice Gets the BDao address
+ * @return bDao The address of the BDao
*/
- function getBLabs() external view returns (address bLabs);
+ function getBDao() external view returns (address bDao);
}
diff --git a/src/interfaces/IBPool.sol b/src/interfaces/IBPool.sol
index 9b18cfa4..99efa01b 100644
--- a/src/interfaces/IBPool.sol
+++ b/src/interfaces/IBPool.sol
@@ -282,58 +282,6 @@ interface IBPool is IERC20 {
uint256 maxPrice
) external returns (uint256 tokenAmountIn, uint256 spotPriceAfter);
- /**
- * @notice Joins a pool providing a single token in, specifying the exact amount of token given
- * @param tokenIn The address of the token to swap in and join
- * @param tokenAmountIn The amount of token to join
- * @param minPoolAmountOut The minimum amount of pool token to receive
- * @return poolAmountOut The amount of pool token received
- */
- function joinswapExternAmountIn(
- address tokenIn,
- uint256 tokenAmountIn,
- uint256 minPoolAmountOut
- ) external returns (uint256 poolAmountOut);
-
- /**
- * @notice Joins a pool providing a single token in, specifying the exact amount of pool tokens received
- * @param tokenIn The address of the token to swap in and join
- * @param poolAmountOut The amount of pool token to receive
- * @param maxAmountIn The maximum amount of token to introduce to the pool
- * @return tokenAmountIn The amount of token in introduced
- */
- function joinswapPoolAmountOut(
- address tokenIn,
- uint256 poolAmountOut,
- uint256 maxAmountIn
- ) external returns (uint256 tokenAmountIn);
-
- /**
- * @notice Exits a pool providing a specific amount of pool tokens in, and receiving only a single token
- * @param tokenOut The address of the token to swap out and exit
- * @param poolAmountIn The amount of pool token to burn
- * @param minAmountOut The minimum amount of token to receive
- * @return tokenAmountOut The amount of token received
- */
- function exitswapPoolAmountIn(
- address tokenOut,
- uint256 poolAmountIn,
- uint256 minAmountOut
- ) external returns (uint256 tokenAmountOut);
-
- /**
- * @notice Exits a pool expecting a specific amount of token out, and providing pool token
- * @param tokenOut The address of the token to swap out and exit
- * @param tokenAmountOut The amount of token to receive
- * @param maxPoolAmountIn The maximum amount of pool token to burn
- * @return poolAmountIn The amount of pool token burned
- */
- function exitswapExternAmountOut(
- address tokenOut,
- uint256 tokenAmountOut,
- uint256 maxPoolAmountIn
- ) external returns (uint256 poolAmountIn);
-
/**
* @notice Gets the spot price of tokenIn in terms of tokenOut
* @param tokenIn The address of the token to swap in
diff --git a/src/interfaces/IFaucet.sol b/src/interfaces/IFaucet.sol
new file mode 100644
index 00000000..84d507b3
--- /dev/null
+++ b/src/interfaces/IFaucet.sol
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-3.0
+pragma solidity 0.8.25;
+
+/**
+ * @title IFaucet
+ * @notice External interface of Sepolia's Faucet contract.
+ */
+interface IFaucet {
+ /**
+ * @notice Drips an amount of tokens to the caller.
+ * @param token The address of the token to drip.
+ */
+ function drip(address token) external;
+}
diff --git a/src/interfaces/ISettlement.sol b/src/interfaces/ISettlement.sol
index 1b176cc1..1bcb793a 100644
--- a/src/interfaces/ISettlement.sol
+++ b/src/interfaces/ISettlement.sol
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-3.0
+// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;
import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';
diff --git a/test/integration/BCoWHelper.t.sol b/test/integration/BCoWHelper.t.sol
new file mode 100644
index 00000000..2bd63ab3
--- /dev/null
+++ b/test/integration/BCoWHelper.t.sol
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import {Test} from 'forge-std/Test.sol';
+
+import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';
+
+import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
+import {IBPool} from 'interfaces/IBPool.sol';
+import {ISettlement} from 'interfaces/ISettlement.sol';
+
+import {ICOWAMMPoolHelper} from '@cow-amm/interfaces/ICOWAMMPoolHelper.sol';
+import {GPv2Interaction} from '@cowprotocol/libraries/GPv2Interaction.sol';
+import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
+import {GPv2Trade} from '@cowprotocol/libraries/GPv2Trade.sol';
+import {GPv2Signing} from '@cowprotocol/mixins/GPv2Signing.sol';
+
+import {GPv2TradeEncoder} from '@composable-cow/test/vendored/GPv2TradeEncoder.sol';
+
+import {BCoWFactory} from 'contracts/BCoWFactory.sol';
+import {BCoWHelper} from 'contracts/BCoWHelper.sol';
+
+contract BCoWHelperIntegrationTest is Test {
+ using GPv2Order for GPv2Order.Data;
+
+ BCoWHelper private helper;
+
+ // All hardcoded addresses are mainnet addresses
+ address public lp = makeAddr('lp');
+
+ ISettlement private settlement = ISettlement(0x9008D19f58AAbD9eD0D60971565AA8510560ab41);
+ address private vaultRelayer;
+
+ address private solver = 0x423cEc87f19F0778f549846e0801ee267a917935;
+
+ BCoWFactory private ammFactory;
+ IBPool private weightedPool;
+ IBPool private basicPool;
+
+ IERC20 private constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
+ IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
+
+ uint256 constant TEN_PERCENT = 0.1 ether;
+ // NOTE: 1 ETH = 1000 DAI
+ uint256 constant INITIAL_DAI_BALANCE = 1000 ether;
+ uint256 constant INITIAL_WETH_BALANCE = 1 ether;
+ uint256 constant INITIAL_SPOT_PRICE = 0.001e18;
+
+ uint256 constant SKEWENESS_RATIO = 95; // -5% skewness
+ uint256 constant EXPECTED_FINAL_SPOT_PRICE = INITIAL_SPOT_PRICE * 100 / SKEWENESS_RATIO;
+
+ function setUp() public {
+ vm.createSelectFork('mainnet', 20_012_063);
+
+ vaultRelayer = address(settlement.vaultRelayer());
+
+ ammFactory = new BCoWFactory(address(settlement), bytes32('appData'));
+ helper = new BCoWHelper(address(ammFactory));
+
+ deal(address(DAI), lp, type(uint256).max, false);
+ deal(address(WETH), lp, type(uint256).max, false);
+
+ vm.startPrank(lp);
+ basicPool = ammFactory.newBPool();
+ weightedPool = ammFactory.newBPool();
+
+ DAI.approve(address(basicPool), type(uint256).max);
+ WETH.approve(address(basicPool), type(uint256).max);
+ basicPool.bind(address(DAI), INITIAL_DAI_BALANCE, 4.2e18); // no weight
+ basicPool.bind(address(WETH), INITIAL_WETH_BALANCE, 4.2e18); // no weight
+
+ DAI.approve(address(weightedPool), type(uint256).max);
+ WETH.approve(address(weightedPool), type(uint256).max);
+ // NOTE: pool is 80-20 DAI-WETH, has 4xDAI balance than basic, same spot price
+ weightedPool.bind(address(DAI), 4 * INITIAL_DAI_BALANCE, 8e18); // 80% weight
+ weightedPool.bind(address(WETH), INITIAL_WETH_BALANCE, 2e18); // 20% weight
+
+ // finalize
+ basicPool.finalize();
+ weightedPool.finalize();
+
+ vm.stopPrank();
+ }
+
+ function testBasicOrder() public {
+ IBCoWPool pool = IBCoWPool(address(basicPool));
+
+ uint256 spotPrice = pool.getSpotPriceSansFee(address(WETH), address(DAI));
+ assertEq(spotPrice, INITIAL_SPOT_PRICE);
+
+ _executeHelperOrder(pool);
+
+ uint256 postSpotPrice = pool.getSpotPriceSansFee(address(WETH), address(DAI));
+ assertEq(postSpotPrice, EXPECTED_FINAL_SPOT_PRICE);
+ }
+
+ // NOTE: reverting test, weighted pools are not supported
+ function testWeightedOrder() public {
+ IBCoWPool pool = IBCoWPool(address(weightedPool));
+
+ uint256 spotPrice = pool.getSpotPriceSansFee(address(WETH), address(DAI));
+ assertEq(spotPrice, INITIAL_SPOT_PRICE);
+
+ vm.expectRevert(ICOWAMMPoolHelper.PoolDoesNotExist.selector);
+ helper.order(address(pool), new uint256[](2));
+ }
+
+ function _executeHelperOrder(IBPool pool) internal {
+ address[] memory tokens = helper.tokens(address(pool));
+ uint256 daiIndex = 0;
+ uint256 wethIndex = 1;
+ assertEq(tokens.length, 2);
+ assertEq(tokens[daiIndex], address(DAI));
+ assertEq(tokens[wethIndex], address(WETH));
+
+ // Prepare the price vector used in the execution of the settlement in
+ // CoW Protocol. We skew the price by ~5% towards a cheaper WETH, so
+ // that the AMM wants to buy WETH.
+ uint256[] memory prices = new uint256[](2);
+ // Note: oracle price are expressed in the same format as prices in
+ // a call to `settle`, where the price vector is expressed so that
+ // if the first token is DAI and the second WETH then a price of 3000
+ // DAI per WETH means a price vector of [1, 3000] (if the decimals are
+ // different, as in WETH/USDC, then the atom amount is what counts).
+ prices[daiIndex] = INITIAL_WETH_BALANCE;
+ prices[wethIndex] = INITIAL_DAI_BALANCE * SKEWENESS_RATIO / 100;
+
+ // The helper generates the AMM order
+ GPv2Order.Data memory ammOrder;
+ GPv2Interaction.Data[] memory preInteractions;
+ GPv2Interaction.Data[] memory postInteractions;
+ bytes memory sig;
+ (ammOrder, preInteractions, postInteractions, sig) = helper.order(address(pool), prices);
+
+ // We expect a commit interaction in pre interactions
+ assertEq(preInteractions.length, 1);
+ assertEq(postInteractions.length, 0);
+
+ // Because of how we changed the price, we expect to buy WETH
+ assertEq(address(ammOrder.sellToken), address(DAI));
+ assertEq(address(ammOrder.buyToken), address(WETH));
+
+ // Check that the amounts and price aren't unreasonable. We changed the
+ // price by about 5%, so the amounts aren't expected to change
+ // significantly more (say, about 2.5% of the original balance).
+ assertApproxEqRel(ammOrder.sellAmount, INITIAL_DAI_BALANCE * 25 / 1000, TEN_PERCENT);
+ assertApproxEqRel(ammOrder.buyAmount, INITIAL_WETH_BALANCE * 25 / 1000, TEN_PERCENT);
+
+ GPv2Trade.Data[] memory trades = new GPv2Trade.Data[](1);
+
+ // pool's trade
+ trades[0] = GPv2Trade.Data({
+ sellTokenIndex: 0,
+ buyTokenIndex: 1,
+ receiver: ammOrder.receiver,
+ sellAmount: ammOrder.sellAmount,
+ buyAmount: ammOrder.buyAmount,
+ validTo: ammOrder.validTo,
+ appData: ammOrder.appData,
+ feeAmount: ammOrder.feeAmount,
+ flags: GPv2TradeEncoder.encodeFlags(ammOrder, GPv2Signing.Scheme.Eip1271),
+ executedAmount: ammOrder.sellAmount,
+ signature: sig
+ });
+
+ GPv2Interaction.Data[][3] memory interactions =
+ [new GPv2Interaction.Data[](1), new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0)];
+
+ interactions[0][0] = preInteractions[0];
+
+ // cast tokens array to IERC20 array
+ IERC20[] memory ierc20vec;
+ assembly {
+ ierc20vec := tokens
+ }
+
+ // finally, settle
+ vm.prank(solver);
+ settlement.settle(ierc20vec, prices, trades, interactions);
+ }
+}
diff --git a/test/integration/BCowPool.t.sol b/test/integration/BCowPool.t.sol
index 83a16922..da711035 100644
--- a/test/integration/BCowPool.t.sol
+++ b/test/integration/BCowPool.t.sol
@@ -8,6 +8,7 @@ import {GPv2Interaction} from '@cowprotocol/libraries/GPv2Interaction.sol';
import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
import {GPv2Trade} from '@cowprotocol/libraries/GPv2Trade.sol';
import {GPv2Signing} from '@cowprotocol/mixins/GPv2Signing.sol';
+
import {BCoWConst} from 'contracts/BCoWConst.sol';
import {BCoWFactory} from 'contracts/BCoWFactory.sol';
diff --git a/test/integration/DeploymentGas.t.sol b/test/integration/DeploymentGas.t.sol
index e2b32d95..d4604024 100644
--- a/test/integration/DeploymentGas.t.sol
+++ b/test/integration/DeploymentGas.t.sol
@@ -1,27 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
+import {BCoWFactory} from 'contracts/BCoWFactory.sol';
import {BFactory} from 'contracts/BFactory.sol';
import {GasSnapshot} from 'forge-gas-snapshot/GasSnapshot.sol';
import {Test} from 'forge-std/Test.sol';
+contract MockSolutionSettler {
+ address public vaultRelayer;
+ bytes32 public domainSeparator;
+}
+
contract DeploymentIntegrationGasTest is Test, GasSnapshot {
- BFactory public factory;
+ BFactory public bFactory;
+ BCoWFactory public bCowFactory;
+ address solutionSettler;
+ address deployer = makeAddr('deployer');
function setUp() public {
- factory = new BFactory();
+ vm.startPrank(deployer);
+ bFactory = new BFactory();
+
+ solutionSettler = address(new MockSolutionSettler());
+ bCowFactory = new BCoWFactory(solutionSettler, bytes32('appData'));
}
function testFactoryDeployment() public {
snapStart('newBFactory');
new BFactory();
snapEnd();
+
+ snapStart('newBCoWFactory');
+ new BCoWFactory(solutionSettler, bytes32('appData'));
+ snapEnd();
}
- function testDeployment() public {
+ function testPoolDeployment() public {
snapStart('newBPool');
- factory.newBPool();
+ bFactory.newBPool();
+ snapEnd();
+
+ snapStart('newBCoWPool');
+ bCowFactory.newBPool();
snapEnd();
}
}
diff --git a/test/invariants/PROPERTIES.md b/test/invariants/PROPERTIES.md
index c4c18501..823548da 100644
--- a/test/invariants/PROPERTIES.md
+++ b/test/invariants/PROPERTIES.md
@@ -1,14 +1,14 @@
| Properties | Type | Id | Halmos | Echidna |
| ------------------------------------------------------------------------------------------- | ------------------- | --- | ------ | ------- |
| BFactory should always be able to deploy new pools | Unit | 1 | [x] | [x] |
-| BFactory's blab should always be modifiable by the current blabs | Unit | 2 | [x] | [x] |
-| BFactory should always be able to transfer the BToken to the blab, if called by it | Unit | 3 | [x] | [x] |
+| BFactory's BDao should always be modifiable by the current BDao | Unit | 2 | [x] | [x] |
+| BFactory should always be able to transfer the BToken to the BDao, if called by it | Unit | 3 | [x] | [x] |
| the amount received can never be less than min amount out | Unit | 4 | :( | [x] |
| the amount spent can never be greater than max amount in | Unit | 5 | :( | [x] |
-| swap fee can only be 0 (cow pool) | Valid state | 6 | [x] | [x] |
+| swap fee can only be 0 (cow pool) | Valid state | 6 | | [x] |
| total weight can be up to 50e18 | Variable transition | 7 | [x] | [x] |
-| BToken increaseApproval should increase the approval of the address by the amount* | Variable transition | 8 | [x] | [x] |
-| BToken decreaseApproval should decrease the approval to max(old-amount, 0)* | Variable transition | 9 | [x] | [x] |
+| BToken increaseApproval should increase the approval of the address by the amount* | Variable transition | 8 | | [x] |
+| BToken decreaseApproval should decrease the approval to max(old-amount, 0)* | Variable transition | 9 | | [x] |
| a pool can either be finalized or not finalized | Valid state | 10 | | [x] |
| a finalized pool cannot switch back to non-finalized | State transition | 11 | | [x] |
| a non-finalized pool can only be finalized when the controller calls finalize() | State transition | 12 | [x] | [x] |
@@ -16,8 +16,8 @@
| an exact amount out is earned only if the amount in calculated in bmath is transfered | High level | 14 | :( | [x] |
| there can't be any amount out for a 0 amount in | High level | 15 | :( | [x] |
| the pool btoken can only be minted/burned in the join and exit operations | High level | 16 | | [x] |
-| a direct token transfer can never reduce the underlying amount of a given token per BPT | High level | 17 | :( | [x] |
-| the amount of underlying token when exiting should always be the amount calculated in bmath | High level | 18 | :( | [x] |
+| ~~a direct token transfer can never reduce the underlying amount of a given token per BPT~~ | High level | 17 | :( | # |
+| ~~the amount of underlying token when exiting should always be the amount calculated in bmath~~ | High level | 18 | :( | # |
| a swap can only happen when the pool is finalized | High level | 19 | | [x] |
| bounding and unbounding token can only be done on a non-finalized pool, by the controller | High level | 20 | [x] | [x] |
| there always should be between MIN_BOUND_TOKENS and MAX_BOUND_TOKENS bound in a pool | High level | 21 | | [x] |
@@ -30,10 +30,11 @@
> (**) [Trail of Bits ERC20 properties](https://github.com/crytic/properties?tab=readme-ov-file#erc20-tests)
-[ ] planed to implement and still to do
-
[x] implemented and tested
-
:( implemented but test not passing due to an external factor (tool limitation - eg halmos max unrolling loop, etc)
-
empty not implemented and will not be (design, etc)
+
`[ ]` planed to implement and still to do
+
`[x]` implemented and tested
+
`:(` implemented but test not passing due to an external factor (tool limitation - eg halmos max unrolling loop, etc)
+
`#` implemented but deprecated feature / property
+
`` empty not implemented and will not be (design, etc)
# Unit-test properties for the math libs (BNum and BMath):
@@ -86,7 +87,7 @@ bpow should be distributive over mult of the same base x^a * x^b == x^(a+b)
bpow should be distributive over mult of the same exp a^x * b^x == (a*b)^x
power of a power should mult the exp (x^a)^b == x^(a*b)
-## Untested (precision issues in test settingsq)
calcOutGivenIn should be inv with calcInGivenOut
-calcPoolOutGivenSingleIn should be inv with calcSingleInGivenPoolOut
-calcSingleOutGivenPoolIn should be inv with calcPoolInGivenSingleOut
+calcInGivenOut should be inv with calcOutGivenIn
+~~calcPoolOutGivenSingleIn should be inv with calcSingleInGivenPoolOut~~
+~~calcSingleOutGivenPoolIn should be inv with calcPoolInGivenSingleOut~~
diff --git a/test/invariants/fuzz/Protocol.t.sol b/test/invariants/fuzz/Protocol.t.sol
index 766d7f8f..8a389a1a 100644
--- a/test/invariants/fuzz/Protocol.t.sol
+++ b/test/invariants/fuzz/Protocol.t.sol
@@ -131,28 +131,28 @@ contract FuzzProtocol is EchidnaTest {
}
/// @custom:property-id 2
- /// @custom:property BFactory's blab should always be modifiable by the current blabs
- function fuzz_blabAlwaysModByBLab() public agentOrDeployer {
+ /// @custom:property BFactory's BDao should always be modifiable by the current BDaos
+ function fuzz_BDaoAlwaysModByBDao() public agentOrDeployer {
// Precondition
- address _currentBLab = factory.getBLabs();
+ address _currentBDao = factory.getBDao();
hevm.prank(currentCaller);
// Action
- try factory.setBLabs(address(123)) {
+ try factory.setBDao(address(123)) {
// Postcondition
- assert(_currentBLab == currentCaller);
+ assert(_currentBDao == currentCaller);
} catch {
- assert(_currentBLab != currentCaller);
+ assert(_currentBDao != currentCaller);
}
}
/* TODO: re-enable this test after fixing the hevm issue with SafeTransfer library
/// @custom:property-id 3
- /// @custom:property BFactory should always be able to transfer the BToken to the blab, if called by it
+ /// @custom:property BFactory should always be able to transfer the BToken to the BDao, if called by it
function fuzz_alwaysCollect() public agentOrDeployer {
// Precondition
- address _currentBLab = factory.getBLabs();
+ address _currentBDao = factory.getBDao();
if (address(pool) == address(0)) {
return;
@@ -163,9 +163,9 @@ contract FuzzProtocol is EchidnaTest {
// Action
try factory.collect(pool) {
// Postcondition
- assert(_currentBLab == currentCaller);
+ assert(_currentBDao == currentCaller);
} catch {
- assert(_currentBLab != currentCaller);
+ assert(_currentBDao != currentCaller);
}
}
*/
@@ -419,6 +419,7 @@ contract FuzzProtocol is EchidnaTest {
assert(ghost_bptMinted - ghost_bptBurned == pool.totalSupply());
}
+ /* NOTE: deprecated calcSingleOutGivenPoolIn
/// @custom:property-id 17
/// @custom:property a direct token transfer can never reduce the underlying amount of a given token per BPT
function fuzz_directTransfer(
@@ -493,6 +494,7 @@ contract FuzzProtocol is EchidnaTest {
assert(tokens[i].balanceOf(currentCaller) == _previousBalances[i] + _amountsToReceive[i]);
}
}
+ */
/// @custom:property-id 20
/// @custom:property bounding and unbounding token can only be done on a non-finalized pool, by the controller
diff --git a/test/invariants/symbolic/Protocol.t.sol b/test/invariants/symbolic/Protocol.t.sol
index db717e33..d67b4f9c 100644
--- a/test/invariants/symbolic/Protocol.t.sol
+++ b/test/invariants/symbolic/Protocol.t.sol
@@ -23,7 +23,6 @@ contract HalmosBalancer is HalmosTest {
BCoWPool pool;
address currentCaller = svm.createAddress('currentCaller');
- // address currentCaller = address(234);
constructor() {
solutionSettler = address(new MockSettler());
@@ -75,42 +74,42 @@ contract HalmosBalancer is HalmosTest {
}
/// @custom:property-id 2
- /// @custom:property BFactory's blab should always be modifiable by the current blabs
- function check_blabAlwaysModByBLab() public {
+ /// @custom:property BFactory's BDao should always be modifiable by the current BDao
+ function check_BDaoAlwaysModByBDao() public {
// Precondition
- address _currentBLab = factory.getBLabs();
+ address _currentBDao = factory.getBDao();
vm.prank(currentCaller);
// Action
- try factory.setBLabs(address(123)) {
+ try factory.setBDao(address(123)) {
// Postcondition
- assert(_currentBLab == currentCaller);
+ assert(_currentBDao == currentCaller);
} catch {
- assert(_currentBLab != currentCaller);
+ assert(_currentBDao != currentCaller);
}
}
/// @custom:property-id 3
- /// @custom:property BFactory should always be able to transfer the BToken to the blab, if called by it
+ /// @custom:property BFactory should always be able to transfer the BToken to the BDao, if called by it
function check_alwaysCollect() public {
// Precondition
- address _currentBLab = factory.getBLabs();
+ address _currentBDao = factory.getBDao();
vm.prank(currentCaller);
// Action
try factory.collect(pool) {
// Postcondition
- assert(_currentBLab == currentCaller);
+ assert(_currentBDao == currentCaller);
} catch {
- assert(_currentBLab != currentCaller);
+ assert(_currentBDao != currentCaller);
}
}
+
/// @custom:property-id 7
/// @custom:property total weight can be up to 50e18
/// @dev Only 2 tokens are used, to avoid hitting the limit in loop unrolling
-
function check_totalWeightMax(uint256[2] calldata _weights) public {
// Precondition
BCoWPool _pool = BCoWPool(address(factory.newBPool()));
diff --git a/test/manual-smock/MockBCoWFactory.sol b/test/manual-smock/MockBCoWFactory.sol
index 5e7baa5a..c4b2e427 100644
--- a/test/manual-smock/MockBCoWFactory.sol
+++ b/test/manual-smock/MockBCoWFactory.sol
@@ -5,6 +5,16 @@ import {BCoWFactory, BCoWPool, BFactory, IBCoWFactory, IBPool} from '../../src/c
import {Test} from 'forge-std/Test.sol';
contract MockBCoWFactory is BCoWFactory, Test {
+ // NOTE: manually added methods (immutable overrides not supported in smock)
+ function mock_call_APP_DATA(bytes32 _appData) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('APP_DATA()'), abi.encode(_appData));
+ }
+
+ function expectCall_APP_DATA() public {
+ vm.expectCall(address(this), abi.encodeWithSignature('APP_DATA()'));
+ }
+
+ // BCoWFactory methods
constructor(address solutionSettler, bytes32 appData) BCoWFactory(solutionSettler, appData) {}
function mock_call_logBCoWPool() public {
@@ -31,8 +41,39 @@ contract MockBCoWFactory is BCoWFactory, Test {
}
// MockBFactory methods
-
function set__isBPool(address _key0, bool _value) public {
_isBPool[_key0] = _value;
}
+
+ function call__isBPool(address _key0) public view returns (bool) {
+ return _isBPool[_key0];
+ }
+
+ function set__bDao(address __bDao) public {
+ _bDao = __bDao;
+ }
+
+ function call__bDao() public view returns (address) {
+ return _bDao;
+ }
+
+ function mock_call_newBPool(IBPool bPool) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('newBPool()'), abi.encode(bPool));
+ }
+
+ function mock_call_setBDao(address bDao) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('setBDao(address)', bDao), abi.encode());
+ }
+
+ function mock_call_collect(IBPool bPool) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('collect(IBPool)', bPool), abi.encode());
+ }
+
+ function mock_call_isBPool(address bPool, bool _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('isBPool(address)', bPool), abi.encode(_returnParam0));
+ }
+
+ function mock_call_getBDao(address _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('getBDao()'), abi.encode(_returnParam0));
+ }
}
diff --git a/test/manual-smock/MockBCoWHelper.sol b/test/manual-smock/MockBCoWHelper.sol
new file mode 100644
index 00000000..003b4ce2
--- /dev/null
+++ b/test/manual-smock/MockBCoWHelper.sol
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity ^0.8.0;
+
+import {
+ BCoWHelper,
+ BMath,
+ GPv2Interaction,
+ GPv2Order,
+ GetTradeableOrder,
+ IBCoWFactory,
+ IBCoWPool,
+ ICOWAMMPoolHelper,
+ IERC20
+} from '../../src/contracts/BCoWHelper.sol';
+import {Test} from 'forge-std/Test.sol';
+
+contract MockBCoWHelper is BCoWHelper, Test {
+ // NOTE: manually added methods (internal immutable exposers not supported in smock)
+ function call__APP_DATA() external view returns (bytes32) {
+ return _APP_DATA;
+ }
+
+ // NOTE: manually added method (public overrides not supported in smock)
+ function tokens(address pool) public view override returns (address[] memory tokens_) {
+ (bool _success, bytes memory _data) = address(this).staticcall(abi.encodeWithSignature('tokens(address)', pool));
+
+ if (_success) return abi.decode(_data, (address[]));
+ else return super.tokens(pool);
+ }
+
+ // NOTE: manually added method (public overrides not supported in smock)
+ function expectCall_tokens(address pool) public {
+ vm.expectCall(address(this), abi.encodeWithSignature('tokens(address)', pool));
+ }
+
+ // BCoWHelper methods
+ constructor(address factory_) BCoWHelper(factory_) {}
+
+ function mock_call_order(
+ address pool,
+ uint256[] calldata prices,
+ GPv2Order.Data memory order_,
+ GPv2Interaction.Data[] memory preInteractions,
+ GPv2Interaction.Data[] memory postInteractions,
+ bytes memory sig
+ ) public {
+ vm.mockCall(
+ address(this),
+ abi.encodeWithSignature('order(address,uint256[])', pool, prices),
+ abi.encode(order_, preInteractions, postInteractions, sig)
+ );
+ }
+
+ function mock_call_tokens(address pool, address[] memory tokens_) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('tokens(address)', pool), abi.encode(tokens_));
+ }
+}
diff --git a/test/manual-smock/MockBCoWPool.sol b/test/manual-smock/MockBCoWPool.sol
index de851269..f070bb0c 100644
--- a/test/manual-smock/MockBCoWPool.sol
+++ b/test/manual-smock/MockBCoWPool.sol
@@ -21,8 +21,18 @@ contract MockBCoWPool is BCoWPool, Test {
vm.expectCall(address(this), abi.encodeWithSignature('verify(GPv2Order.Data)', order));
}
- /// MockBCoWPool mock methods
+ // NOTE: manually added methods (immutable overrides not supported in smock)
+ function mock_call_SOLUTION_SETTLER_DOMAIN_SEPARATOR(bytes32 domainSeparator) public {
+ vm.mockCall(
+ address(this), abi.encodeWithSignature('SOLUTION_SETTLER_DOMAIN_SEPARATOR()'), abi.encode(domainSeparator)
+ );
+ }
+
+ function expectCall_SOLUTION_SETTLER_DOMAIN_SEPARATOR() public {
+ vm.expectCall(address(this), abi.encodeWithSignature('SOLUTION_SETTLER_DOMAIN_SEPARATOR()'));
+ }
+ /// MockBCoWPool mock methods
constructor(address cowSolutionSettler, bytes32 appData) BCoWPool(cowSolutionSettler, appData) {}
function mock_call_commit(bytes32 orderHash) public {
@@ -170,73 +180,19 @@ contract MockBCoWPool is BCoWPool, Test {
);
}
- function mock_call_joinswapExternAmountIn(
- address tokenIn,
- uint256 tokenAmountIn,
- uint256 minPoolAmountOut,
- uint256 poolAmountOut
- ) public {
- vm.mockCall(
- address(this),
- abi.encodeWithSignature(
- 'joinswapExternAmountIn(address,uint256,uint256)', tokenIn, tokenAmountIn, minPoolAmountOut
- ),
- abi.encode(poolAmountOut)
- );
- }
-
- function mock_call_joinswapPoolAmountOut(
- address tokenIn,
- uint256 poolAmountOut,
- uint256 maxAmountIn,
- uint256 tokenAmountIn
- ) public {
- vm.mockCall(
- address(this),
- abi.encodeWithSignature('joinswapPoolAmountOut(address,uint256,uint256)', tokenIn, poolAmountOut, maxAmountIn),
- abi.encode(tokenAmountIn)
- );
- }
-
- function mock_call_exitswapPoolAmountIn(
- address tokenOut,
- uint256 poolAmountIn,
- uint256 minAmountOut,
- uint256 tokenAmountOut
- ) public {
+ function mock_call_getSpotPrice(address tokenIn, address tokenOut, uint256 _returnParam0) public {
vm.mockCall(
address(this),
- abi.encodeWithSignature('exitswapPoolAmountIn(address,uint256,uint256)', tokenOut, poolAmountIn, minAmountOut),
- abi.encode(tokenAmountOut)
- );
- }
-
- function mock_call_exitswapExternAmountOut(
- address tokenOut,
- uint256 tokenAmountOut,
- uint256 maxPoolAmountIn,
- uint256 poolAmountIn
- ) public {
- vm.mockCall(
- address(this),
- abi.encodeWithSignature(
- 'exitswapExternAmountOut(address,uint256,uint256)', tokenOut, tokenAmountOut, maxPoolAmountIn
- ),
- abi.encode(poolAmountIn)
- );
- }
-
- function mock_call_getSpotPrice(address tokenIn, address tokenOut, uint256 spotPrice) public {
- vm.mockCall(
- address(this), abi.encodeWithSignature('getSpotPrice(address,address)', tokenIn, tokenOut), abi.encode(spotPrice)
+ abi.encodeWithSignature('getSpotPrice(address,address)', tokenIn, tokenOut),
+ abi.encode(_returnParam0)
);
}
- function mock_call_getSpotPriceSansFee(address tokenIn, address tokenOut, uint256 spotPrice) public {
+ function mock_call_getSpotPriceSansFee(address tokenIn, address tokenOut, uint256 _returnParam0) public {
vm.mockCall(
address(this),
abi.encodeWithSignature('getSpotPriceSansFee(address,address)', tokenIn, tokenOut),
- abi.encode(spotPrice)
+ abi.encode(_returnParam0)
);
}
@@ -244,20 +200,20 @@ contract MockBCoWPool is BCoWPool, Test {
vm.mockCall(address(this), abi.encodeWithSignature('isFinalized()'), abi.encode(_returnParam0));
}
- function mock_call_isBound(address t, bool _returnParam0) public {
- vm.mockCall(address(this), abi.encodeWithSignature('isBound(address)', t), abi.encode(_returnParam0));
+ function mock_call_isBound(address token, bool _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('isBound(address)', token), abi.encode(_returnParam0));
}
function mock_call_getNumTokens(uint256 _returnParam0) public {
vm.mockCall(address(this), abi.encodeWithSignature('getNumTokens()'), abi.encode(_returnParam0));
}
- function mock_call_getCurrentTokens(address[] memory tokens) public {
- vm.mockCall(address(this), abi.encodeWithSignature('getCurrentTokens()'), abi.encode(tokens));
+ function mock_call_getCurrentTokens(address[] memory _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('getCurrentTokens()'), abi.encode(_returnParam0));
}
- function mock_call_getFinalTokens(address[] memory tokens) public {
- vm.mockCall(address(this), abi.encodeWithSignature('getFinalTokens()'), abi.encode(tokens));
+ function mock_call_getFinalTokens(address[] memory _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('getFinalTokens()'), abi.encode(_returnParam0));
}
function mock_call_getDenormalizedWeight(address token, uint256 _returnParam0) public {
@@ -365,6 +321,84 @@ contract MockBCoWPool is BCoWPool, Test {
vm.expectCall(address(this), abi.encodeWithSignature('_afterFinalize()'));
}
+ function mock_call__pullPoolShare(address from, uint256 amount) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('_pullPoolShare(address,uint256)', from, amount), abi.encode());
+ }
+
+ function _pullPoolShare(address from, uint256 amount) internal override {
+ (bool _success, bytes memory _data) =
+ address(this).call(abi.encodeWithSignature('_pullPoolShare(address,uint256)', from, amount));
+
+ if (_success) return abi.decode(_data, ());
+ else return super._pullPoolShare(from, amount);
+ }
+
+ function call__pullPoolShare(address from, uint256 amount) public {
+ return _pullPoolShare(from, amount);
+ }
+
+ function expectCall__pullPoolShare(address from, uint256 amount) public {
+ vm.expectCall(address(this), abi.encodeWithSignature('_pullPoolShare(address,uint256)', from, 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 mock_call__burnPoolShare(uint256 amount) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('_burnPoolShare(uint256)', amount), abi.encode());
+ }
+
+ function _burnPoolShare(uint256 amount) internal override {
+ (bool _success, bytes memory _data) = address(this).call(abi.encodeWithSignature('_burnPoolShare(uint256)', amount));
+
+ if (_success) return abi.decode(_data, ());
+ else return super._burnPoolShare(amount);
+ }
+
+ function call__burnPoolShare(uint256 amount) public {
+ return _burnPoolShare(amount);
+ }
+
+ function expectCall__burnPoolShare(uint256 amount) public {
+ vm.expectCall(address(this), abi.encodeWithSignature('_burnPoolShare(uint256)', amount));
+ }
+
function mock_call__getLock(bytes32 value) public {
vm.mockCall(address(this), abi.encodeWithSignature('_getLock()'), abi.encode(value));
}
@@ -376,7 +410,7 @@ contract MockBCoWPool is BCoWPool, Test {
else return super._getLock();
}
- function call__getLock() public returns (bytes32 value) {
+ function call__getLock() public view returns (bytes32 value) {
return _getLock();
}
diff --git a/test/smock/MockBFactory.sol b/test/smock/MockBFactory.sol
index 7689e20e..ab754fa5 100644
--- a/test/smock/MockBFactory.sol
+++ b/test/smock/MockBFactory.sol
@@ -13,12 +13,12 @@ contract MockBFactory is BFactory, Test {
return _isBPool[_key0];
}
- function set__bLabs(address __bLabs) public {
- _bLabs = __bLabs;
+ function set__bDao(address __bDao) public {
+ _bDao = __bDao;
}
- function call__bLabs() public view returns (address) {
- return _bLabs;
+ function call__bDao() public view returns (address) {
+ return _bDao;
}
constructor() BFactory() {}
@@ -27,8 +27,8 @@ contract MockBFactory is BFactory, Test {
vm.mockCall(address(this), abi.encodeWithSignature('newBPool()'), abi.encode(bPool));
}
- function mock_call_setBLabs(address bLabs) public {
- vm.mockCall(address(this), abi.encodeWithSignature('setBLabs(address)', bLabs), abi.encode());
+ function mock_call_setBDao(address bDao) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('setBDao(address)', bDao), abi.encode());
}
function mock_call_collect(IBPool bPool) public {
@@ -39,8 +39,8 @@ contract MockBFactory is BFactory, Test {
vm.mockCall(address(this), abi.encodeWithSignature('isBPool(address)', bPool), abi.encode(_returnParam0));
}
- function mock_call_getBLabs(address _returnParam0) public {
- vm.mockCall(address(this), abi.encodeWithSignature('getBLabs()'), abi.encode(_returnParam0));
+ function mock_call_getBDao(address _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('getBDao()'), abi.encode(_returnParam0));
}
function mock_call__newBPool(IBPool bPool) public {
diff --git a/test/smock/MockBPool.sol b/test/smock/MockBPool.sol
index a4eb6d08..bae353f3 100644
--- a/test/smock/MockBPool.sol
+++ b/test/smock/MockBPool.sol
@@ -135,73 +135,19 @@ contract MockBPool is BPool, Test {
);
}
- function mock_call_joinswapExternAmountIn(
- address tokenIn,
- uint256 tokenAmountIn,
- uint256 minPoolAmountOut,
- uint256 poolAmountOut
- ) public {
- vm.mockCall(
- address(this),
- abi.encodeWithSignature(
- 'joinswapExternAmountIn(address,uint256,uint256)', tokenIn, tokenAmountIn, minPoolAmountOut
- ),
- abi.encode(poolAmountOut)
- );
- }
-
- function mock_call_joinswapPoolAmountOut(
- address tokenIn,
- uint256 poolAmountOut,
- uint256 maxAmountIn,
- uint256 tokenAmountIn
- ) public {
+ function mock_call_getSpotPrice(address tokenIn, address tokenOut, uint256 _returnParam0) public {
vm.mockCall(
address(this),
- abi.encodeWithSignature('joinswapPoolAmountOut(address,uint256,uint256)', tokenIn, poolAmountOut, maxAmountIn),
- abi.encode(tokenAmountIn)
- );
- }
-
- function mock_call_exitswapPoolAmountIn(
- address tokenOut,
- uint256 poolAmountIn,
- uint256 minAmountOut,
- uint256 tokenAmountOut
- ) public {
- vm.mockCall(
- address(this),
- abi.encodeWithSignature('exitswapPoolAmountIn(address,uint256,uint256)', tokenOut, poolAmountIn, minAmountOut),
- abi.encode(tokenAmountOut)
- );
- }
-
- function mock_call_exitswapExternAmountOut(
- address tokenOut,
- uint256 tokenAmountOut,
- uint256 maxPoolAmountIn,
- uint256 poolAmountIn
- ) public {
- vm.mockCall(
- address(this),
- abi.encodeWithSignature(
- 'exitswapExternAmountOut(address,uint256,uint256)', tokenOut, tokenAmountOut, maxPoolAmountIn
- ),
- abi.encode(poolAmountIn)
- );
- }
-
- function mock_call_getSpotPrice(address tokenIn, address tokenOut, uint256 spotPrice) public {
- vm.mockCall(
- address(this), abi.encodeWithSignature('getSpotPrice(address,address)', tokenIn, tokenOut), abi.encode(spotPrice)
+ abi.encodeWithSignature('getSpotPrice(address,address)', tokenIn, tokenOut),
+ abi.encode(_returnParam0)
);
}
- function mock_call_getSpotPriceSansFee(address tokenIn, address tokenOut, uint256 spotPrice) public {
+ function mock_call_getSpotPriceSansFee(address tokenIn, address tokenOut, uint256 _returnParam0) public {
vm.mockCall(
address(this),
abi.encodeWithSignature('getSpotPriceSansFee(address,address)', tokenIn, tokenOut),
- abi.encode(spotPrice)
+ abi.encode(_returnParam0)
);
}
@@ -209,20 +155,20 @@ contract MockBPool is BPool, Test {
vm.mockCall(address(this), abi.encodeWithSignature('isFinalized()'), abi.encode(_returnParam0));
}
- function mock_call_isBound(address t, bool _returnParam0) public {
- vm.mockCall(address(this), abi.encodeWithSignature('isBound(address)', t), abi.encode(_returnParam0));
+ function mock_call_isBound(address token, bool _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('isBound(address)', token), abi.encode(_returnParam0));
}
function mock_call_getNumTokens(uint256 _returnParam0) public {
vm.mockCall(address(this), abi.encodeWithSignature('getNumTokens()'), abi.encode(_returnParam0));
}
- function mock_call_getCurrentTokens(address[] memory tokens) public {
- vm.mockCall(address(this), abi.encodeWithSignature('getCurrentTokens()'), abi.encode(tokens));
+ function mock_call_getCurrentTokens(address[] memory _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('getCurrentTokens()'), abi.encode(_returnParam0));
}
- function mock_call_getFinalTokens(address[] memory tokens) public {
- vm.mockCall(address(this), abi.encodeWithSignature('getFinalTokens()'), abi.encode(tokens));
+ function mock_call_getFinalTokens(address[] memory _returnParam0) public {
+ vm.mockCall(address(this), abi.encodeWithSignature('getFinalTokens()'), abi.encode(_returnParam0));
}
function mock_call_getDenormalizedWeight(address token, uint256 _returnParam0) public {
diff --git a/test/unit/BCoWFactory.t.sol b/test/unit/BCoWFactory.t.sol
index 1ff8070a..9c2bfca2 100644
--- a/test/unit/BCoWFactory.t.sol
+++ b/test/unit/BCoWFactory.t.sol
@@ -24,15 +24,15 @@ contract BCoWFactoryTest is Test {
);
}
- function test_ConstructorWhenCalled(address _blabs, address _newSettler, bytes32 _appData) external {
- vm.prank(_blabs);
+ function test_ConstructorWhenCalled(address _bDao, address _newSettler, bytes32 _appData) external {
+ vm.prank(_bDao);
MockBCoWFactory _newFactory = new MockBCoWFactory(_newSettler, _appData);
// it should set solution settler
assertEq(_newFactory.SOLUTION_SETTLER(), _newSettler);
// it should set app data
assertEq(_newFactory.APP_DATA(), _appData);
- // it should set blabs
- assertEq(_newFactory.getBLabs(), _blabs);
+ // it should set BDao
+ assertEq(_newFactory.getBDao(), _bDao);
}
function test__newBPoolWhenCalled() external {
diff --git a/test/unit/BCoWFactory.tree b/test/unit/BCoWFactory.tree
index 37160019..daa4549a 100644
--- a/test/unit/BCoWFactory.tree
+++ b/test/unit/BCoWFactory.tree
@@ -2,7 +2,7 @@ BCoWFactoryTest::constructor
└── when called
├── it should set solution settler
├── it should set app data
- └── it should set blabs
+ └── it should set BDao
BCoWFactoryTest::_newBPool
└── when called
diff --git a/test/unit/BCoWHelper.t.sol b/test/unit/BCoWHelper.t.sol
new file mode 100644
index 00000000..23964575
--- /dev/null
+++ b/test/unit/BCoWHelper.t.sol
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {Test} from 'forge-std/Test.sol';
+import {MockBCoWHelper} from 'test/manual-smock/MockBCoWHelper.sol';
+
+import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
+import {IBPool} from 'interfaces/IBPool.sol';
+import {ISettlement} from 'interfaces/ISettlement.sol';
+
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+
+import {ICOWAMMPoolHelper} from '@cow-amm/interfaces/ICOWAMMPoolHelper.sol';
+import {GPv2Interaction} from '@cowprotocol/libraries/GPv2Interaction.sol';
+import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
+
+import {MockBCoWFactory} from 'test/manual-smock/MockBCoWFactory.sol';
+import {MockBCoWPool} from 'test/manual-smock/MockBCoWPool.sol';
+
+contract BCoWHelperTest is Test {
+ MockBCoWHelper helper;
+
+ MockBCoWFactory factory;
+ MockBCoWPool pool;
+ address invalidPool = makeAddr('invalidPool');
+ address[] tokens = new address[](2);
+ uint256[] priceVector = new uint256[](2);
+
+ uint256 constant VALID_WEIGHT = 1e18;
+ uint256 constant BASE = 1e18;
+
+ function setUp() external {
+ factory = new MockBCoWFactory(address(0), bytes32(0));
+
+ address solutionSettler = makeAddr('solutionSettler');
+ vm.mockCall(
+ solutionSettler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(bytes32('domainSeparator'))
+ );
+ vm.mockCall(
+ solutionSettler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(makeAddr('vaultRelayer'))
+ );
+ pool = new MockBCoWPool(makeAddr('solutionSettler'), bytes32(0));
+
+ // creating a valid pool setup
+ factory.mock_call_isBPool(address(pool), true);
+ tokens[0] = makeAddr('token0');
+ tokens[1] = makeAddr('token1');
+ pool.set__tokens(tokens);
+ pool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: VALID_WEIGHT}));
+ pool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: VALID_WEIGHT}));
+ pool.set__totalWeight(2 * VALID_WEIGHT);
+ pool.set__finalized(true);
+
+ priceVector[0] = 1e18;
+ priceVector[1] = 1.05e18;
+
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(priceVector[0]));
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(priceVector[1]));
+
+ factory.mock_call_APP_DATA(bytes32('appData'));
+ helper = new MockBCoWHelper(address(factory));
+ }
+
+ function test_ConstructorWhenCalled(bytes32 _appData) external {
+ factory.expectCall_APP_DATA();
+ factory.mock_call_APP_DATA(_appData);
+ helper = new MockBCoWHelper(address(factory));
+ // it should set factory
+ assertEq(helper.factory(), address(factory));
+ // it should set app data from factory
+ assertEq(helper.call__APP_DATA(), _appData);
+ }
+
+ function test_TokensRevertWhen_PoolIsNotRegisteredInFactory() external {
+ factory.mock_call_isBPool(address(pool), false);
+ // it should revert
+ vm.expectRevert(ICOWAMMPoolHelper.PoolDoesNotExist.selector);
+ helper.tokens(address(pool));
+ }
+
+ function test_TokensRevertWhen_PoolHasLessThan2Tokens() external {
+ address[] memory invalidTokens = new address[](1);
+ invalidTokens[0] = makeAddr('token0');
+ pool.set__tokens(invalidTokens);
+ // it should revert
+ vm.expectRevert(ICOWAMMPoolHelper.PoolDoesNotExist.selector);
+ helper.tokens(address(pool));
+ }
+
+ function test_TokensRevertWhen_PoolHasMoreThan2Tokens() external {
+ address[] memory invalidTokens = new address[](3);
+ invalidTokens[0] = makeAddr('token0');
+ invalidTokens[1] = makeAddr('token1');
+ invalidTokens[2] = makeAddr('token2');
+ pool.set__tokens(invalidTokens);
+ // it should revert
+ vm.expectRevert(ICOWAMMPoolHelper.PoolDoesNotExist.selector);
+ helper.tokens(address(pool));
+ }
+
+ function test_TokensRevertWhen_PoolTokensHaveDifferentWeights() external {
+ pool.mock_call_getNormalizedWeight(tokens[0], VALID_WEIGHT);
+ pool.mock_call_getNormalizedWeight(tokens[1], VALID_WEIGHT + 1);
+
+ vm.expectRevert(ICOWAMMPoolHelper.PoolDoesNotExist.selector);
+ // it should revert
+ helper.tokens(address(pool));
+ }
+
+ function test_TokensWhenPoolIsSupported() external view {
+ // it should return pool tokens
+ address[] memory returned = helper.tokens(address(pool));
+ assertEq(returned[0], tokens[0]);
+ assertEq(returned[1], tokens[1]);
+ }
+
+ function test_OrderRevertWhen_ThePoolIsNotSupported() external {
+ // it should revert
+ vm.expectRevert(ICOWAMMPoolHelper.PoolDoesNotExist.selector);
+ helper.order(invalidPool, priceVector);
+ }
+
+ function test_OrderWhenThePoolIsSupported(bytes32 domainSeparator) external {
+ // it should call tokens
+ helper.mock_call_tokens(address(pool), tokens);
+ helper.expectCall_tokens(address(pool));
+
+ // it should query the domain separator from the pool
+ pool.expectCall_SOLUTION_SETTLER_DOMAIN_SEPARATOR();
+ pool.mock_call_SOLUTION_SETTLER_DOMAIN_SEPARATOR(domainSeparator);
+
+ (
+ GPv2Order.Data memory order_,
+ GPv2Interaction.Data[] memory preInteractions,
+ GPv2Interaction.Data[] memory postInteractions,
+ bytes memory sig
+ ) = helper.order(address(pool), priceVector);
+
+ // it should return a valid pool order
+ assertEq(order_.receiver, GPv2Order.RECEIVER_SAME_AS_OWNER);
+ assertLe(order_.validTo, block.timestamp + 5 minutes);
+ assertEq(order_.feeAmount, 0);
+ assertEq(order_.appData, factory.APP_DATA());
+ assertEq(order_.kind, GPv2Order.KIND_SELL);
+ assertEq(order_.buyTokenBalance, GPv2Order.BALANCE_ERC20);
+ assertEq(order_.sellTokenBalance, GPv2Order.BALANCE_ERC20);
+
+ // it should return a commit pre-interaction
+ assertEq(preInteractions.length, 1);
+ assertEq(preInteractions[0].target, address(pool));
+ assertEq(preInteractions[0].value, 0);
+ bytes memory commitment = abi.encodeCall(IBCoWPool.commit, GPv2Order.hash(order_, domainSeparator));
+ assertEq(keccak256(preInteractions[0].callData), keccak256(commitment));
+
+ // it should return an empty post-interaction
+ assertTrue(postInteractions.length == 0);
+
+ // it should return a valid signature
+ bytes memory validSig = abi.encodePacked(pool, abi.encode(order_));
+ assertEq(keccak256(validSig), keccak256(sig));
+ }
+
+ function test_OrderGivenAPriceSkewenessToToken1(
+ uint256 priceSkewness,
+ uint256 balanceToken0,
+ uint256 balanceToken1
+ ) external {
+ // skew the price by max 50% (more could result in reverts bc of max swap ratio)
+ // avoids no-skewness revert
+ priceSkewness = bound(priceSkewness, BASE + 0.0001e18, 1.5e18);
+
+ balanceToken0 = bound(balanceToken0, 1e18, 1e27);
+ balanceToken1 = bound(balanceToken1, 1e18, 1e27);
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceToken0));
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceToken1));
+
+ // NOTE: the price of token 1 is increased by the skeweness
+ uint256[] memory prices = new uint256[](2);
+ prices[0] = balanceToken1;
+ prices[1] = balanceToken0 * priceSkewness / BASE;
+
+ // it should return a valid pool order
+ (GPv2Order.Data memory ammOrder,,,) = helper.order(address(pool), prices);
+
+ // it should buy token0
+ assertEq(address(ammOrder.buyToken), tokens[0]);
+
+ // it should return a valid pool order
+ // this call should not revert
+ pool.verify(ammOrder);
+ }
+
+ function test_OrderGivenAPriceSkewenessToToken0(
+ uint256 priceSkewness,
+ uint256 balanceToken0,
+ uint256 balanceToken1
+ ) external {
+ // skew the price by max 50% (more could result in reverts bc of max swap ratio)
+ // avoids no-skewness revert
+ priceSkewness = bound(priceSkewness, 0.5e18, BASE - 0.0001e18);
+
+ balanceToken0 = bound(balanceToken0, 1e18, 1e27);
+ balanceToken1 = bound(balanceToken1, 1e18, 1e27);
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceToken0));
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceToken1));
+
+ // NOTE: the price of token 1 is decrease by the skeweness
+ uint256[] memory prices = new uint256[](2);
+ prices[0] = balanceToken1;
+ prices[1] = balanceToken0 * priceSkewness / BASE;
+
+ // it should return a valid pool order
+ (GPv2Order.Data memory ammOrder,,,) = helper.order(address(pool), prices);
+
+ // it should buy token1
+ assertEq(address(ammOrder.buyToken), tokens[1]);
+
+ // it should return a valid pool order
+ // this call should not revert
+ pool.verify(ammOrder);
+ }
+}
diff --git a/test/unit/BCoWHelper.tree b/test/unit/BCoWHelper.tree
new file mode 100644
index 00000000..e27a5207
--- /dev/null
+++ b/test/unit/BCoWHelper.tree
@@ -0,0 +1,33 @@
+BCoWHelperTest::constructor
+└── when called
+ ├── it should set factory
+ └── it should set app data from factory
+
+BCoWHelperTest::tokens
+├── when pool is not registered in factory
+│ └── it should revert
+├── when pool has less than 2 tokens
+│ └── it should revert
+├── when pool has more than 2 tokens
+│ └── it should revert
+├── when pool tokens have different weights
+│ └── it should revert
+└── when pool is supported
+ └── it should return pool tokens
+
+BCoWHelperTest::order
+├── when the pool is not supported
+│ └── it should revert
+├── when the pool is supported
+│ ├── it should call tokens
+│ ├── it should query the domain separator from the pool
+│ ├── it should return a valid pool order
+│ ├── it should return a commit pre-interaction
+│ ├── it should return an empty post-interaction
+│ └── it should return a valid signature
+├── given a price skeweness to token1
+│ ├── it should buy token0
+│ └── it should return a valid pool order
+└── given a price skeweness to token0
+ ├── it should buy token1
+ └── it should return a valid pool order
diff --git a/test/unit/BCoWPool.t.sol b/test/unit/BCoWPool.t.sol
deleted file mode 100644
index 7c7e566c..00000000
--- a/test/unit/BCoWPool.t.sol
+++ /dev/null
@@ -1,354 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.25;
-
-import {BasePoolTest, SwapExactAmountInUtils} 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';
-import {BCoWConst} from 'contracts/BCoWConst.sol';
-import {IBCoWFactory} from 'interfaces/IBCoWFactory.sol';
-import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
-import {IBPool} from 'interfaces/IBPool.sol';
-import {ISettlement} from 'interfaces/ISettlement.sol';
-import {MockBCoWPool} from 'test/manual-smock/MockBCoWPool.sol';
-import {MockBPool} from 'test/smock/MockBPool.sol';
-
-abstract contract BaseCoWPoolTest is BasePoolTest, BCoWConst {
- address public cowSolutionSettler = makeAddr('cowSolutionSettler');
- bytes32 public domainSeparator = bytes32(bytes2(0xf00b));
- address public vaultRelayer = makeAddr('vaultRelayer');
- bytes32 public appData = bytes32('appData');
-
- GPv2Order.Data correctOrder;
-
- MockBCoWPool bCoWPool;
-
- function setUp() public virtual override {
- super.setUp();
- 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);
- bPool = MockBPool(address(bCoWPool));
- _setRandomTokens(TOKENS_AMOUNT);
- correctOrder = GPv2Order.Data({
- sellToken: IERC20(tokens[1]),
- buyToken: IERC20(tokens[0]),
- receiver: GPv2Order.RECEIVER_SAME_AS_OWNER,
- sellAmount: 0,
- buyAmount: 0,
- validTo: uint32(block.timestamp + 1 minutes),
- appData: appData,
- feeAmount: 0,
- kind: GPv2Order.KIND_SELL,
- partiallyFillable: false,
- sellTokenBalance: GPv2Order.BALANCE_ERC20,
- buyTokenBalance: GPv2Order.BALANCE_ERC20
- });
- }
-}
-
-contract BCoWPool_Unit_Constructor is BaseCoWPoolTest {
- function test_Set_SolutionSettler(address _settler) public {
- assumeNotForgeAddress(_settler);
- vm.mockCall(_settler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(domainSeparator));
- vm.mockCall(_settler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(vaultRelayer));
- MockBCoWPool pool = new MockBCoWPool(_settler, appData);
- assertEq(address(pool.SOLUTION_SETTLER()), _settler);
- }
-
- function test_Set_DomainSeparator(address _settler, bytes32 _separator) public {
- assumeNotForgeAddress(_settler);
- vm.mockCall(_settler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(_separator));
- vm.mockCall(_settler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(vaultRelayer));
- MockBCoWPool pool = new MockBCoWPool(_settler, appData);
- assertEq(pool.SOLUTION_SETTLER_DOMAIN_SEPARATOR(), _separator);
- }
-
- function test_Set_VaultRelayer(address _settler, address _relayer) public {
- assumeNotForgeAddress(_settler);
- vm.mockCall(_settler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(domainSeparator));
- vm.mockCall(_settler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(_relayer));
- MockBCoWPool pool = new MockBCoWPool(_settler, appData);
- assertEq(pool.VAULT_RELAYER(), _relayer);
- }
-
- function test_Set_AppData(bytes32 _appData) public {
- MockBCoWPool pool = new MockBCoWPool(cowSolutionSettler, _appData);
- assertEq(pool.APP_DATA(), _appData);
- }
-}
-
-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);
- vm.prank(sender);
- vm.expectRevert(IBCoWPool.CommitOutsideOfSettlement.selector);
- bCoWPool.commit(orderHash);
- }
-
- function test_Revert_CommitmentAlreadySet(bytes32 _existingCommitment, bytes32 _newCommitment) public {
- vm.assume(_existingCommitment != bytes32(0));
- bCoWPool.call__setLock(_existingCommitment);
- vm.prank(cowSolutionSettler);
- vm.expectRevert(IBPool.BPool_Reentrancy.selector);
- bCoWPool.commit(_newCommitment);
- }
-
- function test_Call_SetLock(bytes32 orderHash) public {
- bCoWPool.expectCall__setLock(orderHash);
- vm.prank(cowSolutionSettler);
- bCoWPool.commit(orderHash);
- }
-
- function test_Set_ReentrancyLock(bytes32 orderHash) public {
- vm.prank(cowSolutionSettler);
- bCoWPool.commit(orderHash);
- assertEq(bCoWPool.call__getLock(), orderHash);
- }
-}
-
-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();
- 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());
- bCoWPool.finalize();
- }
-
- modifier happyPath(GPv2Order.Data memory _order) {
- // sets the order appData to the one defined at deployment (setUp)
- _order.appData = appData;
-
- // stores the order hash in the transient storage slot
- bytes32 _orderHash = GPv2Order.hash(_order, domainSeparator);
- bCoWPool.call__setLock(_orderHash);
- _;
- }
-
- function test_Revert_OrderWithWrongAppdata(GPv2Order.Data memory _order, bytes32 _appData) public {
- vm.assume(_appData != appData);
- _order.appData = _appData;
- bytes32 _orderHash = GPv2Order.hash(_order, domainSeparator);
- vm.expectRevert(IBCoWPool.AppDataDoesNotMatch.selector);
- bCoWPool.isValidSignature(_orderHash, abi.encode(_order));
- }
-
- function test_Revert_OrderSignedWithWrongDomainSeparator(
- GPv2Order.Data memory _order,
- bytes32 _differentDomainSeparator
- ) public happyPath(_order) {
- vm.assume(_differentDomainSeparator != domainSeparator);
- bytes32 _orderHash = GPv2Order.hash(_order, _differentDomainSeparator);
- vm.expectRevert(IBCoWPool.OrderDoesNotMatchMessageHash.selector);
- bCoWPool.isValidSignature(_orderHash, abi.encode(_order));
- }
-
- function test_Revert_OrderWithUnrelatedSignature(
- GPv2Order.Data memory _order,
- bytes32 _orderHash
- ) public happyPath(_order) {
- vm.expectRevert(IBCoWPool.OrderDoesNotMatchMessageHash.selector);
- bCoWPool.isValidSignature(_orderHash, abi.encode(_order));
- }
-
- function test_Revert_OrderHashDifferentFromCommitment(
- GPv2Order.Data memory _order,
- bytes32 _differentCommitment
- ) public happyPath(_order) {
- bCoWPool.call__setLock(_differentCommitment);
- bytes32 _orderHash = GPv2Order.hash(_order, domainSeparator);
- vm.expectRevert(IBCoWPool.OrderDoesNotMatchCommitmentHash.selector);
- bCoWPool.isValidSignature(_orderHash, abi.encode(_order));
- }
-
- function test_Call_Verify(GPv2Order.Data memory _order) public happyPath(_order) {
- bytes32 _orderHash = GPv2Order.hash(_order, domainSeparator);
- bCoWPool.mock_call_verify(_order);
- bCoWPool.expectCall_verify(_order);
- bCoWPool.isValidSignature(_orderHash, abi.encode(_order));
- }
-
- function test_Return_MagicValue(GPv2Order.Data memory _order) public happyPath(_order) {
- bytes32 _orderHash = GPv2Order.hash(_order, domainSeparator);
- bCoWPool.mock_call_verify(_order);
- assertEq(bCoWPool.isValidSignature(_orderHash, abi.encode(_order)), IERC1271.isValidSignature.selector);
- }
-}
diff --git a/test/unit/BCoWPool/BCoWPool.t.sol b/test/unit/BCoWPool/BCoWPool.t.sol
new file mode 100644
index 00000000..5c8c79f3
--- /dev/null
+++ b/test/unit/BCoWPool/BCoWPool.t.sol
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';
+
+import {BCoWPoolBase} from './BCoWPoolBase.t.sol';
+
+import {IBCoWFactory} from 'interfaces/IBCoWFactory.sol';
+
+import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
+import {IBPool} from 'interfaces/IBPool.sol';
+import {ISettlement} from 'interfaces/ISettlement.sol';
+import {MockBCoWPool} from 'test/manual-smock/MockBCoWPool.sol';
+
+contract BCoWPool is BCoWPoolBase {
+ bytes32 public commitmentValue = bytes32(uint256(0xf00ba5));
+ 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_ConstructorWhenCalled(
+ address _settler,
+ bytes32 _separator,
+ address _relayer,
+ bytes32 _appData
+ ) external {
+ assumeNotForgeAddress(_settler);
+ vm.mockCall(_settler, abi.encodePacked(ISettlement.domainSeparator.selector), abi.encode(_separator));
+ vm.mockCall(_settler, abi.encodePacked(ISettlement.vaultRelayer.selector), abi.encode(_relayer));
+ // it should query the solution settler for the domain separator
+ vm.expectCall(_settler, abi.encodePacked(ISettlement.domainSeparator.selector));
+ // it should query the solution settler for the vault relayer
+ vm.expectCall(_settler, abi.encodePacked(ISettlement.vaultRelayer.selector));
+ MockBCoWPool pool = new MockBCoWPool(_settler, _appData);
+ // it should set the solution settler
+ assertEq(address(pool.SOLUTION_SETTLER()), _settler);
+ // it should set the domain separator
+ assertEq(pool.SOLUTION_SETTLER_DOMAIN_SEPARATOR(), _separator);
+ // it should set the vault relayer
+ assertEq(pool.VAULT_RELAYER(), _relayer);
+ // it should set the app data
+ assertEq(pool.APP_DATA(), _appData);
+ }
+
+ function test__afterFinalizeWhenCalled() 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__afterFinalizeWhenFactorysLogBCoWPoolDoesNotRevert() external {
+ // it returns
+ bCoWPool.call__afterFinalize();
+ }
+
+ function test__afterFinalizeWhenFactorysLogBCoWPoolReverts(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();
+ }
+
+ function test_CommitRevertWhen_ReentrancyLockIsSet(bytes32 lockValue) external {
+ vm.assume(lockValue != _MUTEX_FREE);
+ bCoWPool.call__setLock(lockValue);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bCoWPool.commit(commitmentValue);
+ }
+
+ function test_CommitRevertWhen_SenderIsNotSolutionSettler(address caller) external {
+ vm.assume(caller != cowSolutionSettler);
+ vm.prank(caller);
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(IBCoWPool.CommitOutsideOfSettlement.selector));
+ bCoWPool.commit(commitmentValue);
+ }
+
+ function test_CommitWhenPreconditionsAreMet(bytes32 commitmentValue_) external {
+ vm.prank(cowSolutionSettler);
+ bCoWPool.commit(commitmentValue_);
+ // it should set the transient reentrancy lock
+ assertEq(bCoWPool.call__getLock(), commitmentValue_);
+ }
+}
diff --git a/test/unit/BCoWPool/BCoWPool.tree b/test/unit/BCoWPool/BCoWPool.tree
new file mode 100644
index 00000000..9331eb1f
--- /dev/null
+++ b/test/unit/BCoWPool/BCoWPool.tree
@@ -0,0 +1,25 @@
+BCoWPool::Constructor
+└── when called
+ ├── it should set the solution settler
+ ├── it should query the solution settler for the domain separator
+ ├── it should set the domain separator
+ ├── it should query the solution settler for the vault relayer
+ ├── it should set the vault relayer
+ └── it should set the app data
+
+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
+
+BCoWPool::Commit
+├── when reentrancy lock is set
+│ └──it should revert
+├── when sender is not solution settler
+│ └──it should revert
+└── when preconditions are met
+ └── it should set the transient reentrancy lock
diff --git a/test/unit/BCoWPool/BCoWPoolBase.t.sol b/test/unit/BCoWPool/BCoWPoolBase.t.sol
new file mode 100644
index 00000000..db328d1d
--- /dev/null
+++ b/test/unit/BCoWPool/BCoWPoolBase.t.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {BPoolBase} from '../BPool/BPoolBase.t.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');
+ MockBCoWPool bCoWPool;
+
+ function setUp() public virtual override {
+ super.setUp();
+ 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);
+ }
+}
diff --git a/test/unit/BCoWPool/BCoWPool_IsValidSignature.t.sol b/test/unit/BCoWPool/BCoWPool_IsValidSignature.t.sol
new file mode 100644
index 00000000..ba1a58bb
--- /dev/null
+++ b/test/unit/BCoWPool/BCoWPool_IsValidSignature.t.sol
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {IERC20} from '@cowprotocol/interfaces/IERC20.sol';
+
+import {GPv2Order} from '@cowprotocol/libraries/GPv2Order.sol';
+import {IERC1271} from '@openzeppelin/contracts/interfaces/IERC1271.sol';
+
+import {BCoWPoolBase} from './BCoWPoolBase.t.sol';
+import {IBCoWPool} from 'interfaces/IBCoWPool.sol';
+
+contract BCoWPoolIsValidSignature is BCoWPoolBase {
+ GPv2Order.Data validOrder;
+ bytes32 validHash;
+
+ function setUp() public virtual override {
+ super.setUp();
+ // only set up the values that are checked in this method
+ validOrder.appData = appData;
+ validHash = GPv2Order.hash(validOrder, domainSeparator);
+
+ bCoWPool.mock_call_verify(validOrder);
+ }
+
+ function test_RevertWhen_OrdersAppdataIsDifferentThanOneSetAtConstruction(bytes32 appData_) external {
+ vm.assume(appData != appData_);
+ validOrder.appData = appData_;
+ // it should revert
+ vm.expectRevert(IBCoWPool.AppDataDoesNotMatch.selector);
+ bCoWPool.isValidSignature(validHash, abi.encode(validOrder));
+ }
+
+ function test_RevertWhen_OrderHashDoesNotMatchHashedOrder(bytes32 orderHash) external {
+ vm.assume(orderHash != validHash);
+ // it should revert
+ vm.expectRevert(IBCoWPool.OrderDoesNotMatchMessageHash.selector);
+ bCoWPool.isValidSignature(orderHash, abi.encode(validOrder));
+ }
+
+ function test_RevertWhen_HashedOrderDoesNotMatchCommitment(bytes32 commitment) external {
+ vm.assume(validHash != commitment);
+ bCoWPool.call__setLock(commitment);
+ // it should revert
+ vm.expectRevert(IBCoWPool.OrderDoesNotMatchCommitmentHash.selector);
+ bCoWPool.isValidSignature(validHash, abi.encode(validOrder));
+ }
+
+ function test_WhenPreconditionsAreMet() external {
+ // can't do it in setUp because transient storage is wiped in between
+ bCoWPool.call__setLock(validHash);
+ // it calls verify
+ bCoWPool.expectCall_verify(validOrder);
+ // it returns EIP-1271 magic value
+ assertEq(bCoWPool.isValidSignature(validHash, abi.encode(validOrder)), IERC1271.isValidSignature.selector);
+ }
+}
diff --git a/test/unit/BCoWPool/BCoWPool_IsValidSignature.tree b/test/unit/BCoWPool/BCoWPool_IsValidSignature.tree
new file mode 100644
index 00000000..524e8290
--- /dev/null
+++ b/test/unit/BCoWPool/BCoWPool_IsValidSignature.tree
@@ -0,0 +1,10 @@
+BCoWPool::IsValidSignature
+├── when orders appdata is different than one set at construction
+│ └── it should revert
+├── when orderHash does not match hashed order
+│ └── it should revert
+├── when hashed order does not match commitment
+│ └── it should revert
+└── when preconditions are met
+ ├── it calls verify
+ └── it returns EIP-1271 magic value
diff --git a/test/unit/BCoWPool/BCoWPool_Verify.t.sol b/test/unit/BCoWPool/BCoWPool_Verify.t.sol
new file mode 100644
index 00000000..fce924be
--- /dev/null
+++ b/test/unit/BCoWPool/BCoWPool_Verify.t.sol
@@ -0,0 +1,136 @@
+// 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:
+ address public tokenIn;
+ address public tokenOut;
+ 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();
+ tokenIn = tokens[0];
+ tokenOut = tokens[1];
+ 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);
+ }
+}
diff --git a/test/unit/BCoWPool/BCoWPool_Verify.tree b/test/unit/BCoWPool/BCoWPool_Verify.tree
new file mode 100644
index 00000000..56187a2a
--- /dev/null
+++ b/test/unit/BCoWPool/BCoWPool_Verify.tree
@@ -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
diff --git a/test/unit/BFactory.t.sol b/test/unit/BFactory.t.sol
index b1426432..698f8823 100644
--- a/test/unit/BFactory.t.sol
+++ b/test/unit/BFactory.t.sol
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: UNLICENSED
+// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import {IERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
@@ -21,11 +21,11 @@ contract BFactoryTest is Test {
factory = new MockBFactory();
}
- function test_ConstructorWhenCalled(address _blabs) external {
- vm.prank(_blabs);
+ function test_ConstructorWhenCalled(address _bDao) external {
+ vm.prank(_bDao);
MockBFactory newFactory = new MockBFactory();
- // it should set BLabs
- assertEq(newFactory.getBLabs(), _blabs);
+ // it should set BDao
+ assertEq(newFactory.getBDao(), _bDao);
}
function test_NewBPoolWhenCalled(address _deployer, address _newBPool) external {
@@ -60,55 +60,52 @@ contract BFactoryTest is Test {
assertEq(_newBPool.code, _expectedCode);
}
- function test_SetBLabsRevertWhen_TheSenderIsNotTheCurrentBLabs(address _caller) external {
+ function test_SetBDaoRevertWhen_TheSenderIsNotTheCurrentBDao(address _caller) external {
vm.assume(_caller != factoryDeployer);
// it should revert
- vm.expectRevert(IBFactory.BFactory_NotBLabs.selector);
+ vm.expectRevert(IBFactory.BFactory_NotBDao.selector);
vm.prank(_caller);
- factory.setBLabs(makeAddr('newBLabs'));
+ factory.setBDao(makeAddr('newBDao'));
}
- modifier whenTheSenderIsTheCurrentBLabs() {
+ modifier whenTheSenderIsTheCurrentBDao() {
vm.startPrank(factoryDeployer);
_;
}
- function test_SetBLabsRevertWhen_TheAddressIsZero() external whenTheSenderIsTheCurrentBLabs {
+ function test_SetBDaoRevertWhen_TheAddressIsZero() external whenTheSenderIsTheCurrentBDao {
// it should revert
vm.expectRevert(IBFactory.BFactory_AddressZero.selector);
- factory.setBLabs(address(0));
+ factory.setBDao(address(0));
}
- function test_SetBLabsWhenTheAddressIsNotZero(address _newBLabs) external whenTheSenderIsTheCurrentBLabs {
- vm.assume(_newBLabs != address(0));
+ function test_SetBDaoWhenTheAddressIsNotZero(address _newBDao) external whenTheSenderIsTheCurrentBDao {
+ vm.assume(_newBDao != address(0));
- // it should emit a BLabsSet event
+ // it should emit a BDaoSet event
vm.expectEmit(address(factory));
- emit IBFactory.LOG_BLABS(factoryDeployer, _newBLabs);
+ emit IBFactory.LOG_BDAO(factoryDeployer, _newBDao);
- factory.setBLabs(_newBLabs);
+ factory.setBDao(_newBDao);
- // it should set the new bLabs address
- assertEq(factory.getBLabs(), _newBLabs);
+ // it should set the new bDao address
+ assertEq(factory.getBDao(), _newBDao);
}
- function test_CollectRevertWhen_TheSenderIsNotTheCurrentBLabs(address _caller) external {
+ function test_CollectRevertWhen_TheSenderIsNotTheCurrentBDao(address _caller) external {
vm.assume(_caller != factoryDeployer);
// it should revert
- vm.expectRevert(IBFactory.BFactory_NotBLabs.selector);
+ vm.expectRevert(IBFactory.BFactory_NotBDao.selector);
vm.prank(_caller);
factory.collect(IBPool(makeAddr('pool')));
}
- function test_CollectWhenTheSenderIsTheCurrentBLabs(uint256 _factoryBTBalance)
- external
- whenTheSenderIsTheCurrentBLabs
- {
+ function test_CollectWhenTheSenderIsTheCurrentBDao(uint256 _factoryBTBalance) external whenTheSenderIsTheCurrentBDao {
address _mockPool = makeAddr('pool');
vm.mockCall(_mockPool, abi.encodeCall(IERC20.balanceOf, address(factory)), abi.encode(_factoryBTBalance));
vm.mockCall(_mockPool, abi.encodeCall(IERC20.transfer, (factoryDeployer, _factoryBTBalance)), abi.encode(true));
@@ -116,7 +113,7 @@ contract BFactoryTest is Test {
// it should get the pool's btoken balance of the factory
vm.expectCall(_mockPool, abi.encodeCall(IERC20.balanceOf, address(factory)));
- // it should transfer the btoken balance of the factory to BLabs
+ // it should transfer the btoken balance of the factory to BDao
vm.expectCall(_mockPool, abi.encodeCall(IERC20.transfer, (factoryDeployer, _factoryBTBalance)));
factory.collect(IBPool(_mockPool));
@@ -124,7 +121,7 @@ contract BFactoryTest is Test {
function test_CollectRevertWhen_TheBtokenTransferFails(uint256 _factoryBTBalance)
external
- whenTheSenderIsTheCurrentBLabs
+ whenTheSenderIsTheCurrentBDao
{
address _mockPool = makeAddr('pool');
vm.mockCall(_mockPool, abi.encodeCall(IERC20.balanceOf, address(factory)), abi.encode(_factoryBTBalance));
diff --git a/test/unit/BFactory.tree b/test/unit/BFactory.tree
index bd2dce21..02f1fd16 100644
--- a/test/unit/BFactory.tree
+++ b/test/unit/BFactory.tree
@@ -1,6 +1,6 @@
BFactoryTest::constructor
└── when called
- └── it should set the deployer as BLabs
+ └── it should set the deployer as BDao
BFactoryTest::newBPool
└── when called
@@ -14,22 +14,22 @@ BFactoryTest::_newBPool
└── when called
└── it should deploy a new BPool
-BFactoryTest::setBLabs
-├── when the sender is not the current BLabs
+BFactoryTest::setBDao
+├── when the sender is not the current BDao
│ └── it should revert
-└── when the sender is the current BLabs
+└── when the sender is the current BDao
├── when the address is zero
│ └── it should revert
└── when the address is not zero
- ├── it should set the new BLabs address
- └── it should emit a BLabsSet event
+ ├── it should set the new BDao address
+ └── it should emit a BDaoSet event
BFactoryTest::collect
-├── when the sender is not the current BLabs
+├── when the sender is not the current BDao
│ └── it should revert
-└── when the sender is the current BLabs
+└── when the sender is the current BDao
├── it should get the pool's btoken balance of the factory
- ├── it should transfer the btoken balance of the factory to BLabs
+ ├── it should transfer the btoken balance of the factory to BDao
└── when the btoken transfer fails
└── it should revert
diff --git a/test/unit/BMath.t.sol b/test/unit/BMath.t.sol
index 25158305..59c7190f 100644
--- a/test/unit/BMath.t.sol
+++ b/test/unit/BMath.t.sol
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: UNLICENSED
+// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import {BConst} from 'contracts/BConst.sol';
@@ -113,15 +113,6 @@ contract BMathTest is Test, BConst {
bMath.calcOutGivenIn(balanceIn, weightIn, balanceOut, weightOut, amountIn, _swapFee);
}
- function test_CalcOutGivenInWhenSwapFeeEqualsBONE() external virtual {
- uint256 _swapFee = BONE;
-
- // it should return zero
- uint256 _amountOut = bMath.calcOutGivenIn(balanceIn, weightIn, balanceOut, weightOut, amountIn, _swapFee);
-
- assertEq(_amountOut, 0);
- }
-
function test_CalcOutGivenInRevertWhen_TokenAmountInTooBig(uint256 _amountIn) external {
_amountIn = bound(_amountIn, type(uint256).max / (BONE - swapFee) + 1, type(uint256).max);
@@ -154,6 +145,15 @@ contract BMathTest is Test, BConst {
bMath.calcOutGivenIn(_balanceIn, weightIn, balanceOut, weightOut, amountIn, _swapFee);
}
+ function test_CalcOutGivenInWhenSwapFeeEqualsBONE() external virtual {
+ uint256 _swapFee = BONE;
+
+ // it should return zero
+ uint256 _amountOut = bMath.calcOutGivenIn(balanceIn, weightIn, balanceOut, weightOut, amountIn, _swapFee);
+
+ assertEq(_amountOut, 0);
+ }
+
function test_CalcOutGivenInWhenTokenWeightInIsZero() external virtual {
uint256 _weightIn = 0;
@@ -328,214 +328,4 @@ contract BMathTest is Test, BConst {
assertEq(_amountIn, 15.584961142617097267e18);
}
-
- function test_CalcPoolOutGivenSingleInRevertWhen_TokenBalanceInIsZero() external {
- uint256 _balanceIn = 0;
-
- // it should revert
- // division by zero
- vm.expectRevert(BNum.BNum_DivZero.selector);
-
- bMath.calcPoolOutGivenSingleIn(_balanceIn, weightIn, poolSupply, totalWeight, amountIn, swapFee);
- }
-
- function test_CalcPoolOutGivenSingleInWhenTokenWeightInIsZero() external virtual {
- uint256 _weightIn = 0;
-
- // it should return zero
- uint256 _poolOut = bMath.calcPoolOutGivenSingleIn(balanceIn, _weightIn, poolSupply, totalWeight, amountIn, swapFee);
-
- assertEq(_poolOut, 0);
- }
-
- function test_CalcPoolOutGivenSingleInRevertWhen_TotalWeightIsZero() external virtual {
- uint256 _totalWeight = 0;
-
- // it should revert
- // division by zero
- vm.expectRevert(BNum.BNum_DivZero.selector);
-
- uint256 _poolOut = bMath.calcPoolOutGivenSingleIn(balanceIn, weightIn, poolSupply, _totalWeight, amountIn, swapFee);
-
- assertEq(_poolOut, 0);
- }
-
- function test_CalcPoolOutGivenSingleInWhenSwapFeeIsZero() external virtual {
- // it should return correct value
- // ((( ai + bi ) / bi ) ^ (wi/wT)) * pS - pS
- // ((( 5 + 20 ) / 20 ) ^ (1/10)) * 100 - 100 = 2.2565182564...
- uint256 _poolOut = bMath.calcPoolOutGivenSingleIn(balanceIn, weightIn, poolSupply, totalWeight, amountIn, 0);
-
- assertEq(_poolOut, 2.2565182579165133e18);
- }
-
- function test_CalcPoolOutGivenSingleInWhenSwapFeeIsNonZero() external virtual {
- // it should return correct value
- // ((( ai * (1 - ((1-(wi/wT))*sf)) + bi) / bi ) ^ (wi/wT)) * pS - pS
- // ((( 5 * (1 - ((1-(1/10))*0.1)) + 20) / 20 ) ^ (1/10)) * 100 - 100 = 2.07094840224...
- uint256 _poolOut = bMath.calcPoolOutGivenSingleIn(balanceIn, weightIn, poolSupply, totalWeight, amountIn, swapFee);
-
- assertEq(_poolOut, 2.0709484026610259e18);
- }
-
- function test_CalcSingleInGivenPoolOutRevertWhen_TotalWeightIsZero() external {
- uint256 _totalWeight = 0;
-
- // it should revert
- // division by zero
- vm.expectRevert(BNum.BNum_DivZero.selector);
-
- bMath.calcSingleInGivenPoolOut(balanceIn, weightIn, poolSupply, _totalWeight, amountOut, swapFee);
- }
-
- function test_CalcSingleInGivenPoolOutWhenSwapFeeIsZero() external virtual {
- // it should return correct value
- // (((pS + ao) / pS) ^ (wT / wi)) * bi - bi
- // (((100 + 7) / 100) ^ (10 / 1)) * 20 - 20 = 19.3430271458...
- uint256 _amountIn = bMath.calcSingleInGivenPoolOut(balanceIn, weightIn, poolSupply, totalWeight, amountOut, 0);
-
- assertEq(_amountIn, 19.34302714579130644e18);
- }
-
- function test_CalcSingleInGivenPoolOutWhenSwapFeeIsNonZero() external virtual {
- // it should return correct value
- // ((((pS + ao) / pS) ^ (wT/wi)) * bi - bi) / (1 - (1 - (wi/wT)) * sf)
- // ((((100 + 7) / 100) ^ (10/1)) * 20 - 20) / (1 - (1 - (1/10)) * 0.1) = 21.2560737866...
- uint256 _amountIn = bMath.calcSingleInGivenPoolOut(balanceIn, weightIn, poolSupply, totalWeight, amountOut, swapFee);
-
- assertEq(_amountIn, 21.256073786583853231e18);
- }
-
- function test_CalcSingleOutGivenPoolInRevertWhen_PoolSupplyIsZero() external {
- uint256 _poolSupply = 0;
-
- // it should revert
- // division by zero
- vm.expectRevert(BNum.BNum_SubUnderflow.selector);
-
- bMath.calcSingleOutGivenPoolIn(balanceOut, weightOut, _poolSupply, totalWeight, amountIn, swapFee);
- }
-
- function test_CalcSingleOutGivenPoolInRevertWhen_TotalWeightIsZero() external {
- uint256 _totalWeight = 0;
-
- // it should revert
- // division by zero
- vm.expectRevert(BNum.BNum_DivZero.selector);
-
- bMath.calcSingleOutGivenPoolIn(balanceOut, weightOut, poolSupply, _totalWeight, amountIn, swapFee);
- }
-
- function test_CalcSingleOutGivenPoolInWhenTokenBalanceOutIsZero() external virtual {
- uint256 _balanceOut = 0;
-
- // it should return zero
- uint256 _amountOut =
- bMath.calcSingleOutGivenPoolIn(_balanceOut, weightOut, poolSupply, totalWeight, amountIn, swapFee);
-
- assertEq(_amountOut, 0);
- }
-
- function test_CalcSingleOutGivenPoolInWhenSwapFeeAndExitFeeAreZero() external virtual {
- // it should return correct value
- // bo - ((pS - ai)/pS)^(wT/wo) * bo
- // 30 - ((100 - 5)/100)^(10/2) * 30 = 6.786571875
- uint256 _amountOut = bMath.calcSingleOutGivenPoolIn(balanceOut, weightOut, poolSupply, totalWeight, amountIn, 0);
-
- assertEq(_amountOut, 6.786571875e18);
- }
-
- function test_CalcSingleOutGivenPoolInWhenSwapFeeIsZeroAndExitFeeIsNonZero() external {
- vm.skip(true); // NOTE: EXIT_FEE is hardcoded to 0
- // it should return correct value
- // bo - ((pS - (ai * (1 - ef))/pS)^(wT/wo) * bo
- }
-
- function test_CalcSingleOutGivenPoolInWhenSwapFeeIsNonZeroAndExitFeeIsZero() external virtual {
- // it should return correct value
- // (bo - ((pS - ai/pS)^(wT/wo) * bo) * (1 - ((1 - (wo/wT)) * sf))
- // (30 - ((100 - 5)/100)^(10/2) * 30) * (1 - ((1 - (2/10)) * 0.1)) = 6.243646125...
- uint256 _amountOut =
- bMath.calcSingleOutGivenPoolIn(balanceOut, weightOut, poolSupply, totalWeight, amountIn, swapFee);
-
- assertEq(_amountOut, 6.243646125e18);
- }
-
- function test_CalcSingleOutGivenPoolInWhenSwapFeeAndExitFeeAreNonZero() external {
- vm.skip(true); // NOTE: EXIT_FEE is hardcoded to 0
- // it should return correct value
- // (bo - ((pS - (ai * (1 - ef))/pS)^(wT/wo) * bo) * (1 - ((1 - (wo/wT)) * sf))
- }
-
- function test_CalcPoolInGivenSingleOutRevertWhen_TokenBalanceOutIsZero() external {
- uint256 _balanceOut = 0;
-
- // it should revert
- // subtraction underflow
- vm.expectRevert(BNum.BNum_SubUnderflow.selector);
-
- bMath.calcPoolInGivenSingleOut(_balanceOut, weightOut, poolSupply, totalWeight, amountOut, swapFee);
- }
-
- function test_CalcPoolInGivenSingleOutRevertWhen_SwapFeeIs1AndTokenWeightOutIsZero() external {
- uint256 _swapFee = BONE;
- uint256 _weightOut = 0;
-
- // it should revert
- // division by zero
- vm.expectRevert(BNum.BNum_DivZero.selector);
-
- bMath.calcPoolInGivenSingleOut(balanceOut, _weightOut, poolSupply, totalWeight, amountOut, _swapFee);
- }
-
- function test_CalcPoolInGivenSingleOutWhenTokenAmountOutIsZero() external virtual {
- uint256 _amountOut = 0;
-
- // it should return zero
- uint256 _amountIn =
- bMath.calcPoolInGivenSingleOut(balanceOut, weightOut, poolSupply, totalWeight, _amountOut, swapFee);
-
- assertEq(_amountIn, 0);
- }
-
- function test_CalcPoolInGivenSingleOutWhenPoolSupplyIsZero() external virtual {
- uint256 _poolSupply = 0;
-
- // it should return zero
- uint256 _amountIn =
- bMath.calcPoolInGivenSingleOut(balanceOut, weightOut, _poolSupply, totalWeight, amountOut, swapFee);
-
- assertEq(_amountIn, 0);
- }
-
- function test_CalcPoolInGivenSingleOutWhenSwapFeeAndExitFeeAreZero() external virtual {
- // it should return correct value
- // pS - (( (bo - ao) / bo ) ^ (wo/wT)) * pS
- // 100 - (( (30 - 7) / 30 ) ^ (2/10)) * 100 = 5.1753351805...
- uint256 _amountIn = bMath.calcPoolInGivenSingleOut(balanceOut, weightOut, poolSupply, totalWeight, amountOut, 0);
-
- assertEq(_amountIn, 5.1753351791902224e18);
- }
-
- function test_CalcPoolInGivenSingleOutWhenSwapFeeIsZeroAndExitFeeIsNonZero() external {
- vm.skip(true); // NOTE: EXIT_FEE is hardcoded to 0
- // it should return correct value
- // (pS - (( (bo - ao) / bo ) ^ (wo/wT)) * pS) / (1 - ef)
- }
-
- function test_CalcPoolInGivenSingleOutWhenSwapFeeIsNonZeroAndExitFeeIsZero() external virtual {
- // it should return correct value
- // pS - (( (bo - (ao / ( 1 - ((1 - (wo/wT)) * sf) ))) / bo ) ^ (wo/wT)) * pS
- // 100 - (( (30 - (7 / ( 1 - ((1 - (2/10)) * 0.1) ))) / 30 ) ^ (2/10)) * 100 = 5.682641831...
- uint256 _amountIn =
- bMath.calcPoolInGivenSingleOut(balanceOut, weightOut, poolSupply, totalWeight, amountOut, swapFee);
-
- assertEq(_amountIn, 5.6826418299618119e18);
- }
-
- function test_CalcPoolInGivenSingleOutWhenSwapFeeAndExitFeeAreNonZero() external {
- vm.skip(true); // NOTE: EXIT_FEE is hardcoded to 0
- // it should return correct value
- // (pS - (( (bo - (ao / ( 1 - ((1 - (wo/wT)) * sf) ))) / bo ) ^ (wo/wT)) * pS) / (1 - ef)
- }
}
diff --git a/test/unit/BMath.tree b/test/unit/BMath.tree
index be0417b9..a2d2520b 100644
--- a/test/unit/BMath.tree
+++ b/test/unit/BMath.tree
@@ -5,9 +5,9 @@ BMathTest::calcSpotPrice
│ └── it should revert // division by zero
├── when weighted token balance out is zero
│ └── it should revert // division by zero
-├── when swapFee greater than BONE
+├── when swap fee greater than BONE
│ └── it should revert // subtraction underflow
-├── when swapFee equals BONE
+├── when swap fee equals BONE
│ └── it should revert // division by zero
├── when swap fee is zero
│ └── it should return correct value
@@ -21,14 +21,14 @@ BMathTest::calcOutGivenIn
│ └── it should revert // division by zero
├── when swap fee greater than BONE
│ └── it should revert // subtraction underflow
-├── when swap fee equals BONE
-│ └── it should return zero
├── when token amount in too big
│ └── it should revert // ai * (1 - sf) > uint256 max
├── when token balance in and amount in are zero
-│ └── it should revert // bi + (ai * (1 - swapFee)) = 0
+│ └── it should revert // bi + (ai * (1 - sf)) = 0
├── when token balance in is zero and swap fee equals BONE
-│ └── it should revert // bi + (ai * (1 - swapFee)) = 0
+│ └── it should revert // bi + (ai * (1 - sf)) = 0
+├── when swap fee equals BONE
+│ └── it should return zero
├── when token weight in is zero
│ └── it should return zero
├── when token weights are equal
@@ -53,9 +53,9 @@ BMathTest::calcInGivenOut
│ └── it should revert // subtraction underflow
├── when token amount out equals token balance out
│ └── it should revert // division by zero
-├── when swapFee greater than BONE
+├── when swap fee greater than BONE
│ └── it should revert // subtraction underflow
-├── when swapFee equals BONE
+├── when swap fee equals BONE
│ └── it should revert // division by zero
├── when token weight out is zero
│ └── it should return zero
@@ -72,70 +72,4 @@ BMathTest::calcInGivenOut
│ └── bi * (((bo/(bo-ao))^(wo/wi) - 1)))
└── when unequal weights and swap fee is non zero
└── it should return correct value
- └── bi * (((bo/(bo-ao))^(wo/wi) - 1))) / (1 - sf)
-
-BMathTest::calcPoolOutGivenSingleIn
-├── when token balance in is zero
-│ └── it should revert // division by zero
-├── when token weight in is zero
-│ └── it should return zero
-├── when total weight is zero
-│ └── it should revert // division by zero
-├── when swap fee is zero
-│ └── it should return correct value
-│ └── ((( ai + bi ) / bi ) ^ (wi/wT)) * pS - pS
-└── when swap fee is non zero
- └── it should return correct value
- └── ((( ai * (1 - ((1-(wi/wT))*sf)) + bi) / bi ) ^ (wi/wT)) * pS - pS
-
-BMathTest::calcSingleInGivenPoolOut
-├── when total weight is zero
-│ └── it should revert // division by zero
-├── when swap fee is zero
-│ └── it should return correct value
-│ └── (((pS + ao) / pS) ^ (wT/wi))*bi - bi
-└── when swap fee is non zero
- └── it should return correct value
- └── ((((pS + ao) / pS) ^ (wT/wi))*bi - bi) / (1 - ((1 - (wi/wT)) * sf))
-
-BMathTest::calcSingleOutGivenPoolIn
-├── when pool supply is zero
-│ └── it should revert // division by zero
-├── when total weight is zero
-│ └── it should revert // division by zero
-├── when token balance out is zero
-│ └── it should return zero
-├── when swap fee and exit fee are zero
-│ └── it should return correct value
-│ └── bo - ((pS - ai)/pS)^(wT/wo) * bo
-├── when swap fee is zero and exit fee is non zero
-│ └── it should return correct value
-│ └── bo - ((pS - (ai * (1 - ef))/pS)^(wT/wo) * bo
-├── when swap fee is non zero and exit fee is zero
-│ └── it should return correct value
-│ └── (bo - ((pS - ai/pS)^(wT/wo) * bo) * (1 - ((1 - (wo/wT)) * sf))
-└── when swap fee and exit fee are non zero
- └── it should return correct value
- └── (bo - ((pS - (ai * (1 - ef))/pS)^(wT/wo) * bo) * (1 - ((1 - (wo/wT)) * sf))
-
-BMathTest::calcPoolInGivenSingleOut
-├── when token balance out is zero
-│ └── it should revert // subtraction underflow
-├── when swap fee is 1 and token weight out is zero
-│ └── it should revert // division by zero
-├── when token amount out is zero
-│ └── it should return zero
-├── when pool supply is zero
-│ └── it should return zero
-├── when swap fee and exit fee are zero
-│ └── it should return correct value
-│ └── pS - (( (bo - ao) / bo ) ^ (wo/wT)) * pS
-├── when swap fee is zero and exit fee is non zero
-│ └── it should return correct value
-│ └── (pS - (( (bo - ao) / bo ) ^ (wo/wT)) * pS) / (1 - ef)
-├── when swap fee is non zero and exit fee is zero
-│ └── it should return correct value
-│ └── pS - (( (bo - (ao / ( 1 - ((1 - (wo/wT)) * sf) ))) / bo ) ^ (wo/wT)) * pS
-└── when swap fee and exit fee are non zero
- └── it should return correct value
- └── (pS - (( (bo - (ao / ( 1 - ((1 - (wo/wT)) * sf) ))) / bo ) ^ (wo/wT)) * pS) / (1 - ef)
\ No newline at end of file
+ └── bi * (((bo/(bo-ao))^(wo/wi) - 1))) / (1 - sf)
\ No newline at end of file
diff --git a/test/unit/BNum.t.sol b/test/unit/BNum.t.sol
index 58041f73..097b275a 100644
--- a/test/unit/BNum.t.sol
+++ b/test/unit/BNum.t.sol
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: UNLICENSED
+// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import {BConst} from 'contracts/BConst.sol';
@@ -165,8 +165,8 @@ contract BNumTest is Test, BConst {
}
function test_BmulRevertWhen_PassingAAndBTooBig(uint256 _a, uint256 _b) external {
- _a = bound(_a, 1, type(uint256).max);
- _b = bound(_b, _a == 1 ? type(uint256).max : type(uint256).max / _a + 1, type(uint256).max);
+ _a = bound(_a, type(uint256).max / 2, type(uint256).max);
+ _b = bound(_b, type(uint256).max / 2, type(uint256).max);
// it should revert
// a * b > uint256 max
@@ -175,14 +175,14 @@ contract BNumTest is Test, BConst {
bNum.call_bmul(_a, _b);
}
- function test_BmulRevertWhen_PassingAMulBTooBig(uint256 _a, uint256 _b) external {
- _a = bound(_a, 1, type(uint256).max);
- _b = bound(_b, (type(uint256).max - (BONE / 2)) / _a + 1, type(uint256).max);
+ function test_BmulRevertWhen_PassingAMulBTooBig() external {
+ // type(uint256).max - BONE/2 < (2^248 - 1)*2^8 < type(uint256).max
+ uint256 _a = 2 ** 248 - 1;
+ uint256 _b = 2 ** 8;
// it should revert
// a * b + BONE / 2 > uint256 max
vm.expectRevert(BNum.BNum_MulOverflow.selector);
-
bNum.call_bmul(_a, _b);
}
@@ -334,12 +334,29 @@ contract BNumTest is Test, BConst {
}
function test_BpowWhenPassingKnownValues() external {
- // it should return correct value
- // 1.01 ^ 3 = 1.030301
- uint256 _a = 1.01e18;
- uint256 _b = 3e18;
-
- uint256 _result = bNum.call_bpow(_a, _b);
- assertEq(_result, 1.030301e18);
+ uint256 testcasesCount = 5;
+ uint256[] memory bases = new uint256[](testcasesCount);
+ bases[0] = 1.01e18;
+ bases[1] = 0.03e18;
+ bases[2] = 0.4e18;
+ bases[3] = 1.5e18;
+ bases[4] = 1.2e18;
+ uint256[] memory exponents = new uint256[](testcasesCount);
+ exponents[0] = 3e18;
+ exponents[1] = 1.01e18;
+ exponents[2] = 4.1e18;
+ exponents[3] = 9e18;
+ exponents[4] = 0.003e18;
+
+ uint256[] memory results = new uint256[](testcasesCount);
+ results[0] = 1.030301e18;
+ results[1] = 0.02896626284766446e18;
+ results[2] = 0.02335855453582031e18;
+ results[3] = 38.443359375e18;
+ results[4] = 1.000547114282833518e18;
+ for (uint256 i = 0; i < testcasesCount; i++) {
+ uint256 _result = bNum.call_bpow(bases[i], exponents[i]);
+ assertApproxEqAbs(_result, results[i], BPOW_PRECISION);
+ }
}
}
diff --git a/test/unit/BPool.t.sol b/test/unit/BPool.t.sol
deleted file mode 100644
index 4611daf6..00000000
--- a/test/unit/BPool.t.sol
+++ /dev/null
@@ -1,2551 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.25;
-
-import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
-import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
-
-import {BPool} from 'contracts/BPool.sol';
-import {IBPool} from 'interfaces/IBPool.sol';
-import {MockBPool} from 'test/smock/MockBPool.sol';
-
-import {BConst} from 'contracts/BConst.sol';
-import {BMath} from 'contracts/BMath.sol';
-import {Test} from 'forge-std/Test.sol';
-import {Pow} from 'test/utils/Pow.sol';
-import {Utils} from 'test/utils/Utils.sol';
-
-abstract contract BasePoolTest is Test, BConst, Utils, BMath {
- MockBPool public bPool;
-
- // Deploy this external contract to perform a try-catch when calling bpow.
- // If the call fails, it means that the function overflowed, then we reject the fuzzed inputs
- Pow public pow = new Pow();
-
- function setUp() public virtual {
- bPool = new MockBPool();
-
- // Create fake tokens
- address[] memory _tokensToAdd = _getDeterministicTokenArray(TOKENS_AMOUNT);
- for (uint256 i = 0; i < tokens.length; i++) {
- tokens[i] = _tokensToAdd[i];
- }
- }
-
- function _setRandomTokens(uint256 _length) internal returns (address[] memory _tokensToAdd) {
- _tokensToAdd = _getDeterministicTokenArray(_length);
- for (uint256 i = 0; i < _length; i++) {
- _setRecord(_tokensToAdd[i], IBPool.Record({bound: true, index: i, denorm: 0}));
- }
- _setTokens(_tokensToAdd);
- }
-
- 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.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.transferFrom.selector), abi.encode(true));
- }
-
- function _mockPoolBalance(address _token, uint256 _balance) internal {
- vm.mockCall(_token, abi.encodeWithSelector(IERC20.balanceOf.selector, address(bPool)), abi.encode(_balance));
- }
-
- function _setTokens(address[] memory _tokens) internal {
- bPool.set__tokens(_tokens);
- }
-
- function _setRecord(address _token, IBPool.Record memory _record) internal {
- bPool.set__records(_token, _record);
- }
-
- function _setSwapFee(uint256 _swapFee) internal {
- bPool.set__swapFee(_swapFee);
- }
-
- function _setFinalize(bool _isFinalized) internal {
- bPool.set__finalized(_isFinalized);
- }
-
- function _setPoolBalance(address _user, uint256 _balance) internal {
- deal(address(bPool), _user, _balance, true);
- }
-
- function _setTotalSupply(uint256 _totalSupply) internal {
- _setPoolBalance(address(0), _totalSupply);
- }
-
- function _setTotalWeight(uint256 _totalWeight) internal {
- bPool.set__totalWeight(_totalWeight);
- }
-
- function _expectRevertByReentrancy() internal {
- // Assert that the contract is accessible
- assertEq(bPool.call__getLock(), _MUTEX_FREE);
- // Simulate ongoing call to the contract
- bPool.call__setLock(_MUTEX_TAKEN);
-
- vm.expectRevert(IBPool.BPool_Reentrancy.selector);
- }
-
- function _expectSetReentrancyLock() internal {
- // Assert that the contract is accessible
- assertEq(bPool.call__getLock(), _MUTEX_FREE);
- // Expect reentrancy lock to be set
- bPool.expectCall__setLock(_MUTEX_TAKEN);
- }
-
- function _assumeCalcSpotPrice(
- uint256 _tokenInBalance,
- uint256 _tokenInDenorm,
- uint256 _tokenOutBalance,
- uint256 _tokenOutDenorm,
- uint256 _swapFee
- ) internal pure {
- vm.assume(_tokenInDenorm > 0);
- vm.assume(_tokenInBalance < type(uint256).max / BONE);
- vm.assume(_tokenInBalance * BONE < type(uint256).max - (_tokenInDenorm / 2));
-
- uint256 _numer = bdiv(_tokenInBalance, _tokenInDenorm);
- vm.assume(_tokenOutDenorm > 0);
- vm.assume(_tokenOutBalance < type(uint256).max / BONE);
- vm.assume(_tokenOutBalance * BONE < type(uint256).max - (_tokenOutDenorm / 2));
-
- uint256 _denom = bdiv(_tokenOutBalance, _tokenOutDenorm);
- vm.assume(_denom > 0);
- vm.assume(_numer < type(uint256).max / BONE);
- vm.assume(_numer * BONE < type(uint256).max - (_denom / 2));
- vm.assume(_swapFee <= BONE);
-
- uint256 _ratio = bdiv(_numer, _denom);
- vm.assume(bsub(BONE, _swapFee) > 0);
-
- uint256 _scale = bdiv(BONE, bsub(BONE, _swapFee));
- vm.assume(_ratio < type(uint256).max / _scale);
- }
-
- function _assumeCalcInGivenOut(
- uint256 _tokenOutDenorm,
- uint256 _tokenInDenorm,
- uint256 _tokenOutBalance,
- uint256 _tokenAmountOut,
- uint256 _tokenInBalance
- ) internal pure {
- uint256 _weightRatio = bdiv(_tokenOutDenorm, _tokenInDenorm);
- uint256 _diff = bsub(_tokenOutBalance, _tokenAmountOut);
- uint256 _y = bdiv(_tokenOutBalance, _diff);
- uint256 _foo = bpow(_y, _weightRatio);
- vm.assume(bsub(_foo, BONE) < type(uint256).max / _tokenInBalance);
- }
-
- function _assumeCalcOutGivenIn(uint256 _tokenInBalance, uint256 _tokenAmountIn, uint256 _swapFee) internal pure {
- uint256 _adjustedIn = bsub(BONE, _swapFee);
- _adjustedIn = bmul(_tokenAmountIn, _adjustedIn);
- vm.assume(_tokenInBalance < type(uint256).max / BONE);
- vm.assume(_tokenInBalance * BONE < type(uint256).max - (badd(_tokenInBalance, _adjustedIn) / 2));
- }
-
- function _assumeCalcPoolOutGivenSingleIn(
- uint256 _tokenInDenorm,
- uint256 _tokenInBalance,
- uint256 _tokenAmountIn,
- uint256 _swapFee,
- uint256 _totalWeight,
- uint256 _totalSupply
- ) internal pure {
- uint256 _normalizedWeight = bdiv(_tokenInDenorm, _totalWeight);
- vm.assume(_normalizedWeight < bdiv(MAX_WEIGHT, MAX_TOTAL_WEIGHT));
-
- uint256 _zaz = bmul(bsub(BONE, _normalizedWeight), _swapFee);
- uint256 _tokenAmountInAfterFee = bmul(_tokenAmountIn, bsub(BONE, _zaz));
- uint256 _newTokenBalanceIn = badd(_tokenInBalance, _tokenAmountInAfterFee);
- vm.assume(_newTokenBalanceIn < type(uint256).max / BONE);
- vm.assume(_newTokenBalanceIn > _tokenInBalance);
-
- uint256 _tokenInRatio = bdiv(_newTokenBalanceIn, _tokenInBalance);
- uint256 _poolRatio = bpow(_tokenInRatio, _normalizedWeight);
- vm.assume(_poolRatio < type(uint256).max / _totalSupply);
- }
-
- function _assumeCalcSingleInGivenPoolOut(
- uint256 _tokenInBalance,
- uint256 _tokenInDenorm,
- uint256 _poolSupply,
- uint256 _totalWeight,
- uint256 _poolAmountOut
- ) internal view {
- uint256 _normalizedWeight = bdiv(_tokenInDenorm, _totalWeight);
- uint256 _newPoolSupply = badd(_poolSupply, _poolAmountOut);
- vm.assume(_newPoolSupply < type(uint256).max / BONE);
- vm.assume(_newPoolSupply * BONE < type(uint256).max - (_poolSupply / 2)); // bdiv require
-
- uint256 _poolRatio = bdiv(_newPoolSupply, _poolSupply);
- vm.assume(_poolRatio < MAX_BPOW_BASE);
- vm.assume(BONE > _normalizedWeight);
-
- uint256 _boo = bdiv(BONE, _normalizedWeight);
- uint256 _tokenRatio;
- try pow.pow(_poolRatio, _boo) returns (uint256 _result) {
- // pow didn't overflow
- _tokenRatio = _result;
- } catch {
- // pow did an overflow. Reject this inputs
- vm.assume(false);
- }
-
- vm.assume(_tokenRatio < type(uint256).max / _tokenInBalance);
- }
-
- function _assumeCalcSingleOutGivenPoolIn(
- uint256 _tokenOutBalance,
- uint256 _tokenOutDenorm,
- uint256 _poolSupply,
- uint256 _totalWeight,
- uint256 _poolAmountIn,
- uint256 _swapFee
- ) internal pure {
- uint256 _normalizedWeight = bdiv(_tokenOutDenorm, _totalWeight);
- uint256 _exitFee = bsub(BONE, EXIT_FEE);
- vm.assume(_poolAmountIn < type(uint256).max / _exitFee);
-
- uint256 _poolAmountInAfterExitFee = bmul(_poolAmountIn, _exitFee);
- uint256 _newPoolSupply = bsub(_poolSupply, _poolAmountInAfterExitFee);
- vm.assume(_newPoolSupply < type(uint256).max / BONE);
- vm.assume(_newPoolSupply * BONE < type(uint256).max - (_poolSupply / 2)); // bdiv require
-
- uint256 _poolRatio = bdiv(_newPoolSupply, _poolSupply);
- vm.assume(_poolRatio < MAX_BPOW_BASE);
- vm.assume(_poolRatio > MIN_BPOW_BASE);
- vm.assume(BONE > _normalizedWeight);
-
- uint256 _tokenOutRatio = bpow(_poolRatio, bdiv(BONE, _normalizedWeight));
- vm.assume(_tokenOutRatio < type(uint256).max / _tokenOutBalance);
-
- uint256 _newTokenOutBalance = bmul(_tokenOutRatio, _tokenOutBalance);
- uint256 _tokenAmountOutBeforeSwapFee = bsub(_tokenOutBalance, _newTokenOutBalance);
- uint256 _zaz = bmul(bsub(BONE, _normalizedWeight), _swapFee);
- vm.assume(_tokenAmountOutBeforeSwapFee < type(uint256).max / bsub(BONE, _zaz));
- }
-
- function _assumeCalcPoolInGivenSingleOut(
- uint256 _tokenOutBalance,
- uint256 _tokenOutDenorm,
- uint256 _poolSupply,
- uint256 _totalWeight,
- uint256 _tokenAmountOut,
- uint256 _swapFee
- ) internal pure {
- uint256 _normalizedWeight = bdiv(_tokenOutDenorm, _totalWeight);
- vm.assume(BONE > _normalizedWeight);
-
- uint256 _zoo = bsub(BONE, _normalizedWeight);
- uint256 _zar = bmul(_zoo, _swapFee);
- uint256 _tokenAmountOutBeforeSwapFee = bdiv(_tokenAmountOut, bsub(BONE, _zar));
- uint256 _newTokenOutBalance = bsub(_tokenOutBalance, _tokenAmountOutBeforeSwapFee);
- vm.assume(_newTokenOutBalance < type(uint256).max / _tokenOutBalance);
-
- uint256 _tokenOutRatio = bdiv(_newTokenOutBalance, _tokenOutBalance);
- uint256 _poolRatio = bpow(_tokenOutRatio, _normalizedWeight);
- vm.assume(_poolRatio < type(uint256).max / _poolSupply);
- }
-}
-
-abstract contract SwapExactAmountInUtils is BasePoolTest {
- address tokenIn;
- address tokenOut;
-
- struct SwapExactAmountIn_FuzzScenario {
- uint256 tokenAmountIn;
- uint256 tokenInBalance;
- uint256 tokenInDenorm;
- uint256 tokenOutBalance;
- uint256 tokenOutDenorm;
- uint256 swapFee;
- }
-
- function _setValues(SwapExactAmountIn_FuzzScenario memory _fuzz) internal {
- tokenIn = tokens[0];
- tokenOut = tokens[1];
-
- // Create mocks for tokenIn and tokenOut (only use the first 2 tokens)
- _mockTransferFrom(tokenIn);
- _mockTransfer(tokenOut);
-
- // Set balances
- _setRecord(
- tokenIn,
- IBPool.Record({
- bound: true,
- index: 0, // NOTE: irrelevant for this method
- denorm: _fuzz.tokenInDenorm
- })
- );
- _mockPoolBalance(tokenIn, _fuzz.tokenInBalance);
-
- _setRecord(
- tokenOut,
- IBPool.Record({
- bound: true,
- index: 0, // NOTE: irrelevant for this method
- denorm: _fuzz.tokenOutDenorm
- })
- );
- _mockPoolBalance(tokenOut, _fuzz.tokenOutBalance);
-
- // Set swapFee
- _setSwapFee(_fuzz.swapFee);
- // Set finalize
- _setFinalize(true);
- }
-
- function _assumeHappyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) internal view virtual {
- // safe bound assumptions
- _fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE);
-
- // 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 - calcSpotPrice (spotPriceAfter)
- vm.assume(_fuzz.tokenAmountIn < type(uint256).max - _fuzz.tokenInBalance);
- vm.assume(_fuzz.tokenInBalance + _fuzz.tokenAmountIn < type(uint256).max / _fuzz.tokenInDenorm);
-
- // internal calculation for calcSpotPrice (spotPriceBefore)
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
-
- // MAX_IN_RATIO
- vm.assume(_fuzz.tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO));
-
- // L338 BPool.sol
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
-
- _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);
-
- // internal calculation for calcSpotPrice (spotPriceAfter)
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance + _fuzz.tokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _tokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
-
- vm.assume(bmul(_spotPriceBefore, _tokenAmountOut) <= _fuzz.tokenAmountIn);
- }
-
- modifier happyPath(SwapExactAmountIn_FuzzScenario memory _fuzz) {
- _assumeHappyPath(_fuzz);
- _setValues(_fuzz);
- _;
- }
-}
-
-contract BPool_Unit_GetCurrentTokens is BasePoolTest {
- function test_Returns_CurrentTokens(uint256 _length) public {
- vm.assume(_length > 0);
- vm.assume(_length <= MAX_BOUND_TOKENS);
- address[] memory _tokensToAdd = _setRandomTokens(_length);
-
- assertEq(bPool.getCurrentTokens(), _tokensToAdd);
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getCurrentTokens();
- }
-}
-
-contract BPool_Unit_GetFinalTokens is BasePoolTest {
- function test_Returns_FinalTokens(uint256 _length) public {
- vm.assume(_length > 0);
- vm.assume(_length <= MAX_BOUND_TOKENS);
- address[] memory _tokensToAdd = _setRandomTokens(_length);
- _setFinalize(true);
-
- assertEq(bPool.getFinalTokens(), _tokensToAdd);
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getFinalTokens();
- }
-
- function test_Revert_NotFinalized(uint256 _length) public {
- vm.assume(_length > 0);
- vm.assume(_length <= MAX_BOUND_TOKENS);
- _setRandomTokens(_length);
- _setFinalize(false);
-
- vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
- bPool.getFinalTokens();
- }
-}
-
-contract BPool_Unit_GetDenormalizedWeight is BasePoolTest {
- function test_Returns_DenormalizedWeight(address _token, uint256 _weight) public {
- bPool.set__records(_token, IBPool.Record({bound: true, index: 0, denorm: _weight}));
-
- assertEq(bPool.getDenormalizedWeight(_token), _weight);
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getDenormalizedWeight(address(0));
- }
-
- function test_Revert_NotBound(address _token) public {
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.getDenormalizedWeight(_token);
- }
-}
-
-contract BPool_Unit_GetTotalDenormalizedWeight is BasePoolTest {
- function test_Returns_TotalDenormalizedWeight(uint256 _totalWeight) public {
- _setTotalWeight(_totalWeight);
-
- assertEq(bPool.getTotalDenormalizedWeight(), _totalWeight);
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getTotalDenormalizedWeight();
- }
-}
-
-contract BPool_Unit_GetNormalizedWeight is BasePoolTest {
- function test_Returns_NormalizedWeight(address _token, uint256 _weight, uint256 _totalWeight) public {
- _weight = bound(_weight, MIN_WEIGHT, MAX_WEIGHT);
- _totalWeight = bound(_totalWeight, MIN_WEIGHT, MAX_TOTAL_WEIGHT);
- vm.assume(_weight < _totalWeight);
- _setRecord(_token, IBPool.Record({bound: true, index: 0, denorm: _weight}));
- _setTotalWeight(_totalWeight);
-
- assertEq(bPool.getNormalizedWeight(_token), bdiv(_weight, _totalWeight));
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getNormalizedWeight(address(0));
- }
-
- function test_Revert_NotBound(address _token) public {
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.getNormalizedWeight(_token);
- }
-}
-
-contract BPool_Unit_GetBalance is BasePoolTest {
- function test_Returns_Balance(address _token, uint256 _balance) public {
- assumeNotForgeAddress(_token);
-
- bPool.set__records(_token, IBPool.Record({bound: true, index: 0, denorm: 0}));
- _mockPoolBalance(_token, _balance);
-
- assertEq(bPool.getBalance(_token), _balance);
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getBalance(address(0));
- }
-
- function test_Revert_NotBound(address _token) public {
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.getBalance(_token);
- }
-}
-
-contract BPool_Unit_GetSwapFee is BasePoolTest {
- function test_Returns_SwapFee(uint256 _swapFee) public {
- _setSwapFee(_swapFee);
-
- assertEq(bPool.getSwapFee(), _swapFee);
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getSwapFee();
- }
-}
-
-contract BPool_Unit_GetController is BasePoolTest {
- function test_Returns_Controller(address _controller) public {
- bPool.set__controller(_controller);
-
- assertEq(bPool.getController(), _controller);
- }
-
- function test_Revert_Reentrancy() public {
- _expectRevertByReentrancy();
- bPool.getController();
- }
-}
-
-contract BPool_Unit_SetSwapFee is BasePoolTest {
- modifier happyPath(uint256 _fee) {
- vm.assume(_fee >= MIN_FEE);
- vm.assume(_fee <= MAX_FEE);
- _;
- }
-
- function test_Revert_Finalized(uint256 _fee) public happyPath(_fee) {
- _setFinalize(true);
-
- vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector);
- bPool.setSwapFee(_fee);
- }
-
- function test_Revert_NotController(address _controller, address _caller, uint256 _fee) public happyPath(_fee) {
- vm.assume(_controller != _caller);
- bPool.set__controller(_controller);
-
- vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
- vm.prank(_caller);
- bPool.setSwapFee(_fee);
- }
-
- function test_Revert_MinFee(uint256 _fee) public {
- vm.assume(_fee < MIN_FEE);
-
- vm.expectRevert(IBPool.BPool_FeeBelowMinimum.selector);
- bPool.setSwapFee(_fee);
- }
-
- function test_Revert_MaxFee(uint256 _fee) public {
- vm.assume(_fee > MAX_FEE);
-
- vm.expectRevert(IBPool.BPool_FeeAboveMaximum.selector);
- bPool.setSwapFee(_fee);
- }
-
- function test_Revert_Reentrancy(uint256 _fee) public happyPath(_fee) {
- _expectRevertByReentrancy();
- bPool.setSwapFee(_fee);
- }
-
- function test_Set_SwapFee(uint256 _fee) public happyPath(_fee) {
- bPool.setSwapFee(_fee);
-
- assertEq(bPool.call__swapFee(), _fee);
- }
-
- function test_Set_ReentrancyLock(uint256 _fee) public happyPath(_fee) {
- _expectSetReentrancyLock();
- bPool.setSwapFee(_fee);
- }
-
- function test_Emit_LogCall(uint256 _fee) public happyPath(_fee) {
- vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(BPool.setSwapFee.selector, _fee);
- emit IBPool.LOG_CALL(BPool.setSwapFee.selector, address(this), _data);
-
- bPool.setSwapFee(_fee);
- }
-}
-
-contract BPool_Unit_SetController is BasePoolTest {
- function test_Revert_NotController(address _controller, address _caller, address _newController) public {
- vm.assume(_newController != address(0));
- vm.assume(_controller != _caller);
- bPool.set__controller(_controller);
-
- vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
- vm.prank(_caller);
- bPool.setController(_newController);
- }
-
- function test_Revert_Reentrancy(address _controller) public {
- _expectRevertByReentrancy();
- bPool.setController(_controller);
- }
-
- function test_Revert_AddressZero() public {
- vm.expectRevert(IBPool.BPool_AddressZero.selector);
-
- bPool.setController(address(0));
- }
-
- function test_Set_Controller(address _controller) public {
- vm.assume(_controller != address(0));
- bPool.setController(_controller);
-
- assertEq(bPool.call__controller(), _controller);
- }
-
- function test_Emit_LogCall(address _controller) public {
- vm.assume(_controller != address(0));
- vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(BPool.setController.selector, _controller);
- emit IBPool.LOG_CALL(BPool.setController.selector, address(this), _data);
-
- bPool.setController(_controller);
- }
-
- function test_Set_ReentrancyLock(address _controller) public {
- vm.assume(_controller != address(0));
- _expectSetReentrancyLock();
- bPool.setController(_controller);
- }
-}
-
-contract BPool_Unit_Finalize is BasePoolTest {
- modifier happyPath(uint256 _tokensLength) {
- _tokensLength = bound(_tokensLength, MIN_BOUND_TOKENS, MAX_BOUND_TOKENS);
- _setRandomTokens(_tokensLength);
- _;
- }
-
- function test_Revert_NotController(
- address _controller,
- address _caller,
- uint256 _tokensLength
- ) public happyPath(_tokensLength) {
- vm.assume(_controller != _caller);
- bPool.set__controller(_controller);
-
- vm.prank(_caller);
- vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
- bPool.finalize();
- }
-
- function test_Revert_Finalized(uint256 _tokensLength) public happyPath(_tokensLength) {
- _setFinalize(true);
-
- vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector);
- bPool.finalize();
- }
-
- function test_Revert_MinTokens(uint256 _tokensLength) public {
- _tokensLength = bound(_tokensLength, 0, MIN_BOUND_TOKENS - 1);
- _setRandomTokens(_tokensLength);
-
- vm.expectRevert(IBPool.BPool_TokensBelowMinimum.selector);
- bPool.finalize();
- }
-
- function test_Revert_Reentrancy(uint256 _tokensLength) public happyPath(_tokensLength) {
- _expectRevertByReentrancy();
- bPool.finalize();
- }
-
- function test_Set_Finalize(uint256 _tokensLength) public happyPath(_tokensLength) {
- bPool.finalize();
-
- assertEq(bPool.call__finalized(), true);
- }
-
- function test_Set_ReentrancyLock(uint256 _tokensLength) public happyPath(_tokensLength) {
- _expectSetReentrancyLock();
- bPool.finalize();
- }
-
- function test_Call_AfterFinalizeHook(uint256 _tokensLength) public happyPath(_tokensLength) {
- bPool.expectCall__afterFinalize();
- bPool.finalize();
- }
-
- function test_Mint_InitPoolSupply(uint256 _tokensLength) public happyPath(_tokensLength) {
- bPool.finalize();
-
- assertEq(bPool.totalSupply(), INIT_POOL_SUPPLY);
- }
-
- function test_Push_InitPoolSupply(uint256 _tokensLength) public happyPath(_tokensLength) {
- bPool.finalize();
-
- assertEq(bPool.balanceOf(address(this)), INIT_POOL_SUPPLY);
- }
-
- function test_Emit_LogCall(uint256 _tokensLength) public happyPath(_tokensLength) {
- vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(BPool.finalize.selector);
- emit IBPool.LOG_CALL(BPool.finalize.selector, address(this), _data);
-
- bPool.finalize();
- }
-}
-
-contract BPool_Unit_GetSpotPrice is BasePoolTest {
- struct GetSpotPrice_FuzzScenario {
- address tokenIn;
- address tokenOut;
- uint256 tokenInBalance;
- uint256 tokenInDenorm;
- uint256 tokenOutBalance;
- uint256 tokenOutDenorm;
- uint256 swapFee;
- }
-
- function _setValues(GetSpotPrice_FuzzScenario memory _fuzz) internal {
- _setRecord(_fuzz.tokenIn, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenInDenorm}));
- _mockPoolBalance(_fuzz.tokenIn, _fuzz.tokenInBalance);
- _setRecord(_fuzz.tokenOut, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenOutDenorm}));
- _mockPoolBalance(_fuzz.tokenOut, _fuzz.tokenOutBalance);
- _setSwapFee(_fuzz.swapFee);
- }
-
- function _assumeHappyPath(GetSpotPrice_FuzzScenario memory _fuzz) internal pure {
- assumeNotForgeAddress(_fuzz.tokenIn);
- assumeNotForgeAddress(_fuzz.tokenOut);
- vm.assume(_fuzz.tokenIn != _fuzz.tokenOut);
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- }
-
- modifier happyPath(GetSpotPrice_FuzzScenario memory _fuzz) {
- _assumeHappyPath(_fuzz);
- _setValues(_fuzz);
- _;
- }
-
- function test_Revert_NotBoundTokenIn(
- GetSpotPrice_FuzzScenario memory _fuzz,
- address _tokenIn
- ) public happyPath(_fuzz) {
- vm.assume(_tokenIn != _fuzz.tokenIn);
- vm.assume(_tokenIn != _fuzz.tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.getSpotPrice(_tokenIn, _fuzz.tokenOut);
- }
-
- function test_Revert_NotBoundTokenOut(
- GetSpotPrice_FuzzScenario memory _fuzz,
- address _tokenOut
- ) public happyPath(_fuzz) {
- vm.assume(_tokenOut != _fuzz.tokenIn);
- vm.assume(_tokenOut != _fuzz.tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.getSpotPrice(_fuzz.tokenIn, _tokenOut);
- }
-
- function test_Returns_SpotPrice(GetSpotPrice_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _expectedSpotPrice = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- uint256 _spotPrice = bPool.getSpotPrice(_fuzz.tokenIn, _fuzz.tokenOut);
- assertEq(_spotPrice, _expectedSpotPrice);
- }
-
- function test_Revert_Reentrancy(GetSpotPrice_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectRevertByReentrancy();
- bPool.getSpotPrice(_fuzz.tokenIn, _fuzz.tokenOut);
- }
-}
-
-contract BPool_Unit_GetSpotPriceSansFee is BasePoolTest {
- struct GetSpotPriceSansFee_FuzzScenario {
- address tokenIn;
- address tokenOut;
- uint256 tokenInBalance;
- uint256 tokenInDenorm;
- uint256 tokenOutBalance;
- uint256 tokenOutDenorm;
- }
-
- function _setValues(GetSpotPriceSansFee_FuzzScenario memory _fuzz) internal {
- _setRecord(_fuzz.tokenIn, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenInDenorm}));
- _mockPoolBalance(_fuzz.tokenIn, _fuzz.tokenInBalance);
- _setRecord(_fuzz.tokenOut, IBPool.Record({bound: true, index: 0, denorm: _fuzz.tokenOutDenorm}));
- _mockPoolBalance(_fuzz.tokenOut, _fuzz.tokenOutBalance);
- _setSwapFee(0);
- }
-
- function _assumeHappyPath(GetSpotPriceSansFee_FuzzScenario memory _fuzz) internal pure {
- assumeNotForgeAddress(_fuzz.tokenIn);
- assumeNotForgeAddress(_fuzz.tokenOut);
- vm.assume(_fuzz.tokenIn != _fuzz.tokenOut);
- _assumeCalcSpotPrice(_fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, 0);
- }
-
- modifier happyPath(GetSpotPriceSansFee_FuzzScenario memory _fuzz) {
- _assumeHappyPath(_fuzz);
- _setValues(_fuzz);
- _;
- }
-
- function test_Revert_NotBoundTokenIn(
- GetSpotPriceSansFee_FuzzScenario memory _fuzz,
- address _tokenIn
- ) public happyPath(_fuzz) {
- vm.assume(_tokenIn != _fuzz.tokenIn);
- vm.assume(_tokenIn != _fuzz.tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.getSpotPriceSansFee(_tokenIn, _fuzz.tokenOut);
- }
-
- function test_Revert_NotBoundTokenOut(
- GetSpotPriceSansFee_FuzzScenario memory _fuzz,
- address _tokenOut
- ) public happyPath(_fuzz) {
- vm.assume(_tokenOut != _fuzz.tokenIn);
- vm.assume(_tokenOut != _fuzz.tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.getSpotPriceSansFee(_fuzz.tokenIn, _tokenOut);
- }
-
- function test_Returns_SpotPrice(GetSpotPriceSansFee_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _expectedSpotPrice =
- calcSpotPrice(_fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, 0);
- uint256 _spotPrice = bPool.getSpotPriceSansFee(_fuzz.tokenIn, _fuzz.tokenOut);
- assertEq(_spotPrice, _expectedSpotPrice);
- }
-
- function test_Revert_Reentrancy(GetSpotPriceSansFee_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectRevertByReentrancy();
- bPool.getSpotPriceSansFee(_fuzz.tokenIn, _fuzz.tokenOut);
- }
-}
-
-contract BPool_Unit_ExitPool is BasePoolTest {
- struct ExitPool_FuzzScenario {
- uint256 poolAmountIn;
- uint256 initPoolSupply;
- uint256[TOKENS_AMOUNT] balance;
- }
-
- function _setValues(ExitPool_FuzzScenario memory _fuzz) internal {
- // Create mocks
- for (uint256 i = 0; i < tokens.length; i++) {
- _mockTransfer(tokens[i]);
- }
-
- // Set tokens
- _setTokens(_tokensToMemory());
-
- // Set balances
- for (uint256 i = 0; i < tokens.length; i++) {
- _setRecord(
- tokens[i],
- IBPool.Record({
- bound: true,
- index: 0, // NOTE: irrelevant for this method
- denorm: 0 // NOTE: irrelevant for this method
- })
- );
- _mockPoolBalance(tokens[i], _fuzz.balance[i]);
- }
-
- // Set LP token balance
- _setPoolBalance(address(this), _fuzz.poolAmountIn); // give LP tokens to fn caller
- // Set totalSupply
- _setTotalSupply(_fuzz.initPoolSupply - _fuzz.poolAmountIn);
- // Set finalize
- _setFinalize(true);
- }
-
- function _assumeHappyPath(ExitPool_FuzzScenario memory _fuzz) internal pure {
- uint256 _maxInitSupply = type(uint256).max / BONE;
- _fuzz.initPoolSupply = bound(_fuzz.initPoolSupply, INIT_POOL_SUPPLY, _maxInitSupply);
-
- uint256 _poolAmountInAfterFee = _fuzz.poolAmountIn - (_fuzz.poolAmountIn * EXIT_FEE);
- vm.assume(_poolAmountInAfterFee <= _fuzz.initPoolSupply);
- vm.assume(_poolAmountInAfterFee * BONE > _fuzz.initPoolSupply);
- vm.assume(_poolAmountInAfterFee * BONE < type(uint256).max - (_fuzz.initPoolSupply / 2));
-
- uint256 _ratio = bdiv(_poolAmountInAfterFee, _fuzz.initPoolSupply);
- uint256 _maxBalance = type(uint256).max / (_ratio * BONE);
-
- for (uint256 i = 0; i < _fuzz.balance.length; i++) {
- _fuzz.balance[i] = bound(_fuzz.balance[i], BONE, _maxBalance);
- }
- }
-
- modifier happyPath(ExitPool_FuzzScenario memory _fuzz) {
- _assumeHappyPath(_fuzz);
- _setValues(_fuzz);
- _;
- }
-
- function test_Revert_NotFinalized(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _setFinalize(false);
-
- vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
- }
-
- function test_Revert_InvalidPoolRatio(
- ExitPool_FuzzScenario memory _fuzz,
- uint256 _poolAmountIn
- ) public happyPath(_fuzz) {
- _poolAmountIn = bound(_poolAmountIn, 0, (INIT_POOL_SUPPLY / 2 / BONE) - 1); // bdiv rounds up
-
- vm.expectRevert(IBPool.BPool_InvalidPoolRatio.selector);
- bPool.exitPool(_poolAmountIn, _zeroArray(tokens.length));
- }
-
- function test_Pull_PoolShare(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- assertEq(bPool.balanceOf(address(this)), _fuzz.poolAmountIn);
-
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
-
- assertEq(bPool.balanceOf(address(this)), 0);
- }
-
- function test_Push_PoolShare(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- address _factoryAddress = bPool.FACTORY();
- uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE);
- uint256 _balanceBefore = bPool.balanceOf(_factoryAddress);
-
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
-
- assertEq(bPool.balanceOf(_factoryAddress), _balanceBefore - _fuzz.poolAmountIn + _exitFee);
- }
-
- function test_Burn_PoolShare(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE);
- uint256 _pAiAfterExitFee = bsub(_fuzz.poolAmountIn, _exitFee);
- uint256 _totalSupplyBefore = bPool.totalSupply();
-
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
-
- assertEq(bPool.totalSupply(), _totalSupplyBefore - _pAiAfterExitFee);
- }
-
- function test_Set_ReentrancyLock(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectSetReentrancyLock();
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
- }
-
- function test_Revert_InvalidTokenAmountOut(
- ExitPool_FuzzScenario memory _fuzz,
- uint256 _tokenIndex
- ) public happyPath(_fuzz) {
- _assumeHappyPath(_fuzz);
- _tokenIndex = bound(_tokenIndex, 0, TOKENS_AMOUNT - 1);
- _fuzz.balance[_tokenIndex] = 0;
- _setValues(_fuzz);
-
- vm.expectRevert(IBPool.BPool_InvalidTokenAmountOut.selector);
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
- }
-
- function test_Revert_TokenAmountOutBelowMinAmountOut(
- ExitPool_FuzzScenario memory _fuzz,
- uint256 _tokenIndex,
- uint256[TOKENS_AMOUNT] memory _minAmountsOut
- ) public happyPath(_fuzz) {
- _tokenIndex = bound(_tokenIndex, 0, TOKENS_AMOUNT - 1);
-
- uint256 _poolTotal = _fuzz.initPoolSupply;
- uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE);
- uint256 _pAiAfterExitFee = bsub(_fuzz.poolAmountIn, _exitFee);
- uint256 _ratio = bdiv(_pAiAfterExitFee, _poolTotal);
-
- for (uint256 i = 0; i < tokens.length; i++) {
- uint256 _bal = _fuzz.balance[i];
- uint256 _tokenAmountOut = bmul(_ratio, _bal);
-
- _minAmountsOut[i] = _tokenIndex == i ? _tokenAmountOut + 1 : _tokenAmountOut;
- }
-
- vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinAmountOut.selector);
- bPool.exitPool(_fuzz.poolAmountIn, _staticToDynamicUintArray(_minAmountsOut));
- }
-
- function test_Revert_Reentrancy(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectRevertByReentrancy();
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
- }
-
- function test_Emit_TokenArrayLogExit(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE);
- uint256 _pAiAfterExitFee = bsub(_fuzz.poolAmountIn, _exitFee);
- uint256 _ratio = bdiv(_pAiAfterExitFee, _fuzz.initPoolSupply);
-
- for (uint256 i = 0; i < tokens.length; i++) {
- uint256 _bal = _fuzz.balance[i];
- uint256 _tokenAmountOut = bmul(_ratio, _bal);
- vm.expectEmit();
- emit IBPool.LOG_EXIT(address(this), tokens[i], _tokenAmountOut);
- }
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
- }
-
- function test_Push_TokenArrayTokenAmountOut(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE);
- uint256 _pAiAfterExitFee = bsub(_fuzz.poolAmountIn, _exitFee);
- uint256 _ratio = bdiv(_pAiAfterExitFee, _fuzz.initPoolSupply);
-
- for (uint256 i = 0; i < tokens.length; i++) {
- uint256 _bal = _fuzz.balance[i];
- uint256 _tokenAmountOut = bmul(_ratio, _bal);
- vm.expectCall(
- address(tokens[i]), abi.encodeWithSelector(IERC20.transfer.selector, address(this), _tokenAmountOut)
- );
- }
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
- }
-
- function test_Emit_LogCall(ExitPool_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(BPool.exitPool.selector, _fuzz.poolAmountIn, _zeroArray(tokens.length));
- emit IBPool.LOG_CALL(BPool.exitPool.selector, address(this), _data);
-
- bPool.exitPool(_fuzz.poolAmountIn, _zeroArray(tokens.length));
- }
-}
-
-contract BPool_Unit_SwapExactAmountIn is SwapExactAmountInUtils {
- function test_Revert_NotBoundTokenIn(
- SwapExactAmountIn_FuzzScenario memory _fuzz,
- address _tokenIn
- ) public happyPath(_fuzz) {
- assumeNotForgeAddress(_tokenIn);
- vm.assume(_tokenIn != tokenIn);
- vm.assume(_tokenIn != tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.swapExactAmountIn(_tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Revert_NotBoundTokenOut(
- SwapExactAmountIn_FuzzScenario memory _fuzz,
- address _tokenOut
- ) public happyPath(_fuzz) {
- assumeNotForgeAddress(_tokenOut);
- vm.assume(_tokenOut != tokenIn);
- vm.assume(_tokenOut != tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, _tokenOut, 0, type(uint256).max);
- }
-
- function test_Revert_NotFinalized(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _setFinalize(false);
-
- vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Revert_TokenAmountInAboveMaxIn(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountIn = bmul(_fuzz.tokenInBalance, MAX_IN_RATIO) + 1;
-
- vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector);
- bPool.swapExactAmountIn(tokenIn, _tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Revert_SpotPriceAboveMaxPrice(
- SwapExactAmountIn_FuzzScenario memory _fuzz,
- uint256 _maxPrice
- ) public happyPath(_fuzz) {
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- vm.assume(_spotPriceBefore > 0);
- _maxPrice = bound(_maxPrice, 0, _spotPriceBefore - 1);
-
- vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, _maxPrice);
- }
-
- function test_Revert_TokenAmountOutBelowMinOut(
- SwapExactAmountIn_FuzzScenario memory _fuzz,
- uint256 _minAmountOut
- ) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = calcOutGivenIn(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountIn,
- _fuzz.swapFee
- );
- _minAmountOut = bound(_minAmountOut, _tokenAmountOut + 1, type(uint256).max);
-
- vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector);
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, _minAmountOut, type(uint256).max);
- }
-
- function test_Revert_Reentrancy(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectRevertByReentrancy();
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Revert_SpotPriceAfterBelowSpotPriceBefore() public {
- vm.skip(true);
- // TODO: this revert might be unreachable. Find a way to test it or remove the revert in the code.
- }
-
- function test_Revert_SpotPriceAfterAboveMaxPrice(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = calcOutGivenIn(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountIn,
- _fuzz.swapFee
- );
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- uint256 _spotPriceAfter = calcSpotPrice(
- _fuzz.tokenInBalance + _fuzz.tokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _tokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
- vm.assume(_spotPriceAfter > _spotPriceBefore);
-
- vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, _spotPriceBefore);
- }
-
- function test_Revert_SpotPriceBeforeAboveTokenRatio(SwapExactAmountIn_FuzzScenario memory _fuzz) public {
- // Replicating _assumeHappyPath, but removing irrelevant assumptions and conditioning the revert
- _fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE);
- _fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenInDenorm);
- _fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max / _fuzz.tokenOutDenorm);
- vm.assume(_fuzz.tokenAmountIn < type(uint256).max - _fuzz.tokenInBalance);
- vm.assume(_fuzz.tokenInBalance + _fuzz.tokenAmountIn < type(uint256).max / _fuzz.tokenInDenorm);
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- vm.assume(_fuzz.tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO));
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- _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);
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance + _fuzz.tokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _tokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
- vm.assume(_spotPriceBefore > bdiv(_fuzz.tokenAmountIn, _tokenAmountOut));
-
- _setValues(_fuzz);
-
- vm.expectRevert(IBPool.BPool_SpotPriceBeforeAboveTokenRatio.selector);
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Emit_LogSwap(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = calcOutGivenIn(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountIn,
- _fuzz.swapFee
- );
-
- vm.expectEmit();
- emit IBPool.LOG_SWAP(address(this), tokenIn, tokenOut, _fuzz.tokenAmountIn, _tokenAmountOut);
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Set_ReentrancyLock(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectSetReentrancyLock();
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Pull_TokenAmountIn(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectCall(
- address(tokenIn),
- abi.encodeWithSelector(IERC20.transferFrom.selector, address(this), address(bPool), _fuzz.tokenAmountIn)
- );
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Push_TokenAmountOut(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = calcOutGivenIn(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountIn,
- _fuzz.swapFee
- );
-
- vm.expectCall(address(tokenOut), abi.encodeWithSelector(IERC20.transfer.selector, address(this), _tokenAmountOut));
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-
- function test_Returns_AmountAndPrice(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _expectedTokenAmountOut = calcOutGivenIn(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountIn,
- _fuzz.swapFee
- );
- uint256 _expectedSpotPriceAfter = calcSpotPrice(
- _fuzz.tokenInBalance + _fuzz.tokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _expectedTokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
-
- (uint256 _tokenAmountOut, uint256 _spotPriceAfter) =
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
-
- assertEq(_tokenAmountOut, _expectedTokenAmountOut);
- assertEq(_spotPriceAfter, _expectedSpotPriceAfter);
- }
-
- function test_Emit_LogCall(SwapExactAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(
- BPool.swapExactAmountIn.selector, tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max
- );
- emit IBPool.LOG_CALL(BPool.swapExactAmountIn.selector, address(this), _data);
-
- bPool.swapExactAmountIn(tokenIn, _fuzz.tokenAmountIn, tokenOut, 0, type(uint256).max);
- }
-}
-
-contract BPool_Unit_SwapExactAmountOut is BasePoolTest {
- address tokenIn;
- address tokenOut;
-
- struct SwapExactAmountOut_FuzzScenario {
- uint256 tokenAmountOut;
- uint256 tokenInBalance;
- uint256 tokenInDenorm;
- uint256 tokenOutBalance;
- uint256 tokenOutDenorm;
- uint256 swapFee;
- }
-
- function _setValues(SwapExactAmountOut_FuzzScenario memory _fuzz) internal {
- tokenIn = tokens[0];
- tokenOut = tokens[1];
-
- // Create mocks for tokenIn and tokenOut (only use the first 2 tokens)
- _mockTransferFrom(tokenIn);
- _mockTransfer(tokenOut);
-
- // Set balances
- _setRecord(
- tokenIn,
- IBPool.Record({
- bound: true,
- index: 0, // NOTE: irrelevant for this method
- denorm: _fuzz.tokenInDenorm
- })
- );
- _mockPoolBalance(tokenIn, _fuzz.tokenInBalance);
-
- _setRecord(
- tokenOut,
- IBPool.Record({
- bound: true,
- index: 0, // NOTE: irrelevant for this method
- denorm: _fuzz.tokenOutDenorm
- })
- );
- _mockPoolBalance(tokenOut, _fuzz.tokenOutBalance);
-
- // Set swapFee
- _setSwapFee(_fuzz.swapFee);
- // Set finalize
- _setFinalize(true);
- }
-
- function _assumeHappyPath(SwapExactAmountOut_FuzzScenario memory _fuzz) internal pure {
- // safe bound assumptions
- _fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE);
-
- _fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max);
- _fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max);
-
- // max - calcSpotPrice (spotPriceBefore)
- vm.assume(_fuzz.tokenInBalance < type(uint256).max / _fuzz.tokenInDenorm);
- vm.assume(_fuzz.tokenOutBalance < type(uint256).max / _fuzz.tokenOutDenorm);
-
- // max - calcSpotPrice (spotPriceAfter)
- vm.assume(_fuzz.tokenAmountOut < type(uint256).max - _fuzz.tokenOutBalance);
- vm.assume(_fuzz.tokenOutBalance + _fuzz.tokenAmountOut < type(uint256).max / _fuzz.tokenOutDenorm);
-
- // internal calculation for calcSpotPrice (spotPriceBefore)
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
-
- // MAX_OUT_RATIO
- vm.assume(_fuzz.tokenAmountOut <= bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO));
-
- // L364 BPool.sol
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
-
- // internal calculation for calcInGivenOut
- _assumeCalcInGivenOut(
- _fuzz.tokenOutDenorm, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenAmountOut, _fuzz.tokenInBalance
- );
-
- uint256 _tokenAmountIn = calcInGivenOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
-
- vm.assume(_tokenAmountIn > BONE);
- vm.assume(_tokenAmountIn < type(uint256).max / BONE);
- vm.assume(_spotPriceBefore <= bdiv(_tokenAmountIn, _fuzz.tokenAmountOut));
-
- // max - calcSpotPrice (spotPriceAfter)
- vm.assume(_tokenAmountIn < type(uint256).max - _fuzz.tokenInBalance);
- vm.assume(_fuzz.tokenInBalance + _tokenAmountIn < type(uint256).max / _fuzz.tokenInDenorm);
-
- // internal calculation for calcSpotPrice (spotPriceAfter)
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance + _tokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _fuzz.tokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
- }
-
- modifier happyPath(SwapExactAmountOut_FuzzScenario memory _fuzz) {
- _assumeHappyPath(_fuzz);
- _setValues(_fuzz);
- _;
- }
-
- function test_Revert_NotBoundTokenIn(
- SwapExactAmountOut_FuzzScenario memory _fuzz,
- address _tokenIn
- ) public happyPath(_fuzz) {
- assumeNotForgeAddress(_tokenIn);
- vm.assume(_tokenIn != tokenIn);
- vm.assume(_tokenIn != tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.swapExactAmountOut(_tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_NotBoundTokenOut(
- SwapExactAmountOut_FuzzScenario memory _fuzz,
- address _tokenOut
- ) public happyPath(_fuzz) {
- assumeNotForgeAddress(_tokenOut);
- vm.assume(_tokenOut != tokenIn);
- vm.assume(_tokenOut != tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, _tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_NotFinalized(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _setFinalize(false);
-
- vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_TokenAmountOutAboveMaxOut(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO) + 1;
-
- vm.expectRevert(IBPool.BPool_TokenAmountOutAboveMaxOut.selector);
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_SpotPriceAboveMaxPrice(
- SwapExactAmountOut_FuzzScenario memory _fuzz,
- uint256 _maxPrice
- ) public happyPath(_fuzz) {
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- vm.assume(_spotPriceBefore > 0);
- _maxPrice = bound(_maxPrice, 0, _spotPriceBefore - 1);
-
- vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, _maxPrice);
- }
-
- function test_Revert_TokenAmountInAboveMaxAmountIn(
- SwapExactAmountOut_FuzzScenario memory _fuzz,
- uint256 _maxAmountIn
- ) public happyPath(_fuzz) {
- uint256 _tokenAmountIn = calcInGivenOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- _maxAmountIn = bound(_maxAmountIn, 0, _tokenAmountIn - 1);
-
- vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxAmountIn.selector);
- bPool.swapExactAmountOut(tokenIn, _maxAmountIn, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_Reentrancy(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectRevertByReentrancy();
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_MathApprox() public {
- vm.skip(true);
- // TODO: this revert might be unreachable. Find a way to test it or remove the revert in the code.
- }
-
- function test_Revert_SpotPriceAfterAboveMaxPrice(SwapExactAmountOut_FuzzScenario memory _fuzz)
- public
- happyPath(_fuzz)
- {
- uint256 _tokenAmountIn = calcInGivenOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- uint256 _spotPriceAfter = calcSpotPrice(
- _fuzz.tokenInBalance + _tokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _fuzz.tokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
- vm.assume(_spotPriceAfter > _spotPriceBefore);
-
- vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, _spotPriceBefore);
- }
-
- function test_Revert_SpotPriceBeforeAboveTokenRatio(SwapExactAmountOut_FuzzScenario memory _fuzz) public {
- // Replicating _assumeHappyPath, but removing irrelevant assumptions and conditioning the revert
- _fuzz.tokenInDenorm = bound(_fuzz.tokenInDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, MIN_WEIGHT, MAX_WEIGHT);
- _fuzz.swapFee = bound(_fuzz.swapFee, MIN_FEE, MAX_FEE);
- _fuzz.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max);
- _fuzz.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max);
- vm.assume(_fuzz.tokenInBalance < type(uint256).max / _fuzz.tokenInDenorm);
- vm.assume(_fuzz.tokenOutBalance < type(uint256).max / _fuzz.tokenOutDenorm);
- vm.assume(_fuzz.tokenAmountOut < type(uint256).max - _fuzz.tokenOutBalance);
- vm.assume(_fuzz.tokenOutBalance + _fuzz.tokenAmountOut < type(uint256).max / _fuzz.tokenOutDenorm);
- vm.assume(_fuzz.tokenAmountOut <= bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO));
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- uint256 _spotPriceBefore = calcSpotPrice(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenOutDenorm, _fuzz.swapFee
- );
- _assumeCalcInGivenOut(
- _fuzz.tokenOutDenorm, _fuzz.tokenInDenorm, _fuzz.tokenOutBalance, _fuzz.tokenAmountOut, _fuzz.tokenInBalance
- );
- uint256 _tokenAmountIn = calcInGivenOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- vm.assume(_tokenAmountIn > BONE);
- vm.assume(_tokenAmountIn < type(uint256).max - _fuzz.tokenInBalance);
- vm.assume(_fuzz.tokenInBalance + _tokenAmountIn < type(uint256).max / _fuzz.tokenInDenorm);
- _assumeCalcSpotPrice(
- _fuzz.tokenInBalance + _tokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _fuzz.tokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
- vm.assume(_spotPriceBefore > bdiv(_tokenAmountIn, _fuzz.tokenAmountOut));
-
- _setValues(_fuzz);
-
- vm.expectRevert(IBPool.BPool_SpotPriceBeforeAboveTokenRatio.selector);
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Emit_LogSwap(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountIn = calcInGivenOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
-
- vm.expectEmit();
- emit IBPool.LOG_SWAP(address(this), tokenIn, tokenOut, _tokenAmountIn, _fuzz.tokenAmountOut);
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Set_ReentrancyLock(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectSetReentrancyLock();
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Pull_TokenAmountIn(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountIn = calcInGivenOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
-
- vm.expectCall(
- address(tokenIn),
- abi.encodeWithSelector(IERC20.transferFrom.selector, address(this), address(bPool), _tokenAmountIn)
- );
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Push_TokenAmountOut(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectCall(
- address(tokenOut), abi.encodeWithSelector(IERC20.transfer.selector, address(this), _fuzz.tokenAmountOut)
- );
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Returns_AmountAndPrice(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _expectedTokenAmountIn = calcInGivenOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- uint256 _expectedSpotPriceAfter = calcSpotPrice(
- _fuzz.tokenInBalance + _expectedTokenAmountIn,
- _fuzz.tokenInDenorm,
- _fuzz.tokenOutBalance - _fuzz.tokenAmountOut,
- _fuzz.tokenOutDenorm,
- _fuzz.swapFee
- );
-
- (uint256 _tokenAmountIn, uint256 _spotPriceAfter) =
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
-
- assertEq(_expectedTokenAmountIn, _tokenAmountIn);
- assertEq(_expectedSpotPriceAfter, _spotPriceAfter);
- }
-
- function test_Emit_LogCall(SwapExactAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(
- BPool.swapExactAmountOut.selector, tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max
- );
- emit IBPool.LOG_CALL(BPool.swapExactAmountOut.selector, address(this), _data);
-
- bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-}
-
-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;
-
- struct JoinswapPoolAmountOut_FuzzScenario {
- uint256 poolAmountOut;
- uint256 tokenInBalance;
- uint256 tokenInDenorm;
- uint256 totalSupply;
- uint256 totalWeight;
- uint256 swapFee;
- }
-
- function _setValues(JoinswapPoolAmountOut_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(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) internal view {
- // 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.poolAmountOut = bound(_fuzz.poolAmountOut, INIT_POOL_SUPPLY, type(uint256).max - INIT_POOL_SUPPLY);
- _fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max - _fuzz.poolAmountOut);
-
- // min
- vm.assume(_fuzz.tokenInBalance >= MIN_BALANCE);
-
- // internal calculation for calcSingleInGivenPoolOut
- _assumeCalcSingleInGivenPoolOut(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.totalSupply, _fuzz.totalWeight, _fuzz.poolAmountOut
- );
-
- uint256 _tokenAmountIn = calcSingleInGivenPoolOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountOut,
- _fuzz.swapFee
- );
-
- // L428 BPool.sol
- vm.assume(_tokenAmountIn > 0);
-
- // max
- vm.assume(_fuzz.tokenInBalance < type(uint256).max - _tokenAmountIn);
-
- // MAX_IN_RATIO
- vm.assume(_fuzz.tokenInBalance < type(uint256).max / MAX_IN_RATIO);
- vm.assume(_tokenAmountIn <= bmul(_fuzz.tokenInBalance, MAX_IN_RATIO));
- }
-
- modifier happyPath(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) {
- _assumeHappyPath(_fuzz);
- _setValues(_fuzz);
- _;
- }
-
- function test_Revert_NotFinalized(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _setFinalize(false);
-
- vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Revert_NotBound(
- JoinswapPoolAmountOut_FuzzScenario memory _fuzz,
- address _tokenIn
- ) public happyPath(_fuzz) {
- assumeNotForgeAddress(_tokenIn);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.joinswapPoolAmountOut(_tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Revert_InvalidTokenAmountIn(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _fuzz.poolAmountOut = 0;
-
- vm.expectRevert(IBPool.BPool_InvalidTokenAmountIn.selector);
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Revert_TokenAmountInAboveMaxAmountIn(
- JoinswapPoolAmountOut_FuzzScenario memory _fuzz,
- uint256 _maxAmountIn
- ) public happyPath(_fuzz) {
- uint256 _tokenAmountIn = calcSingleInGivenPoolOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountOut,
- _fuzz.swapFee
- );
- _maxAmountIn = bound(_maxAmountIn, 0, _tokenAmountIn - 1);
-
- vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxAmountIn.selector);
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, _maxAmountIn);
- }
-
- function test_Revert_TokenAmountInAboveMaxIn(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public {
- // Replicating _assumeHappyPath, but removing irrelevant assumptions and conditioning the revert
- _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.tokenInBalance = bound(_fuzz.tokenInBalance, MIN_BALANCE, type(uint256).max / MAX_IN_RATIO);
- _fuzz.poolAmountOut = bound(_fuzz.poolAmountOut, INIT_POOL_SUPPLY, type(uint256).max - INIT_POOL_SUPPLY);
- _fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max - _fuzz.poolAmountOut);
- _assumeCalcSingleInGivenPoolOut(
- _fuzz.tokenInBalance, _fuzz.tokenInDenorm, _fuzz.totalSupply, _fuzz.totalWeight, _fuzz.poolAmountOut
- );
- uint256 _tokenAmountIn = calcSingleInGivenPoolOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountOut,
- _fuzz.swapFee
- );
- vm.assume(_tokenAmountIn > bmul(_fuzz.tokenInBalance, MAX_IN_RATIO));
-
- _setValues(_fuzz);
-
- vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector);
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Revert_Reentrancy(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public {
- _expectRevertByReentrancy();
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Emit_LogJoin(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountIn = calcSingleInGivenPoolOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountOut,
- _fuzz.swapFee
- );
-
- vm.expectEmit();
- emit IBPool.LOG_JOIN(address(this), tokenIn, _tokenAmountIn);
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Set_ReentrancyLock(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectSetReentrancyLock();
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Mint_PoolShare(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
-
- assertEq(bPool.totalSupply(), _fuzz.totalSupply + _fuzz.poolAmountOut);
- }
-
- function test_Push_PoolShare(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _balanceBefore = bPool.balanceOf(address(this));
-
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
-
- assertEq(bPool.balanceOf(address(this)), _balanceBefore + _fuzz.poolAmountOut);
- }
-
- function test_Pull_Underlying(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountIn = calcSingleInGivenPoolOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountOut,
- _fuzz.swapFee
- );
-
- vm.expectCall(
- address(tokenIn),
- abi.encodeWithSelector(IERC20.transferFrom.selector, address(this), address(bPool), _tokenAmountIn)
- );
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-
- function test_Returns_TokenAmountIn(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _expectedTokenAmountIn = calcSingleInGivenPoolOut(
- _fuzz.tokenInBalance,
- _fuzz.tokenInDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountOut,
- _fuzz.swapFee
- );
-
- (uint256 _tokenAmountIn) = bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
-
- assertEq(_expectedTokenAmountIn, _tokenAmountIn);
- }
-
- function test_Emit_LogCall(JoinswapPoolAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectEmit();
- bytes memory _data =
- abi.encodeWithSelector(BPool.joinswapPoolAmountOut.selector, tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- emit IBPool.LOG_CALL(BPool.joinswapPoolAmountOut.selector, address(this), _data);
-
- bPool.joinswapPoolAmountOut(tokenIn, _fuzz.poolAmountOut, type(uint256).max);
- }
-}
-
-contract BPool_Unit_ExitswapPoolAmountIn is BasePoolTest {
- address tokenOut;
-
- struct ExitswapPoolAmountIn_FuzzScenario {
- uint256 poolAmountIn;
- uint256 tokenOutBalance;
- uint256 tokenOutDenorm;
- uint256 totalSupply;
- uint256 totalWeight;
- uint256 swapFee;
- }
-
- function _setValues(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) internal {
- tokenOut = tokens[0];
-
- // Create mocks for tokenOut
- _mockTransfer(tokenOut);
-
- // Set balances
- _setRecord(
- tokenOut,
- IBPool.Record({
- bound: true,
- index: 0, // NOTE: irrelevant for this method
- denorm: _fuzz.tokenOutDenorm
- })
- );
- _mockPoolBalance(tokenOut, _fuzz.tokenOutBalance);
-
- // Set swapFee
- _setSwapFee(_fuzz.swapFee);
- // Set finalize
- _setFinalize(true);
- // Set balance
- _setPoolBalance(address(this), _fuzz.poolAmountIn); // give LP tokens to fn caller
- // Set totalSupply
- _setTotalSupply(_fuzz.totalSupply - _fuzz.poolAmountIn);
- // Set totalWeight
- _setTotalWeight(_fuzz.totalWeight);
- }
-
- function _assumeHappyPath(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) internal pure {
- // safe bound assumptions
- _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, 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);
-
- // max
- vm.assume(_fuzz.poolAmountIn < _fuzz.totalSupply);
- vm.assume(_fuzz.totalSupply < type(uint256).max - _fuzz.poolAmountIn);
-
- // min
- vm.assume(_fuzz.tokenOutBalance >= MIN_BALANCE);
-
- // internal calculation for calcSingleOutGivenPoolIn
- _assumeCalcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
-
- uint256 _tokenAmountOut = calcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
-
- // max
- vm.assume(_fuzz.tokenOutBalance < type(uint256).max - _tokenAmountOut);
-
- // MAX_OUT_RATIO
- vm.assume(_fuzz.tokenOutBalance < type(uint256).max / MAX_OUT_RATIO);
- vm.assume(_tokenAmountOut <= bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO));
- }
-
- modifier happyPath(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) {
- _assumeHappyPath(_fuzz);
- _setValues(_fuzz);
- _;
- }
-
- function test_Revert_NotFinalized(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _setFinalize(false);
-
- vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
- }
-
- function test_Revert_NotBound(
- ExitswapPoolAmountIn_FuzzScenario memory _fuzz,
- address _tokenOut
- ) public happyPath(_fuzz) {
- vm.assume(_tokenOut != tokenOut);
- assumeNotForgeAddress(_tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.exitswapPoolAmountIn(_tokenOut, _fuzz.poolAmountIn, 0);
- }
-
- function test_Revert_TokenAmountOutBelowMinAmountOut(
- ExitswapPoolAmountIn_FuzzScenario memory _fuzz,
- uint256 _minAmountOut
- ) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = calcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
- _minAmountOut = bound(_minAmountOut, _tokenAmountOut + 1, type(uint256).max);
-
- vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinAmountOut.selector);
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, _minAmountOut);
- }
-
- function test_Revert_TokenAmountOutAboveMaxOut(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public {
- // Replicating _assumeHappyPath, but removing irrelevant assumptions and conditioning the revert
- _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, 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.tokenOutBalance = bound(_fuzz.tokenOutBalance, MIN_BALANCE, type(uint256).max / MAX_OUT_RATIO);
- _fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max);
- vm.assume(_fuzz.totalSupply < type(uint256).max - _fuzz.poolAmountIn);
- vm.assume(_fuzz.poolAmountIn < _fuzz.totalSupply);
- _assumeCalcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
- uint256 _tokenAmountOut = calcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
- vm.assume(_tokenAmountOut > bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO));
-
- _setValues(_fuzz);
-
- vm.expectRevert(IBPool.BPool_TokenAmountOutAboveMaxOut.selector);
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
- }
-
- function test_Revert_Reentrancy(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectRevertByReentrancy();
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
- }
-
- function test_Emit_LogExit(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = calcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
-
- vm.expectEmit();
- emit IBPool.LOG_EXIT(address(this), tokenOut, _tokenAmountOut);
-
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
- }
-
- function test_Pull_PoolShare(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _balanceBefore = bPool.balanceOf(address(this));
-
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
-
- assertEq(bPool.balanceOf(address(this)), _balanceBefore - _fuzz.poolAmountIn);
- }
-
- function test_Set_ReentrancyLock(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectSetReentrancyLock();
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
- }
-
- function test_Burn_PoolShare(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _totalSupplyBefore = bPool.totalSupply();
- uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE);
-
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
-
- assertEq(bPool.totalSupply(), _totalSupplyBefore - bsub(_fuzz.poolAmountIn, _exitFee));
- }
-
- function test_Push_PoolShare(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- address _factoryAddress = bPool.FACTORY();
- uint256 _balanceBefore = bPool.balanceOf(_factoryAddress);
- uint256 _exitFee = bmul(_fuzz.poolAmountIn, EXIT_FEE);
-
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
-
- assertEq(bPool.balanceOf(_factoryAddress), _balanceBefore - _fuzz.poolAmountIn + _exitFee);
- }
-
- function test_Push_Underlying(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _tokenAmountOut = calcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
-
- vm.expectCall(address(tokenOut), abi.encodeWithSelector(IERC20.transfer.selector, address(this), _tokenAmountOut));
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
- }
-
- function test_Returns_TokenAmountOut(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _expectedTokenAmountOut = calcSingleOutGivenPoolIn(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.poolAmountIn,
- _fuzz.swapFee
- );
-
- (uint256 _tokenAmountOut) = bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
-
- assertEq(_tokenAmountOut, _expectedTokenAmountOut);
- }
-
- function test_Emit_LogCall(ExitswapPoolAmountIn_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(BPool.exitswapPoolAmountIn.selector, tokenOut, _fuzz.poolAmountIn, 0);
- emit IBPool.LOG_CALL(BPool.exitswapPoolAmountIn.selector, address(this), _data);
-
- bPool.exitswapPoolAmountIn(tokenOut, _fuzz.poolAmountIn, 0);
- }
-}
-
-contract BPool_Unit_ExitswapExternAmountOut is BasePoolTest {
- address tokenOut;
-
- struct ExitswapExternAmountOut_FuzzScenario {
- uint256 tokenAmountOut;
- uint256 tokenOutBalance;
- uint256 tokenOutDenorm;
- uint256 totalSupply;
- uint256 totalWeight;
- uint256 swapFee;
- }
-
- function _setValues(ExitswapExternAmountOut_FuzzScenario memory _fuzz, uint256 _poolAmountIn) internal {
- tokenOut = tokens[0];
-
- // Create mocks for tokenOut
- _mockTransfer(tokenOut);
-
- // Set balances
- _setRecord(
- tokenOut,
- IBPool.Record({
- bound: true,
- index: 0, // NOTE: irrelevant for this method
- denorm: _fuzz.tokenOutDenorm
- })
- );
- _mockPoolBalance(tokenOut, _fuzz.tokenOutBalance);
-
- // Set swapFee
- _setSwapFee(_fuzz.swapFee);
- // Set finalize
- _setFinalize(true);
- // Set balance
- _setPoolBalance(address(this), _poolAmountIn); // give LP tokens to fn caller
- // Set totalSupply
- _setTotalSupply(_fuzz.totalSupply - _poolAmountIn);
- // Set totalWeight
- _setTotalWeight(_fuzz.totalWeight);
- }
-
- function _assumeHappyPath(ExitswapExternAmountOut_FuzzScenario memory _fuzz)
- internal
- pure
- returns (uint256 _poolAmountIn)
- {
- // safe bound assumptions
- _fuzz.tokenOutDenorm = bound(_fuzz.tokenOutDenorm, 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);
-
- // min
- _fuzz.totalSupply = bound(_fuzz.totalSupply, INIT_POOL_SUPPLY, type(uint256).max / BONE);
-
- // MAX_OUT_RATIO
- vm.assume(_fuzz.tokenOutBalance < type(uint256).max / MAX_OUT_RATIO);
- vm.assume(_fuzz.tokenAmountOut <= bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO));
-
- // min
- vm.assume(_fuzz.tokenOutBalance >= MIN_BALANCE);
-
- // max
- vm.assume(_fuzz.tokenOutBalance < type(uint256).max - _fuzz.tokenAmountOut);
-
- // internal calculation for calcPoolInGivenSingleOut
- _assumeCalcPoolInGivenSingleOut(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
-
- _poolAmountIn = calcPoolInGivenSingleOut(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
-
- // min
- vm.assume(_poolAmountIn > 0);
-
- // max
- vm.assume(_poolAmountIn < _fuzz.totalSupply);
- vm.assume(_fuzz.totalSupply < type(uint256).max - _poolAmountIn);
- }
-
- modifier happyPath(ExitswapExternAmountOut_FuzzScenario memory _fuzz) {
- uint256 _poolAmountIn = _assumeHappyPath(_fuzz);
- _setValues(_fuzz, _poolAmountIn);
- _;
- }
-
- function test_Revert_NotFinalized(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public {
- _setFinalize(false);
-
- vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_NotBound(
- ExitswapExternAmountOut_FuzzScenario memory _fuzz,
- address _tokenOut
- ) public happyPath(_fuzz) {
- vm.assume(_tokenOut != tokenOut);
- assumeNotForgeAddress(_tokenOut);
-
- vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
- bPool.exitswapExternAmountOut(_tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_TokenAmountOutAboveMaxOut(ExitswapExternAmountOut_FuzzScenario memory _fuzz)
- public
- happyPath(_fuzz)
- {
- uint256 _maxTokenAmountOut = bmul(_fuzz.tokenOutBalance, MAX_OUT_RATIO);
-
- vm.expectRevert(IBPool.BPool_TokenAmountOutAboveMaxOut.selector);
- bPool.exitswapExternAmountOut(tokenOut, _maxTokenAmountOut + 1, type(uint256).max);
- }
-
- function test_Revert_InvalidPoolAmountIn(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _fuzz.tokenAmountOut = 0;
-
- vm.expectRevert(IBPool.BPool_InvalidPoolAmountIn.selector);
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Revert_PoolAmountInAboveMaxPoolAmountIn(
- ExitswapExternAmountOut_FuzzScenario memory _fuzz,
- uint256 _maxPoolAmountIn
- ) public happyPath(_fuzz) {
- uint256 _poolAmountIn = calcPoolInGivenSingleOut(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- _maxPoolAmountIn = bound(_maxPoolAmountIn, 0, _poolAmountIn - 1);
-
- vm.expectRevert(IBPool.BPool_PoolAmountInAboveMaxPoolAmountIn.selector);
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, _maxPoolAmountIn);
- }
-
- function test_Revert_Reentrancy(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public {
- _expectRevertByReentrancy();
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Emit_LogExit(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectEmit();
- emit IBPool.LOG_EXIT(address(this), tokenOut, _fuzz.tokenAmountOut);
-
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Set_ReentrancyLock(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- _expectSetReentrancyLock();
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Pull_PoolShare(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _balanceBefore = bPool.balanceOf(address(this));
- uint256 _poolAmountIn = calcPoolInGivenSingleOut(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- uint256 _exitFee = bmul(_poolAmountIn, EXIT_FEE);
-
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
-
- assertEq(bPool.balanceOf(address(this)), _balanceBefore - bsub(_poolAmountIn, _exitFee));
- }
-
- function test_Burn_PoolShare(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _totalSupplyBefore = bPool.totalSupply();
- uint256 _poolAmountIn = calcPoolInGivenSingleOut(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- uint256 _exitFee = bmul(_poolAmountIn, EXIT_FEE);
-
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
-
- assertEq(bPool.totalSupply(), _totalSupplyBefore - bsub(_poolAmountIn, _exitFee));
- }
-
- function test_Push_PoolShare(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- address _factoryAddress = bPool.FACTORY();
- uint256 _balanceBefore = bPool.balanceOf(_factoryAddress);
- uint256 _poolAmountIn = calcPoolInGivenSingleOut(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
- uint256 _exitFee = bmul(_poolAmountIn, EXIT_FEE);
-
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
-
- assertEq(bPool.balanceOf(_factoryAddress), _balanceBefore - _poolAmountIn + _exitFee);
- }
-
- function test_Push_Underlying(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectCall(
- address(tokenOut), abi.encodeWithSelector(IERC20.transfer.selector, address(this), _fuzz.tokenAmountOut)
- );
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-
- function test_Returns_PoolAmountIn(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- uint256 _expectedPoolAmountIn = calcPoolInGivenSingleOut(
- _fuzz.tokenOutBalance,
- _fuzz.tokenOutDenorm,
- _fuzz.totalSupply,
- _fuzz.totalWeight,
- _fuzz.tokenAmountOut,
- _fuzz.swapFee
- );
-
- (uint256 _poolAmountIn) = bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
-
- assertEq(_expectedPoolAmountIn, _poolAmountIn);
- }
-
- function test_Emit_LogCall(ExitswapExternAmountOut_FuzzScenario memory _fuzz) public happyPath(_fuzz) {
- vm.expectEmit();
- bytes memory _data =
- abi.encodeWithSelector(BPool.exitswapExternAmountOut.selector, tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- emit IBPool.LOG_CALL(BPool.exitswapExternAmountOut.selector, address(this), _data);
-
- bPool.exitswapExternAmountOut(tokenOut, _fuzz.tokenAmountOut, type(uint256).max);
- }
-}
-
-contract BPool_Unit__PullUnderlying is BasePoolTest {
- function test_Call_TransferFrom(address _erc20, address _from, uint256 _amount) public {
- assumeNotForgeAddress(_erc20);
-
- vm.mockCall(
- _erc20, abi.encodeWithSelector(IERC20.transferFrom.selector, _from, address(bPool), _amount), abi.encode(true)
- );
-
- vm.expectCall(address(_erc20), abi.encodeWithSelector(IERC20.transferFrom.selector, _from, address(bPool), _amount));
- bPool.call__pullUnderlying(_erc20, _from, _amount);
- }
-
- function test_Revert_ERC20False(address _erc20, address _from, uint256 _amount) public {
- assumeNotForgeAddress(_erc20);
- vm.mockCall(
- _erc20, abi.encodeWithSelector(IERC20.transferFrom.selector, _from, address(bPool), _amount), abi.encode(false)
- );
-
- vm.expectRevert(abi.encodeWithSelector(SafeERC20.SafeERC20FailedOperation.selector, _erc20));
- bPool.call__pullUnderlying(_erc20, _from, _amount);
- }
-
- function test_Success_NoReturnValueERC20(address _erc20, address _from, uint256 _amount) public {
- assumeNotForgeAddress(_erc20);
- vm.mockCall(
- _erc20, abi.encodeWithSelector(IERC20.transferFrom.selector, _from, address(bPool), _amount), abi.encode()
- );
-
- bPool.call__pullUnderlying(_erc20, _from, _amount);
- }
-}
-
-contract BPool_Unit__PushUnderlying is BasePoolTest {
- function test_Call_Transfer(address _erc20, address _to, uint256 _amount) public {
- assumeNotForgeAddress(_erc20);
-
- vm.mockCall(_erc20, abi.encodeWithSelector(IERC20.transfer.selector, _to, _amount), abi.encode(true));
-
- vm.expectCall(address(_erc20), abi.encodeWithSelector(IERC20.transfer.selector, _to, _amount));
- bPool.call__pushUnderlying(_erc20, _to, _amount);
- }
-
- function test_Revert_ERC20False(address _erc20, address _to, uint256 _amount) public {
- assumeNotForgeAddress(_erc20);
- vm.mockCall(_erc20, abi.encodeWithSelector(IERC20.transfer.selector, _to, _amount), abi.encode(false));
-
- vm.expectRevert(abi.encodeWithSelector(SafeERC20.SafeERC20FailedOperation.selector, _erc20));
- bPool.call__pushUnderlying(_erc20, _to, _amount);
- }
-
- function test_Success_NoReturnValueERC20(address _erc20, address _to, uint256 _amount) public {
- assumeNotForgeAddress(_erc20);
- vm.mockCall(_erc20, abi.encodeWithSelector(IERC20.transfer.selector, _to, _amount), abi.encode());
-
- bPool.call__pushUnderlying(_erc20, _to, _amount);
- }
-}
diff --git a/test/unit/BPool/BPool.t.sol b/test/unit/BPool/BPool.t.sol
index b3bebdee..a14f3d9f 100644
--- a/test/unit/BPool/BPool.t.sol
+++ b/test/unit/BPool/BPool.t.sol
@@ -1,13 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
-import {BPoolBase} from './BPoolBase.sol';
+import {BPoolBase} from './BPoolBase.t.sol';
+
+import {IERC20Errors} from '@openzeppelin/contracts/interfaces/draft-IERC6093.sol';
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
+import {Address} from '@openzeppelin/contracts/utils/Address.sol';
+
+import {BMath} from 'contracts/BMath.sol';
import {IBPool} from 'interfaces/IBPool.sol';
import {MockBPool} from 'test/smock/MockBPool.sol';
-contract BPool is BPoolBase {
+contract BPool is BPoolBase, BMath {
+ address controller = makeAddr('controller');
+ address randomCaller = makeAddr('random caller');
+ address unknownToken = makeAddr('unknown token');
+ uint256 swapFee = 0.1e18;
+
+ uint256 public tokenWeight = 1e18;
+ uint256 public totalWeight = 10e18;
+ uint256 public balanceTokenIn = 10e18;
+ uint256 public balanceTokenOut = 20e18;
+
+ // sP = (tokenInBalance / tokenInWeight) / (tokenOutBalance/ tokenOutWeight) * (1 / (1 - swapFee))
+ // tokenInWeight == tokenOutWeight
+ // sP = 10 / 20 = 0.5e18
+ // sPf = (10 / 20) * (1 / (1-0.1)) = 0.555...e18 (round-up)
+ uint256 public spotPriceWithoutFee = 0.5e18;
+ uint256 public spotPrice = 0.555555555555555556e18;
+ address public transferRecipient = makeAddr('transfer recipient');
+ address public transferFromSpender = makeAddr('transfer from spender');
+ address public transferredToken = makeAddr('underlying token');
+ uint256 public transferredAmount = 10e18;
+
+ function setUp() public virtual override {
+ super.setUp();
+
+ bPool.set__finalized(true);
+ 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.set__totalWeight(totalWeight);
+ bPool.set__swapFee(swapFee);
+ bPool.set__controller(controller);
+ }
+
function test_ConstructorWhenCalled(address _deployer) external {
- vm.prank(_deployer);
+ vm.startPrank(_deployer);
MockBPool _newBPool = new MockBPool();
// it sets caller as controller
@@ -20,8 +60,96 @@ contract BPool is BPoolBase {
assertEq(_newBPool.call__finalized(), false);
}
- function test_IsFinalizedWhenPoolIsFinalized() external {
- bPool.set__finalized(true);
+ function test_SetSwapFeeRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.setSwapFee(0);
+ }
+
+ function test_SetSwapFeeRevertWhen_CallerIsNotController() external {
+ vm.prank(randomCaller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
+ bPool.setSwapFee(0);
+ }
+
+ function test_SetSwapFeeRevertWhen_PoolIsFinalized() external {
+ vm.prank(controller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector);
+ bPool.setSwapFee(0);
+ }
+
+ function test_SetSwapFeeRevertWhen_SwapFeeIsBelowMIN_FEE() external {
+ bPool.set__finalized(false);
+ vm.prank(controller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_FeeBelowMinimum.selector);
+ bPool.setSwapFee(MIN_FEE - 1);
+ }
+
+ function test_SetSwapFeeRevertWhen_SwapFeeIsAboveMAX_FEE() external {
+ bPool.set__finalized(false);
+ vm.prank(controller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_FeeAboveMaximum.selector);
+ bPool.setSwapFee(MAX_FEE + 1);
+ }
+
+ function test_SetSwapFeeWhenPreconditionsAreMet(uint256 _swapFee) external {
+ bPool.set__finalized(false);
+ vm.prank(controller);
+ _swapFee = bound(_swapFee, MIN_FEE, MAX_FEE);
+
+ // it emits LOG_CALL event
+ vm.expectEmit();
+ bytes memory _data = abi.encodeWithSelector(IBPool.setSwapFee.selector, _swapFee);
+ emit IBPool.LOG_CALL(IBPool.setSwapFee.selector, controller, _data);
+
+ bPool.setSwapFee(_swapFee);
+
+ // it sets swap fee
+ assertEq(bPool.getSwapFee(), _swapFee);
+ }
+
+ function test_SetControllerRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.setController(controller);
+ }
+
+ function test_SetControllerRevertWhen_CallerIsNotController() external {
+ vm.prank(randomCaller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
+ bPool.setController(controller);
+ }
+
+ function test_SetControllerRevertWhen_NewControllerIsZeroAddress() external {
+ vm.prank(controller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_AddressZero.selector);
+ bPool.setController(address(0));
+ }
+
+ function test_SetControllerWhenPreconditionsAreMet(address _controller) external {
+ vm.prank(controller);
+ vm.assume(_controller != address(0));
+
+ // it emits LOG_CALL event
+ vm.expectEmit();
+ bytes memory _data = abi.encodeWithSelector(IBPool.setController.selector, _controller);
+ emit IBPool.LOG_CALL(IBPool.setController.selector, controller, _data);
+
+ bPool.setController(_controller);
+
+ // it sets new controller
+ assertEq(bPool.getController(), _controller);
+ }
+
+ function test_IsFinalizedWhenPoolIsFinalized() external view {
// it returns true
assertTrue(bPool.isFinalized());
}
@@ -33,13 +161,13 @@ contract BPool is BPoolBase {
}
function test_IsBoundWhenTokenIsBound(address _token) external {
- _setRecord(_token, IBPool.Record({bound: true, index: 0, denorm: 0}));
+ bPool.set__records(_token, IBPool.Record({bound: true, index: 0, denorm: 0}));
// it returns true
assertTrue(bPool.isBound(_token));
}
function test_IsBoundWhenTokenIsNOTBound(address _token) external {
- _setRecord(_token, IBPool.Record({bound: false, index: 0, denorm: 0}));
+ bPool.set__records(_token, IBPool.Record({bound: false, index: 0, denorm: 0}));
// it returns false
assertFalse(bPool.isBound(_token));
}
@@ -50,4 +178,397 @@ contract BPool is BPoolBase {
// it returns number of tokens
assertEq(bPool.getNumTokens(), _tokensToAdd);
}
+
+ function test_GetFinalTokensRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getFinalTokens();
+ }
+
+ function test_GetFinalTokensRevertWhen_PoolIsNotFinalized() external {
+ bPool.set__finalized(false);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
+ bPool.getFinalTokens();
+ }
+
+ function test_GetFinalTokensWhenPreconditionsAreMet() external view {
+ // it returns pool tokens
+ address[] memory _tokens = bPool.getFinalTokens();
+ assertEq(_tokens.length, tokens.length);
+ assertEq(_tokens[0], tokens[0]);
+ assertEq(_tokens[1], tokens[1]);
+ }
+
+ function test_GetCurrentTokensRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getCurrentTokens();
+ }
+
+ function test_GetCurrentTokensWhenPreconditionsAreMet() external view {
+ // it returns pool tokens
+ address[] memory _tokens = bPool.getCurrentTokens();
+ assertEq(_tokens.length, tokens.length);
+ assertEq(_tokens[0], tokens[0]);
+ assertEq(_tokens[1], tokens[1]);
+ }
+
+ function test_GetDenormalizedWeightRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getDenormalizedWeight(tokens[0]);
+ }
+
+ function test_GetDenormalizedWeightRevertWhen_TokenIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.getDenormalizedWeight(unknownToken);
+ }
+
+ function test_GetDenormalizedWeightWhenPreconditionsAreMet() external view {
+ // it returns token weight
+ uint256 _tokenWeight = bPool.getDenormalizedWeight(tokens[0]);
+ assertEq(_tokenWeight, tokenWeight);
+ }
+
+ function test_GetTotalDenormalizedWeightRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getTotalDenormalizedWeight();
+ }
+
+ function test_GetTotalDenormalizedWeightWhenPreconditionsAreMet() external view {
+ // it returns total weight
+ uint256 _totalWeight = bPool.getTotalDenormalizedWeight();
+ assertEq(_totalWeight, totalWeight);
+ }
+
+ function test_GetNormalizedWeightRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getNormalizedWeight(tokens[0]);
+ }
+
+ function test_GetNormalizedWeightRevertWhen_TokenIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.getNormalizedWeight(unknownToken);
+ }
+
+ function test_GetNormalizedWeightWhenPreconditionsAreMet() external view {
+ // it returns normalized weight
+ // normalizedWeight = tokenWeight / totalWeight
+ uint256 _normalizedWeight = bPool.getNormalizedWeight(tokens[0]);
+ assertEq(_normalizedWeight, 0.1e18);
+ }
+
+ function test_GetBalanceRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getBalance(tokens[0]);
+ }
+
+ function test_GetBalanceRevertWhen_TokenIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.getBalance(unknownToken);
+ }
+
+ function test_GetBalanceWhenPreconditionsAreMet(uint256 tokenBalance) external {
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(tokenBalance));
+ // it queries token balance
+ vm.expectCall(tokens[0], abi.encodeWithSelector(IERC20.balanceOf.selector));
+ // it returns token balance
+ uint256 _balance = bPool.getBalance(tokens[0]);
+ assertEq(_balance, tokenBalance);
+ }
+
+ function test_GetSwapFeeRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getSwapFee();
+ }
+
+ function test_GetSwapFeeWhenPreconditionsAreMet() external view {
+ // it returns swap fee
+ uint256 _swapFee = bPool.getSwapFee();
+ assertEq(_swapFee, swapFee);
+ }
+
+ function test_GetControllerRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getController();
+ }
+
+ function test_GetControllerWhenPreconditionsAreMet() external view {
+ // it returns controller
+ address _controller = bPool.getController();
+ assertEq(_controller, controller);
+ }
+
+ function test_GetSpotPriceRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getSpotPrice(tokens[0], tokens[1]);
+ }
+
+ function test_GetSpotPriceRevertWhen_TokenInIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.getSpotPrice(unknownToken, tokens[1]);
+ }
+
+ function test_GetSpotPriceRevertWhen_TokenOutIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.getSpotPrice(tokens[0], unknownToken);
+ }
+
+ function test_GetSpotPriceWhenPreconditionsAreMet() external {
+ // it queries token in balance
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceTokenIn));
+ vm.expectCall(tokens[0], abi.encodeWithSelector(IERC20.balanceOf.selector));
+ // it queries token out balance
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceTokenOut));
+ vm.expectCall(tokens[1], abi.encodeWithSelector(IERC20.balanceOf.selector));
+ // it returns spot price
+ assertEq(bPool.getSpotPrice(tokens[0], tokens[1]), spotPrice);
+ }
+
+ function test_GetSpotPriceSansFeeRevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.getSpotPriceSansFee(tokens[0], tokens[1]);
+ }
+
+ function test_GetSpotPriceSansFeeRevertWhen_TokenInIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.getSpotPriceSansFee(unknownToken, tokens[1]);
+ }
+
+ function test_GetSpotPriceSansFeeRevertWhen_TokenOutIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.getSpotPriceSansFee(tokens[0], unknownToken);
+ }
+
+ function test_GetSpotPriceSansFeeWhenPreconditionsAreMet() external {
+ // it queries token in balance
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceTokenIn));
+ vm.expectCall(tokens[0], abi.encodeWithSelector(IERC20.balanceOf.selector));
+ // it queries token out balance
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(balanceTokenOut));
+ vm.expectCall(tokens[1], abi.encodeWithSelector(IERC20.balanceOf.selector));
+ // it returns spot price
+ assertEq(bPool.getSpotPriceSansFee(tokens[0], tokens[1]), spotPriceWithoutFee);
+ }
+
+ function test_FinalizeRevertWhen_CallerIsNotController(address _caller) external {
+ vm.assume(_caller != controller);
+ vm.startPrank(_caller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_CallerIsNotController.selector);
+ bPool.finalize();
+ }
+
+ function test_FinalizeRevertWhen_PoolIsFinalized() external {
+ vm.startPrank(controller);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector);
+ bPool.finalize();
+ }
+
+ function test_FinalizeRevertWhen_ThereAreTooFewTokensBound() external {
+ vm.startPrank(controller);
+ bPool.set__finalized(false);
+ 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 {
+ vm.startPrank(controller);
+ bPool.set__finalized(false);
+ 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(controller, 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(controller, 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, controller, data);
+
+ bPool.finalize();
+ // it finalizes the pool
+ assertEq(bPool.call__finalized(), true);
+ }
+
+ function test__pushUnderlyingRevertWhen_UnderlyingTokenReturnsFalse() external {
+ vm.mockCall(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transfer.selector, transferRecipient, transferredAmount),
+ abi.encode(false)
+ );
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(SafeERC20.SafeERC20FailedOperation.selector, transferredToken));
+ bPool.call__pushUnderlying(transferredToken, transferRecipient, transferredAmount);
+ }
+
+ function test__pushUnderlyingWhenUnderlyingTokenDoesntReturnAValue() external {
+ vm.mockCall(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transfer.selector, transferRecipient, transferredAmount),
+ abi.encode()
+ );
+ // it assumes transfer success
+ bPool.call__pushUnderlying(transferredToken, transferRecipient, transferredAmount);
+ }
+
+ function test__pushUnderlyingRevertWhen_UnderlyingTokenRevertsWithoutData() external {
+ // it should revert with FailedInnerCall
+ vm.mockCallRevert(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transfer.selector, transferRecipient, transferredAmount),
+ abi.encode()
+ );
+ vm.expectRevert(Address.FailedInnerCall.selector);
+ bPool.call__pushUnderlying(transferredToken, transferRecipient, transferredAmount);
+ }
+
+ function test__pushUnderlyingRevertWhen_UnderlyingTokenRevertsWithData(bytes memory errorData) external {
+ vm.assume(keccak256(errorData) != keccak256(bytes('')));
+ vm.mockCallRevert(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transfer.selector, transferRecipient, transferredAmount),
+ errorData
+ );
+ // it should revert with same error data
+ vm.expectRevert(errorData);
+ bPool.call__pushUnderlying(transferredToken, transferRecipient, transferredAmount);
+ }
+
+ function test__pushUnderlyingWhenUnderlyingTokenReturnsTrue() external {
+ vm.mockCall(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transfer.selector, transferRecipient, transferredAmount),
+ abi.encode(true)
+ );
+ // it calls underlying transfer
+ vm.expectCall(
+ transferredToken, abi.encodeWithSelector(IERC20.transfer.selector, transferRecipient, transferredAmount)
+ );
+ bPool.call__pushUnderlying(transferredToken, transferRecipient, transferredAmount);
+ }
+
+ function test__pullUnderlyingRevertWhen_UnderlyingTokenReturnsFalse() external {
+ vm.mockCall(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transferFrom.selector, transferFromSpender, address(bPool), transferredAmount),
+ abi.encode(false)
+ );
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(SafeERC20.SafeERC20FailedOperation.selector, transferredToken));
+ bPool.call__pullUnderlying(transferredToken, transferFromSpender, transferredAmount);
+ }
+
+ function test__pullUnderlyingWhenUnderlyingTokenDoesntReturnAValue() external {
+ // it assumes transferFrom success
+ vm.mockCall(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transferFrom.selector, transferFromSpender, address(bPool), transferredAmount),
+ abi.encode()
+ );
+ // it assumes transferFrom success
+ bPool.call__pullUnderlying(transferredToken, transferFromSpender, transferredAmount);
+ }
+
+ function test__pullUnderlyingRevertWhen_UnderlyingTokenRevertsWithoutData() external {
+ vm.mockCallRevert(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transferFrom.selector, transferFromSpender, address(bPool), transferredAmount),
+ abi.encode()
+ );
+ // it should revert with FailedInnerCall
+ vm.expectRevert(Address.FailedInnerCall.selector);
+ bPool.call__pullUnderlying(transferredToken, transferFromSpender, transferredAmount);
+ }
+
+ function test__pullUnderlyingRevertWhen_UnderlyingTokenRevertsWithData(bytes memory errorData) external {
+ vm.assume(keccak256(errorData) != keccak256(bytes('')));
+ vm.mockCallRevert(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transferFrom.selector, transferFromSpender, address(bPool), transferredAmount),
+ errorData
+ );
+ // it should revert with same error data
+ vm.expectRevert(errorData);
+ bPool.call__pullUnderlying(transferredToken, transferFromSpender, transferredAmount);
+ }
+
+ function test__pullUnderlyingWhenUnderlyingTokenReturnsTrue() external {
+ vm.mockCall(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transferFrom.selector, transferFromSpender, address(bPool), transferredAmount),
+ abi.encode(true)
+ );
+ // it calls underlying transferFrom
+ vm.expectCall(
+ transferredToken,
+ abi.encodeWithSelector(IERC20.transferFrom.selector, transferFromSpender, address(bPool), transferredAmount)
+ );
+ bPool.call__pullUnderlying(transferredToken, transferFromSpender, transferredAmount);
+ }
+
+ function test__mintPoolShareWhenCalled() external {
+ uint256 mintAmount = 100e18;
+ // it mints shares to the pool's own balance
+ bPool.call__mintPoolShare(mintAmount);
+ assertEq(bPool.balanceOf(address(bPool)), mintAmount);
+ }
+
+ function test__burnPoolShareRevertWhen_PoolHasLessBalanceThanAmountToBurn() external {
+ uint256 existingBalance = 40e18;
+ deal(address(bPool), address(bPool), existingBalance);
+ uint256 burnAmount = 100e18;
+ // it should revert
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ IERC20Errors.ERC20InsufficientBalance.selector, address(bPool), existingBalance, burnAmount
+ )
+ );
+ bPool.call__burnPoolShare(burnAmount);
+ }
+
+ function test__burnPoolShareWhenPoolHasEnoughBalance() external {
+ uint256 existingBalance = 100e18;
+ uint256 burnAmount = 30e18;
+ deal(address(bPool), address(bPool), existingBalance);
+ bPool.call__burnPoolShare(burnAmount);
+ // it mints shares to the pool's own balance
+ assertEq(bPool.balanceOf(address(bPool)), existingBalance - burnAmount);
+ }
}
diff --git a/test/unit/BPool/BPool.tree b/test/unit/BPool/BPool.tree
index 379bec03..0405f003 100644
--- a/test/unit/BPool/BPool.tree
+++ b/test/unit/BPool/BPool.tree
@@ -5,6 +5,32 @@ BPool::constructor
├── it sets swap fee to MIN_FEE
└── it does NOT finalize the pool
+BPool::setSwapFee
+├── when reentrancy lock is set
+│ └── it should revert
+├── when caller is not controller
+│ └── it should revert
+├── when pool is finalized
+│ └── it should revert
+├── when swap fee is below MIN_FEE
+│ └── it should revert
+├── when swap fee is above MAX_FEE
+│ └── it should revert
+└── when preconditions are met
+ ├── it emits LOG_CALL event
+ └── it sets swap fee
+
+BPool::setController
+├── when reentrancy lock is set
+│ └── it should revert
+├── when caller is not controller
+│ └── it should revert
+├── when new controller is zero address
+│ └── it should revert
+└── when preconditions are met
+ ├── it emits LOG_CALL event
+ └── it sets new controller
+
BPool::isFinalized
├── when pool is finalized
│ └── it returns true
@@ -20,3 +46,132 @@ BPool::isBound
BPool::getNumTokens
└── when called
└── it returns number of tokens
+
+BPool::getFinalTokens
+├── when reentrancy lock is set
+│ └── it should revert
+├── when pool is not finalized
+│ └── it should revert
+└── when preconditions are met
+ └── it returns pool tokens
+
+BPool::getCurrentTokens
+├── when reentrancy lock is set
+│ └── it should revert
+└── when preconditions are met
+ └── it returns pool tokens
+
+BPool::getDenormalizedWeight
+├── when reentrancy lock is set
+│ └── it should revert
+├── when token is not bound
+│ └── it should revert
+└── when preconditions are met
+ └── it returns token weight
+
+BPool::getTotalDenormalizedWeight
+├── when reentrancy lock is set
+│ └── it should revert
+└── when preconditions are met
+ └── it returns total weight
+
+BPool::getNormalizedWeight
+├── when reentrancy lock is set
+│ └── it should revert
+├── when token is not bound
+│ └── it should revert
+└── when preconditions are met
+ └── it returns normalized weight
+
+BPool::getBalance
+├── when reentrancy lock is set
+│ └── it should revert
+├── when token is not bound
+│ └── it should revert
+└── when preconditions are met
+ ├── it queries token balance
+ └── it returns token balance
+
+BPool::getSwapFee
+├── when reentrancy lock is set
+│ └── it should revert
+└── when preconditions are met
+ └── it returns swap fee
+
+BPool::getController
+├── when reentrancy lock is set
+│ └── it should revert
+└── when preconditions are met
+ └── it returns controller
+
+BPool::getSpotPrice
+├── when reentrancy lock is set
+│ └── it should revert
+├── when token in is not bound
+│ └── it should revert
+├── when token out is not bound
+│ └── it should revert
+└── when preconditions are met
+ ├── it queries token in balance
+ ├── it queries token out balance
+ └── it returns spot price
+
+BPool::getSpotPriceSansFee
+├── when reentrancy lock is set
+│ └── it should revert
+├── when token in is not bound
+│ └── it should revert
+├── when token out is not bound
+│ └── it should revert
+└── when preconditions are met
+ ├── it queries token in balance
+ ├── it queries token out balance
+ └── it returns spot price sans fee
+
+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
+
+BPool::_pushUnderlying
+├── when underlying token returns false
+│ └── it should revert
+├── when underlying token doesnt return a value
+│ └── it assumes transfer success
+├── when underlying token reverts without data
+│ └── it should revert // FailedInnerCall
+├── when underlying token reverts with data
+│ └── it should revert
+└── when underlying token returns true
+ └── it calls underlying transfer
+
+BPool::_pullUnderlying
+├── when underlying token returns false
+│ └── it should revert
+├── when underlying token doesnt return a value
+│ └── it assumes transferFrom success
+├── when underlying token reverts without data
+│ └── it should revert // FailedInnerCall
+├── when underlying token reverts with data
+│ └── it should revert
+└── when underlying token returns true
+ └── it calls underlying transferFrom
+
+BPool::_mintPoolShare
+└── when called
+ └── it mints shares to the pool's own balance
+
+BPool::_burnPoolShare
+├── when pool has less balance than amount to burn
+│ └── it should revert
+└── when pool has enough balance
+ └── it mints shares to the pool's own balance
diff --git a/test/unit/BPool/BPoolBase.sol b/test/unit/BPool/BPoolBase.sol
deleted file mode 100644
index 41dd6c88..00000000
--- a/test/unit/BPool/BPoolBase.sol
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.25;
-
-import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
-import {BConst} from 'contracts/BConst.sol';
-import {Test} from 'forge-std/Test.sol';
-import {IBPool} from 'interfaces/IBPool.sol';
-import {MockBPool} from 'test/smock/MockBPool.sol';
-import {Utils} from 'test/utils/Utils.sol';
-
-contract BPoolBase is Test, BConst, Utils {
- MockBPool public bPool;
- address public deployer = makeAddr('deployer');
-
- address public token = makeAddr('token');
- uint256 public tokenWeight = 1e18;
- uint256 public totalWeight = 10e18;
- address public secondToken = makeAddr('secondToken');
-
- function setUp() public virtual {
- vm.prank(deployer);
- bPool = new MockBPool();
-
- vm.mockCall(token, abi.encodePacked(IERC20.transferFrom.selector), abi.encode());
- vm.mockCall(token, abi.encodePacked(IERC20.transfer.selector), abi.encode());
- }
-
- function _setRandomTokens(uint256 _length) internal returns (address[] memory _tokensToAdd) {
- _tokensToAdd = _getDeterministicTokenArray(_length);
- for (uint256 i = 0; i < _length; i++) {
- _setRecord(_tokensToAdd[i], IBPool.Record({bound: true, index: i, denorm: 0}));
- }
- _setTokens(_tokensToAdd);
- }
-
- function _setTokens(address[] memory _tokens) internal {
- bPool.set__tokens(_tokens);
- }
-
- function _setRecord(address _token, IBPool.Record memory _record) internal {
- bPool.set__records(_token, _record);
- }
-}
diff --git a/test/unit/BPool/BPoolBase.t.sol b/test/unit/BPool/BPoolBase.t.sol
new file mode 100644
index 00000000..1495f9d1
--- /dev/null
+++ b/test/unit/BPool/BPoolBase.t.sol
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {BConst} from 'contracts/BConst.sol';
+import {Test} from 'forge-std/Test.sol';
+import {IBPool} from 'interfaces/IBPool.sol';
+
+import {LibString} from 'solmate/utils/LibString.sol';
+import {MockBPool} from 'test/smock/MockBPool.sol';
+
+contract BPoolBase is Test, BConst {
+ using LibString for uint256;
+
+ address[] public tokens;
+
+ MockBPool public bPool;
+
+ function setUp() public virtual {
+ bPool = new MockBPool();
+ tokens.push(makeAddr('token0'));
+ tokens.push(makeAddr('token1'));
+ }
+
+ function _getDeterministicTokenArray(uint256 _length) internal returns (address[] memory _tokenArray) {
+ _tokenArray = new address[](_length);
+ for (uint256 i = 0; i < _length; i++) {
+ _tokenArray[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 _setRandomTokens(uint256 _length) internal returns (address[] memory _tokensToAdd) {
+ _tokensToAdd = _getDeterministicTokenArray(_length);
+ for (uint256 i = 0; i < _length; i++) {
+ bPool.set__records(_tokensToAdd[i], IBPool.Record({bound: true, index: i, denorm: 0}));
+ }
+ bPool.set__tokens(_tokensToAdd);
+ }
+}
diff --git a/test/unit/BPool/BPool_Bind.t.sol b/test/unit/BPool/BPool_Bind.t.sol
index 95ede7d6..8e8c0bc5 100644
--- a/test/unit/BPool/BPool_Bind.t.sol
+++ b/test/unit/BPool/BPool_Bind.t.sol
@@ -1,11 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
-import {BPoolBase} from './BPoolBase.sol';
+import {BPoolBase} from './BPoolBase.t.sol';
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {IBPool} from 'interfaces/IBPool.sol';
contract BPoolBind is BPoolBase {
+ address public token;
uint256 public tokenBindBalance = 100e18;
+ uint256 public tokenWeight = 1e18;
+ uint256 public totalWeight = 10e18;
+
+ function setUp() public virtual override {
+ super.setUp();
+ token = tokens[0];
+
+ vm.mockCall(token, abi.encodePacked(IERC20.transferFrom.selector), abi.encode());
+ vm.mockCall(token, abi.encodePacked(IERC20.transfer.selector), abi.encode());
+ }
function test_RevertWhen_ReentrancyLockIsSet() external {
bPool.call__setLock(_MUTEX_TAKEN);
@@ -16,76 +28,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(token, tokenBindBalance, tokenWeight);
}
- modifier whenCallerIsController() {
- vm.startPrank(deployer);
- _;
- }
-
- function test_RevertWhen_TokenIsAlreadyBound() external whenCallerIsController {
- _setRecord(token, IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
+ function test_RevertWhen_TokenIsAlreadyBound() external {
+ bPool.set__records(token, IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
// it should revert
vm.expectRevert(IBPool.BPool_TokenAlreadyBound.selector);
bPool.bind(token, 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(token, 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(token, tokenBindBalance, tokenWeight);
}
- function test_RevertWhen_TokenWeightIsTooLow() external whenCallerIsController {
+ function test_RevertWhen_TokenWeightIsTooLow() external {
// it should revert
vm.expectRevert(IBPool.BPool_WeightBelowMinimum.selector);
bPool.bind(token, 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(token, 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(token, 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(token, 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(token, deployer, tokenBindBalance);
+ bPool.expectCall__pullUnderlying(token, 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, token, tokenBindBalance, tokenWeight);
- emit IBPool.LOG_CALL(IBPool.bind.selector, deployer, _data);
+ emit IBPool.LOG_CALL(IBPool.bind.selector, address(this), _data);
bPool.bind(token, tokenBindBalance, tokenWeight);
diff --git a/test/unit/BPool/BPool_Bind.tree b/test/unit/BPool/BPool_Bind.tree
index f0ab3278..a7157745 100644
--- a/test/unit/BPool/BPool_Bind.tree
+++ b/test/unit/BPool/BPool_Bind.tree
@@ -3,26 +3,25 @@ BPool::Bind
│ └── it should revert
├── when caller is NOT controller
│ └── it should revert
-└── when caller is controller
- ├── when token is already bound
- │ └── it should revert
- ├── when pool is finalized
- │ └── it should revert
- ├── when MAX_BOUND_TOKENS tokens are already bound
- │ └── it should revert
- ├── when token weight is too low
- │ └── it should revert
- ├── when token weight is too high
- │ └── it should revert
- ├── when too little balance is provided
- │ └── it should revert
- ├── when weight sum exceeds MAX_TOTAL_WEIGHT
- │ └── it should revert
- └── when token can be bound
- ├── it sets the reentrancy lock
- ├── it emits LOG_CALL event
- ├── it increments _totalWeight
- ├── it calls _pullUnderlying
- ├── it adds token to the tokens array
- ├── it sets the token record
- └── it clears the reentrancy lock
+├── when token is already bound
+│ └── it should revert
+├── when pool is finalized
+│ └── it should revert
+├── when MAX_BOUND_TOKENS tokens are already bound
+│ └── it should revert
+├── when token weight is too low
+│ └── it should revert
+├── when token weight is too high
+│ └── it should revert
+├── when too little balance is provided
+│ └── it should revert
+├── when weight sum exceeds MAX_TOTAL_WEIGHT
+│ └── it should revert
+└── when token can be bound
+ ├── it sets the reentrancy lock
+ ├── it emits LOG_CALL event
+ ├── it increments _totalWeight
+ ├── it calls _pullUnderlying
+ ├── it adds token to the tokens array
+ ├── it sets the token record
+ └── it clears the reentrancy lock
diff --git a/test/unit/BPool/BPool_ExitPool.t.sol b/test/unit/BPool/BPool_ExitPool.t.sol
new file mode 100644
index 00000000..143fe7ba
--- /dev/null
+++ b/test/unit/BPool/BPool_ExitPool.t.sol
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {BPoolBase} from './BPoolBase.t.sol';
+
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+
+import {BNum} from 'contracts/BNum.sol';
+import {IBPool} from 'interfaces/IBPool.sol';
+
+contract BPoolExitPool is BPoolBase, BNum {
+ // Valid scenario
+ uint256 public constant SHARE_PROPORTION = 20;
+ uint256 public poolAmountIn = INIT_POOL_SUPPLY / SHARE_PROPORTION;
+ uint256 public token0Balance = 20e18;
+ uint256 public token1Balance = 50e18;
+ // currently hard-coded to zero
+ uint256 public exitFee = bmul(poolAmountIn, EXIT_FEE);
+ uint256[] minAmountsOut;
+
+ // when buring n pool shares, caller expects enough amount X of every token t
+ // should be sent to statisfy:
+ // Xt = n/BPT.totalSupply() * t.balanceOf(BPT)
+ uint256 public expectedToken0Out = token0Balance / SHARE_PROPORTION;
+ uint256 public expectedToken1Out = token1Balance / SHARE_PROPORTION;
+
+ function setUp() public virtual override {
+ super.setUp();
+ 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());
+ // token weights are not used for all-token exits
+ bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0}));
+ bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: 0}));
+ // underlying balances are used instead
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token0Balance)));
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token1Balance)));
+
+ // caller not having enough pool shares would revert inside `_pullPoolShare`
+ bPool.mock_call__pullPoolShare(address(this), poolAmountIn);
+
+ minAmountsOut = new uint256[](2);
+ minAmountsOut[0] = expectedToken0Out;
+ minAmountsOut[1] = expectedToken1Out;
+ }
+
+ function test_RevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.exitPool(0, minAmountsOut);
+ }
+
+ function test_RevertWhen_PoolIsNotFinalized() external {
+ bPool.set__finalized(false);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
+ bPool.exitPool(0, minAmountsOut);
+ }
+
+ function test_RevertWhen_TotalSupplyIsZero() external {
+ bPool.call__burnPoolShare(INIT_POOL_SUPPLY);
+ // it should revert
+ // division by zero
+ vm.expectRevert(BNum.BNum_DivZero.selector);
+ bPool.exitPool(0, minAmountsOut);
+ }
+
+ function test_RevertWhen_PoolAmountInIsTooSmall(uint256 amountIn) external {
+ amountIn = bound(amountIn, 0, (INIT_POOL_SUPPLY / 1e18) / 2 - 1);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_InvalidPoolRatio.selector);
+ bPool.exitPool(amountIn, minAmountsOut);
+ }
+
+ function test_RevertWhen_BalanceOfPoolInAnyTokenIsZero() external {
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(0)));
+ // it should revert
+ vm.expectRevert(IBPool.BPool_InvalidTokenAmountOut.selector);
+ bPool.exitPool(poolAmountIn, minAmountsOut);
+ }
+
+ function test_RevertWhen_ReturnedAmountOfATokenIsLessThanMinAmountsOut() external {
+ minAmountsOut[1] += 1;
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinAmountOut.selector);
+ bPool.exitPool(poolAmountIn, minAmountsOut);
+ }
+
+ function test_WhenPreconditionsAreMet() external {
+ // it pulls poolAmountIn shares
+ bPool.expectCall__pullPoolShare(address(this), poolAmountIn);
+ // it sends exitFee to factory
+ bPool.expectCall__pushPoolShare(address(this), exitFee);
+ // it burns poolAmountIn - exitFee shares
+ bPool.expectCall__burnPoolShare(poolAmountIn - exitFee);
+ // it calls _pushUnderlying for every token
+ bPool.mock_call__pushUnderlying(tokens[0], address(this), expectedToken0Out);
+ bPool.expectCall__pushUnderlying(tokens[0], address(this), expectedToken0Out);
+ bPool.mock_call__pushUnderlying(tokens[1], address(this), expectedToken1Out);
+ bPool.expectCall__pushUnderlying(tokens[1], address(this), expectedToken1Out);
+ // it sets the reentrancy lock
+ bPool.expectCall__setLock(_MUTEX_TAKEN);
+ // it emits LOG_CALL event
+ bytes memory _data = abi.encodeWithSelector(IBPool.exitPool.selector, poolAmountIn, minAmountsOut);
+ vm.expectEmit();
+ emit IBPool.LOG_CALL(IBPool.exitPool.selector, address(this), _data);
+ // it emits LOG_EXIT event for every token
+ vm.expectEmit();
+ emit IBPool.LOG_EXIT(address(this), tokens[0], expectedToken0Out);
+ vm.expectEmit();
+ emit IBPool.LOG_EXIT(address(this), tokens[1], expectedToken1Out);
+
+ bPool.exitPool(poolAmountIn, minAmountsOut);
+
+ // it clears the reentrancy lock
+ assertEq(_MUTEX_FREE, bPool.call__getLock());
+ }
+}
diff --git a/test/unit/BPool/BPool_ExitPool.tree b/test/unit/BPool/BPool_ExitPool.tree
new file mode 100644
index 00000000..b13d6c20
--- /dev/null
+++ b/test/unit/BPool/BPool_ExitPool.tree
@@ -0,0 +1,22 @@
+BPool::ExitPool
+├── when reentrancy lock is set
+│ └── it should revert
+├── when pool is not finalized
+│ └── it should revert
+├── when total supply is zero
+│ └── it should revert // division by zero
+├── when pool amount in is too small
+│ └── it should revert
+├── when balance of pool in any token is zero
+│ └── it should revert
+├── when returned amount of a token is less than minAmountsOut
+│ └── it should revert
+└── when preconditions are met
+ ├── it emits LOG_CALL event
+ ├── it sets the reentrancy lock
+ ├── it pulls poolAmountIn shares
+ ├── it sends exitFee to factory
+ ├── it burns poolAmountIn - exitFee shares
+ ├── it calls _pushUnderlying for every token
+ ├── it emits LOG_EXIT event for every token
+ └── it clears the reentrancy lock
diff --git a/test/unit/BPool/BPool_JoinPool.t.sol b/test/unit/BPool/BPool_JoinPool.t.sol
index 6cc9f9a9..1b954745 100644
--- a/test/unit/BPool/BPool_JoinPool.t.sol
+++ b/test/unit/BPool/BPool_JoinPool.t.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
-import {BPoolBase} from './BPoolBase.sol';
+import {BPoolBase} from './BPoolBase.t.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {BNum} from 'contracts/BNum.sol';
@@ -25,16 +25,13 @@ contract BPoolJoinPool is BPoolBase {
bPool.set__finalized(true);
// mint an initial amount of pool shares (expected to happen at _finalize)
bPool.call__mintPoolShare(INIT_POOL_SUPPLY);
- address[] memory _tokens = new address[](2);
- _tokens[0] = token;
- _tokens[1] = secondToken;
- bPool.set__tokens(_tokens);
+ bPool.set__tokens(_tokensToMemory());
// token weights are not used for all-token joins
- _setRecord(token, IBPool.Record({bound: true, index: 0, denorm: 0}));
- _setRecord(secondToken, IBPool.Record({bound: true, index: 1, denorm: 0}));
+ bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0}));
+ bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 1, denorm: 0}));
// underlying balances are used instead
- vm.mockCall(token, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token0Balance)));
- vm.mockCall(secondToken, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token1Balance)));
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token0Balance)));
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(token1Balance)));
maxAmountsIn = new uint256[](2);
maxAmountsIn[0] = requiredToken0In;
@@ -74,7 +71,7 @@ contract BPoolJoinPool is BPoolBase {
}
function test_RevertWhen_BalanceOfPoolInAnyTokenIsZero() external {
- vm.mockCall(secondToken, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(0)));
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(0)));
// it should revert
vm.expectRevert(IBPool.BPool_InvalidTokenAmountIn.selector);
bPool.joinPool(poolAmountOut, maxAmountsIn);
@@ -91,10 +88,10 @@ contract BPoolJoinPool is BPoolBase {
// it sets reentrancy lock
bPool.expectCall__setLock(_MUTEX_TAKEN);
// it calls _pullUnderlying for every token
- bPool.mock_call__pullUnderlying(token, address(this), requiredToken0In);
- bPool.expectCall__pullUnderlying(token, address(this), requiredToken0In);
- bPool.mock_call__pullUnderlying(secondToken, address(this), requiredToken1In);
- bPool.expectCall__pullUnderlying(secondToken, address(this), requiredToken1In);
+ bPool.mock_call__pullUnderlying(tokens[0], address(this), requiredToken0In);
+ bPool.expectCall__pullUnderlying(tokens[0], address(this), requiredToken0In);
+ bPool.mock_call__pullUnderlying(tokens[1], address(this), requiredToken1In);
+ bPool.expectCall__pullUnderlying(tokens[1], address(this), requiredToken1In);
// it mints the pool shares
bPool.expectCall__mintPoolShare(poolAmountOut);
// it sends pool shares to caller
@@ -109,9 +106,9 @@ contract BPoolJoinPool is BPoolBase {
emit IBPool.LOG_CALL(IBPool.joinPool.selector, address(this), _data);
// it emits LOG_JOIN event for every token
vm.expectEmit();
- emit IBPool.LOG_JOIN(address(this), token, requiredToken0In);
+ emit IBPool.LOG_JOIN(address(this), tokens[0], requiredToken0In);
vm.expectEmit();
- emit IBPool.LOG_JOIN(address(this), secondToken, requiredToken1In);
+ emit IBPool.LOG_JOIN(address(this), tokens[1], requiredToken1In);
bPool.joinPool(poolAmountOut, maxAmounts);
// it clears the reentrancy lock
assertEq(_MUTEX_FREE, bPool.call__getLock());
diff --git a/test/unit/BPool/BPool_SwapExactAmountIn.t.sol b/test/unit/BPool/BPool_SwapExactAmountIn.t.sol
new file mode 100644
index 00000000..51af608d
--- /dev/null
+++ b/test/unit/BPool/BPool_SwapExactAmountIn.t.sol
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {BPoolBase} from './BPoolBase.t.sol';
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+
+import {BNum} from 'contracts/BNum.sol';
+import {IBPool} from 'interfaces/IBPool.sol';
+
+contract BPoolSwapExactAmountIn is BPoolBase, BNum {
+ // Valid scenario
+ address public tokenIn;
+ uint256 public tokenAmountIn = 3e18;
+
+ uint256 public tokenInBalance = 10e18;
+ uint256 public tokenOutBalance = 40e18;
+ // pool is expected to keep 2X the value of tokenIn than tokenOut
+ uint256 public tokenInWeight = 2e18;
+ uint256 public tokenOutWeight = 1e18;
+
+ address public tokenOut;
+ // (tokenInBalance / tokenInWeight) / (tokenOutBalance/ tokenOutWeight)
+ uint256 public spotPriceBeforeSwapWithoutFee = 0.125e18;
+ uint256 public spotPriceBeforeSwap = bmul(spotPriceBeforeSwapWithoutFee, bdiv(BONE, bsub(BONE, MIN_FEE)));
+ // from bmath: 40*(1-(10/(10+3*(1-10^-6)))^2)
+ uint256 public expectedAmountOut = 16.3313500227545254e18;
+ // (tokenInBalance / tokenInWeight) / (tokenOutBalance/ tokenOutWeight)
+ // (13 / 2) / (40-expectedAmountOut/ 1)
+ uint256 public spotPriceAfterSwapWithoutFee = 0.274624873250014625e18;
+ uint256 public spotPriceAfterSwap = bmul(spotPriceAfterSwapWithoutFee, bdiv(BONE, bsub(BONE, MIN_FEE)));
+
+ function setUp() public virtual override {
+ super.setUp();
+ tokenIn = tokens[0];
+ tokenOut = tokens[1];
+ bPool.set__finalized(true);
+ bPool.set__tokens(tokens);
+ bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight}));
+ bPool.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)));
+ }
+
+ function test_RevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_PoolIsNotFinalized() external {
+ bPool.set__finalized(false);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_TokenInIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.swapExactAmountIn(makeAddr('unknown token'), tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_TokenOutIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn, makeAddr('unknown token'), expectedAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_TokenAmountInExceedsMaxAllowedRatio(uint256 tokenAmountIn_) external {
+ tokenAmountIn_ = bound(tokenAmountIn_, bmul(tokenInBalance, MAX_IN_RATIO) + 1, type(uint256).max);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxRatio.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn_, tokenOut, 0, 0);
+ }
+
+ function test_RevertWhen_SpotPriceBeforeSwapExceedsMaxPrice() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceBeforeSwap - 1);
+ }
+
+ function test_RevertWhen_CalculatedTokenAmountOutIsLessThanMinAmountOut() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenAmountOutBelowMinOut.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut + 1, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_SpotPriceAfterSwapExceedsSpotPriceBeforeSwap() external {
+ // it should revert
+ // skipping since the code for this is unreachable without manually
+ // overriding `calcSpotPrice` in a mock:
+ // P_{sb} = \frac{\frac{b_i}{w_i}}{\frac{b_o}{w_o}}
+ // P_{sa} = \frac{\frac{b_i + a_i}{w_i}}{\frac{b_o - a_o}{w_o}}
+ // ...and both a_i (amount in) and a_o (amount out) are uints
+ vm.skip(true);
+ }
+
+ function test_RevertWhen_SpotPriceAfterSwapExceedsMaxPrice() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap - 1);
+ }
+
+ function test_RevertWhen_SpotPriceBeforeSwapExceedsTokenRatioAfterSwap() external {
+ uint256 tokenAmountIn_ = 30e18;
+ uint256 balanceTokenIn_ = 36_830_000_000_000_000_000_000_000_000_000;
+ uint256 weightTokenIn_ = 1e18;
+ uint256 balanceTokenOut_ = 18_100_000_000_000_000_000_000_000_000_000;
+ uint256 weightTokenOut_ = 1e18;
+ uint256 swapFee_ = 0.019e18;
+ vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(balanceTokenIn_)));
+ vm.mockCall(tokenOut, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(balanceTokenOut_)));
+ bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: weightTokenIn_}));
+ bPool.set__records(tokenOut, IBPool.Record({bound: true, index: 1, denorm: weightTokenOut_}));
+ bPool.set__swapFee(swapFee_);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_SpotPriceBeforeAboveTokenRatio.selector);
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn_, tokenOut, 0, type(uint256).max);
+ }
+
+ function test_WhenPreconditionsAreMet() external {
+ // it sets reentrancy lock
+ bPool.expectCall__setLock(_MUTEX_TAKEN);
+ // it calls _pullUnderlying for tokenIn
+ bPool.mock_call__pullUnderlying(tokenIn, address(this), tokenAmountIn);
+ bPool.expectCall__pullUnderlying(tokenIn, address(this), tokenAmountIn);
+ // it calls _pushUnderlying for tokenOut
+ bPool.mock_call__pushUnderlying(tokenOut, address(this), expectedAmountOut);
+ bPool.expectCall__pushUnderlying(tokenOut, address(this), expectedAmountOut);
+ // it emits a LOG_CALL event
+ bytes memory _data = abi.encodeCall(
+ IBPool.swapExactAmountIn, (tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap)
+ );
+ vm.expectEmit();
+ emit IBPool.LOG_CALL(IBPool.swapExactAmountIn.selector, address(this), _data);
+ // it emits a LOG_SWAP event
+ vm.expectEmit();
+ emit IBPool.LOG_SWAP(address(this), tokenIn, tokenOut, tokenAmountIn, expectedAmountOut);
+
+ // it returns the tokenOut amount swapped
+ // it returns the spot price after the swap
+ (uint256 out, uint256 priceAfter) =
+ bPool.swapExactAmountIn(tokenIn, tokenAmountIn, tokenOut, expectedAmountOut, spotPriceAfterSwap);
+ assertEq(out, expectedAmountOut);
+ assertEq(priceAfter, spotPriceAfterSwap);
+ // it clears the reeentrancy lock
+ assertEq(bPool.call__getLock(), _MUTEX_FREE);
+ }
+}
diff --git a/test/unit/BPool/BPool_SwapExactAmountIn.tree b/test/unit/BPool/BPool_SwapExactAmountIn.tree
new file mode 100644
index 00000000..8d78fd2e
--- /dev/null
+++ b/test/unit/BPool/BPool_SwapExactAmountIn.tree
@@ -0,0 +1,30 @@
+BPool::SwapExactAmountIn
+├── when reentrancy lock is set
+│ └── it should revert
+├── when pool is not finalized
+│ └── it should revert
+├── when token in is not bound
+│ └── it should revert
+├── when token out is not bound
+│ └── it should revert
+├── when token amount in exceeds max allowed ratio
+│ └── it should revert
+├── when spot price before swap exceeds maxPrice
+│ └── it should revert
+├── when calculated token amount out is less than minAmountOut
+│ └── it should revert
+├── when spot price after swap exceeds spot price before swap
+│ └── it should revert
+├── when spot price after swap exceeds maxPrice
+│ └── it should revert
+├── when spot price before swap exceeds token ratio after swap
+│ └── it should revert
+└── when preconditions are met
+ ├── it emits a LOG_CALL event
+ ├── it sets the reentrancy lock
+ ├── it emits a LOG_SWAP event
+ ├── it calls _pullUnderlying for tokenIn
+ ├── it calls _pushUnderlying for tokenOut
+ ├── it returns the tokenOut amount swapped
+ ├── it returns the spot price after the swap
+ └── it clears the reeentrancy lock
diff --git a/test/unit/BPool/BPool_SwapExactAmountOut.t.sol b/test/unit/BPool/BPool_SwapExactAmountOut.t.sol
new file mode 100644
index 00000000..1f379b5b
--- /dev/null
+++ b/test/unit/BPool/BPool_SwapExactAmountOut.t.sol
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.25;
+
+import {BPoolBase} from './BPoolBase.t.sol';
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+
+import {BNum} from 'contracts/BNum.sol';
+import {IBPool} from 'interfaces/IBPool.sol';
+
+contract BPoolSwapExactAmountOut is BPoolBase, BNum {
+ // Valid scenario
+ address public tokenIn;
+ uint256 public tokenAmountOut = 1e18;
+
+ uint256 public tokenInBalance = 50e18;
+ uint256 public tokenOutBalance = 20e18;
+ // pool is expected to keep 3X the value of tokenOut than tokenIn
+ uint256 public tokenInWeight = 1e18;
+ uint256 public tokenOutWeight = 3e18;
+
+ address public tokenOut;
+ // (tokenInBalance / tokenInWeight) / (tokenOutBalance/ tokenOutWeight)
+ uint256 public spotPriceBeforeSwapWithoutFee = 7.5e18;
+ uint256 public spotPriceBeforeSwap = bmul(spotPriceBeforeSwapWithoutFee, bdiv(BONE, bsub(BONE, MIN_FEE)));
+ // from bmath: bi*((bo/(bo-ao))^(wo/wi) - 1)/(1-f)
+ // (50*((20/(20-1))^(3) - 1))/(1-10^-6)
+ uint256 public expectedAmountIn = 8.317547317401523552e18;
+ // (tokenInBalance / tokenInWeight) / (tokenOutBalance/ tokenOutWeight)
+ // (50+8.317547317401523553 / 1) / (19/ 3)
+ uint256 public spotPriceAfterSwapWithoutFee = 9.208033786958135298e18;
+ uint256 public spotPriceAfterSwap = bmul(spotPriceAfterSwapWithoutFee, bdiv(BONE, bsub(BONE, MIN_FEE)));
+
+ function setUp() public virtual override {
+ super.setUp();
+ tokenIn = tokens[0];
+ tokenOut = tokens[1];
+ bPool.set__finalized(true);
+ bPool.set__tokens(tokens);
+ bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInWeight}));
+ bPool.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)));
+ }
+
+ function test_RevertWhen_ReentrancyLockIsSet() external {
+ bPool.call__setLock(_MUTEX_TAKEN);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_Reentrancy.selector);
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn, tokenOut, tokenAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_PoolIsNotFinalized() external {
+ bPool.set__finalized(false);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_PoolNotFinalized.selector);
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn, tokenOut, tokenAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_TokenInIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.swapExactAmountOut(makeAddr('unkonwn token'), expectedAmountIn, tokenOut, tokenAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_TokenOutIsNotBound() external {
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn, makeAddr('unkonwn token'), tokenAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_TokenOutExceedsMaxAllowedRatio(uint256 tokenAmountOut_) external {
+ tokenAmountOut_ = bound(tokenAmountOut_, bmul(tokenOutBalance, MAX_OUT_RATIO + 1), type(uint256).max);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_TokenAmountOutAboveMaxOut.selector);
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn, tokenOut, tokenAmountOut_, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_SpotPriceBeforeSwapExceedsMaxPrice() external {
+ vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
+ // it should revert
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn, tokenOut, tokenAmountOut, spotPriceBeforeSwap - 1);
+ }
+
+ function test_RevertWhen_SpotPriceAfterSwapExceedsMaxPrice() external {
+ vm.expectRevert(IBPool.BPool_SpotPriceAboveMaxPrice.selector);
+ // it should revert
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn, tokenOut, tokenAmountOut, spotPriceAfterSwap - 1);
+ }
+
+ function test_RevertWhen_RequiredTokenInIsMoreThanMaxAmountIn() external {
+ vm.expectRevert(IBPool.BPool_TokenAmountInAboveMaxAmountIn.selector);
+ // it should revert
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn - 1, tokenOut, tokenAmountOut, spotPriceAfterSwap);
+ }
+
+ function test_RevertWhen_TokenRatioAfterSwapExceedsSpotPriceBeforeSwap() external {
+ // params obtained from legacy fuzz tests:
+ uint256 tokenAmountOut_ = 621_143_522_536_167_460_787_693_100_883_186_780;
+ uint256 tokenInBalance_ = 1_020_504_230_788_863_581_113_405_134_266_627;
+ uint256 tokenInDenorm_ = 49_062_504_624_460_684_226;
+ uint256 tokenOutBalance_ = 15_332_515_003_530_544_593_793_307_770_397_516_084_212_022_325;
+ uint256 tokenOutDenorm_ = 19_469_010_750_289_341_034;
+ uint256 swapFee_ = 894_812_326_421_000_610;
+
+ vm.mockCall(tokenIn, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenInBalance_)));
+ vm.mockCall(tokenOut, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(uint256(tokenOutBalance_)));
+ bPool.set__records(tokenIn, IBPool.Record({bound: true, index: 0, denorm: tokenInDenorm_}));
+ bPool.set__records(tokenOut, IBPool.Record({bound: true, index: 1, denorm: tokenOutDenorm_}));
+ bPool.set__swapFee(swapFee_);
+ // it should revert
+ vm.expectRevert(IBPool.BPool_SpotPriceBeforeAboveTokenRatio.selector);
+ bPool.swapExactAmountOut(tokenIn, type(uint256).max, tokenOut, tokenAmountOut_, type(uint256).max);
+ }
+
+ function test_WhenPreconditionsAreMet() external {
+ // it sets reentrancy lock
+ bPool.expectCall__setLock(_MUTEX_TAKEN);
+ // it calls _pullUnderlying for tokenIn
+ bPool.mock_call__pullUnderlying(tokenIn, address(this), expectedAmountIn);
+ bPool.expectCall__pullUnderlying(tokenIn, address(this), expectedAmountIn);
+ // it calls _pushUnderlying for tokenOut
+ bPool.mock_call__pushUnderlying(tokenOut, address(this), tokenAmountOut);
+ bPool.expectCall__pushUnderlying(tokenOut, address(this), tokenAmountOut);
+ bytes memory _data = abi.encodeCall(
+ IBPool.swapExactAmountOut, (tokenIn, expectedAmountIn, tokenOut, tokenAmountOut, spotPriceAfterSwap)
+ );
+ // it emits a LOG_CALL event
+ vm.expectEmit();
+ emit IBPool.LOG_CALL(IBPool.swapExactAmountOut.selector, address(this), _data);
+ // it emits a LOG_SWAP event
+ vm.expectEmit();
+ emit IBPool.LOG_SWAP(address(this), tokenIn, tokenOut, expectedAmountIn, tokenAmountOut);
+ // it returns the tokenIn amount swapped
+ // it returns the spot price after the swap
+ (uint256 in_, uint256 priceAfter) =
+ bPool.swapExactAmountOut(tokenIn, expectedAmountIn, tokenOut, tokenAmountOut, spotPriceAfterSwap);
+ assertEq(in_, expectedAmountIn);
+ assertEq(priceAfter, spotPriceAfterSwap);
+ // it clears the reeentrancy lock
+ assertEq(bPool.call__getLock(), _MUTEX_FREE);
+ }
+}
diff --git a/test/unit/BPool/BPool_SwapExactAmountOut.tree b/test/unit/BPool/BPool_SwapExactAmountOut.tree
new file mode 100644
index 00000000..6535647a
--- /dev/null
+++ b/test/unit/BPool/BPool_SwapExactAmountOut.tree
@@ -0,0 +1,28 @@
+BPool::SwapExactAmountOut
+├── when reentrancy lock is set
+│ └── it should revert
+├── when pool is not finalized
+│ └── it should revert
+├── when token in is not bound
+│ └── it should revert
+├── when token out is not bound
+│ └── it should revert
+├── when token out exceeds max allowed ratio
+│ └── it should revert
+├── when spot price before swap exceeds maxPrice
+│ └── it should revert
+├── when spot price after swap exceeds maxPrice
+│ └── it should revert
+├── when required tokenIn is more than maxAmountIn
+│ └── it should revert
+├── when token ratio after swap exceeds spot price before swap
+│ └── it should revert
+└── when preconditions are met
+ ├── it emits a LOG_CALL event
+ ├── it sets the reentrancy lock
+ ├── it emits a LOG_SWAP event
+ ├── it calls _pullUnderlying for tokenIn
+ ├── it calls _pushUnderlying for tokenOut
+ ├── it returns the tokenIn amount swapped
+ ├── it returns the spot price after the swap
+ └── it clears the reeentrancy lock
diff --git a/test/unit/BPool/BPool_Unbind.t.sol b/test/unit/BPool/BPool_Unbind.t.sol
index f854c0c7..5bcbece2 100644
--- a/test/unit/BPool/BPool_Unbind.t.sol
+++ b/test/unit/BPool/BPool_Unbind.t.sol
@@ -1,100 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
-import {BPoolBase} from './BPoolBase.sol';
+import {BPoolBase} from './BPoolBase.t.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
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();
- vm.mockCall(token, abi.encodePacked(IERC20.balanceOf.selector), abi.encode(boundTokenAmount));
- vm.mockCall(secondToken, abi.encodePacked(IERC20.transferFrom.selector), abi.encode());
+ vm.mockCall(tokens[0], abi.encodePacked(IERC20.balanceOf.selector), abi.encode(boundTokenAmount));
+ vm.mockCall(tokens[1], abi.encodePacked(IERC20.transferFrom.selector), abi.encode());
}
function test_RevertWhen_ReentrancyLockIsSet() external {
bPool.call__setLock(_MUTEX_TAKEN);
vm.expectRevert(IBPool.BPool_Reentrancy.selector);
// it should revert
- bPool.unbind(token);
+ bPool.unbind(tokens[0]);
}
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.unbind(token);
+ bPool.unbind(tokens[0]);
}
- modifier whenCallerIsController() {
- vm.startPrank(deployer);
- _;
- }
-
- function test_RevertWhen_TokenIsNotBound() external whenCallerIsController {
+ function test_RevertWhen_TokenIsNotBound() external {
vm.expectRevert(IBPool.BPool_TokenNotBound.selector);
// it should revert
- bPool.unbind(token);
+ bPool.unbind(tokens[0]);
}
- function test_RevertWhen_PoolIsFinalized() external whenCallerIsController {
- _setRecord(token, IBPool.Record({bound: true, index: 0, denorm: 0}));
+ function test_RevertWhen_PoolIsFinalized() external {
+ bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: 0}));
bPool.set__finalized(true);
// it should revert
vm.expectRevert(IBPool.BPool_PoolIsFinalized.selector);
- bPool.unbind(token);
+ bPool.unbind(tokens[0]);
}
modifier whenTokenCanBeUnbound() {
- _setRecord(token, IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
+ bPool.set__records(tokens[0], IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
bPool.set__totalWeight(totalWeight);
address[] memory tokens = new address[](1);
- tokens[0] = token;
+ tokens[0] = tokens[0];
bPool.set__tokens(tokens);
_;
}
- function test_WhenTokenIsLastOnTheTokensArray() external whenCallerIsController whenTokenCanBeUnbound {
+ function test_WhenTokenIsLastOnTheTokensArray() external whenTokenCanBeUnbound {
// it sets the reentrancy lock
bPool.expectCall__setLock(_MUTEX_TAKEN);
// it calls _pushUnderlying
- bPool.expectCall__pushUnderlying(token, deployer, boundTokenAmount);
+ bPool.expectCall__pushUnderlying(tokens[0], address(this), boundTokenAmount);
// it emits LOG_CALL event
vm.expectEmit();
- bytes memory _data = abi.encodeWithSelector(IBPool.unbind.selector, token);
- emit IBPool.LOG_CALL(IBPool.unbind.selector, deployer, _data);
- bPool.unbind(token);
+ bytes memory _data = abi.encodeWithSelector(IBPool.unbind.selector, tokens[0]);
+ emit IBPool.LOG_CALL(IBPool.unbind.selector, address(this), _data);
+ bPool.unbind(tokens[0]);
// it clears the reentrancy lock
assertEq(bPool.call__getLock(), _MUTEX_FREE);
// it removes the token record
- assertFalse(bPool.call__records(token).bound);
+ assertFalse(bPool.call__records(tokens[0]).bound);
// it pops from the array
assertEq(bPool.getNumTokens(), 0);
// it decreases the total weight
assertEq(bPool.call__totalWeight(), totalWeight - tokenWeight);
}
- function test_WhenTokenIsNOTLastOnTheTokensArray() external whenCallerIsController whenTokenCanBeUnbound {
- _setRecord(secondToken, IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
- address[] memory tokens = new address[](2);
- tokens[0] = token;
- tokens[1] = secondToken;
- bPool.set__tokens(tokens);
- bPool.unbind(token);
+ function test_WhenTokenIsNOTLastOnTheTokensArray() external whenTokenCanBeUnbound {
+ bPool.set__records(tokens[1], IBPool.Record({bound: true, index: 0, denorm: tokenWeight}));
+ bPool.set__tokens(_tokensToMemory());
+ bPool.unbind(tokens[0]);
// it removes the token record
- assertFalse(bPool.call__records(token).bound);
+ assertFalse(bPool.call__records(tokens[0]).bound);
// it removes the token from the array
assertEq(bPool.getNumTokens(), 1);
// it keeps other tokens in the array
- assertEq(bPool.call__tokens()[0], secondToken);
- assertTrue(bPool.call__records(secondToken).bound);
+ assertEq(bPool.call__tokens()[0], tokens[1]);
+ assertTrue(bPool.call__records(tokens[1]).bound);
// it updates records to point to the new indices
- assertEq(bPool.call__records(secondToken).index, 0);
+ assertEq(bPool.call__records(tokens[1]).index, 0);
}
}
diff --git a/test/unit/BPool/BPool_Unbind.tree b/test/unit/BPool/BPool_Unbind.tree
index 0f8fd8e7..12ac3f5b 100644
--- a/test/unit/BPool/BPool_Unbind.tree
+++ b/test/unit/BPool/BPool_Unbind.tree
@@ -3,22 +3,21 @@ BPool::Unbind
│ └── it should revert
├── when caller is NOT controller
│ └── it should revert
-└── when caller is controller
- ├── when token is not bound
- │ └── it should revert
- ├── when pool is finalized
- │ └── it should revert
- └── when token can be unbound
- ├── when token is last on the tokens array
- │ ├── it sets the reentrancy lock
- │ ├── it emits LOG_CALL event
- │ ├── it calls _pushUnderlying
- │ ├── it removes the token record
- │ ├── it decreases the total weight
- │ ├── it pops from the array
- │ └── it clears the reentrancy lock
- └── when token is NOT last on the tokens array
- ├── it removes the token record
- ├── it removes the token from the array
- ├── it keeps other tokens in the array
- └── it updates records to point to the correct indices
+├── when token is not bound
+│ └── it should revert
+├── when pool is finalized
+│ └── it should revert
+└── when token can be unbound
+ ├── when token is last on the tokens array
+ │ ├── it sets the reentrancy lock
+ │ ├── it emits LOG_CALL event
+ │ ├── it calls _pushUnderlying
+ │ ├── it removes the token record
+ │ ├── it decreases the total weight
+ │ ├── it pops from the array
+ │ └── it clears the reentrancy lock
+ └── when token is NOT last on the tokens array
+ ├── it removes the token record
+ ├── it removes the token from the array
+ ├── it keeps other tokens in the array
+ └── it updates records to point to the correct indices
diff --git a/test/unit/BToken.t.sol b/test/unit/BToken.t.sol
index 58c7f441..ce2f54f2 100644
--- a/test/unit/BToken.t.sol
+++ b/test/unit/BToken.t.sol
@@ -1,154 +1,123 @@
-// SPDX-License-Identifier: GPL-3
+// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
+import {IERC20} from '@openzeppelin/contracts/interfaces/IERC20.sol';
import {IERC20Errors} from '@openzeppelin/contracts/interfaces/draft-IERC6093.sol';
import {Test} from 'forge-std/Test.sol';
import {MockBToken} from 'test/smock/MockBToken.sol';
-contract BToken_Unit_Constructor is Test {
- function test_ConstructorParams() public {
- MockBToken btoken = new MockBToken();
- assertEq(btoken.name(), 'Balancer Pool Token');
- assertEq(btoken.symbol(), 'BPT');
- assertEq(btoken.decimals(), 18);
+contract BToken is Test {
+ MockBToken public bToken;
+ uint256 public initialApproval = 100e18;
+ uint256 public initialBalance = 100e18;
+ address public caller = makeAddr('caller');
+ address public spender = makeAddr('spender');
+ address public target = makeAddr('target');
+
+ function setUp() external {
+ bToken = new MockBToken();
+
+ vm.startPrank(caller);
+ // sets initial approval (cannot be mocked)
+ bToken.approve(spender, initialApproval);
}
-}
-abstract contract BToken_Unit_base is Test {
- MockBToken internal bToken;
+ function test_ConstructorWhenCalled() external {
+ MockBToken _bToken = new MockBToken();
+ // it sets token name
+ assertEq(_bToken.name(), 'Balancer Pool Token');
+ // it sets token symbol
+ assertEq(_bToken.symbol(), 'BPT');
+ }
- modifier assumeNonZeroAddresses(address addr1, address addr2) {
- vm.assume(addr1 != address(0));
- vm.assume(addr2 != address(0));
- _;
+ function test_IncreaseApprovalRevertWhen_SenderIsAddressZero() external {
+ vm.startPrank(address(0));
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidApprover.selector, address(0)));
+
+ bToken.increaseApproval(spender, 100e18);
}
- modifier assumeNonZeroAddress(address addr) {
- vm.assume(addr != address(0));
- _;
+ function test_IncreaseApprovalRevertWhen_SpenderIsAddressZero() external {
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidSpender.selector, address(0)));
+ bToken.increaseApproval(address(0), 100e18);
}
- function setUp() public virtual {
- bToken = new MockBToken();
+ function test_IncreaseApprovalWhenCalled() external {
+ // it emits Approval event
+ vm.expectEmit();
+ emit IERC20.Approval(caller, spender, 200e18);
+
+ bToken.increaseApproval(spender, 100e18);
+ // it increases spender approval
+ assertEq(bToken.allowance(caller, spender), 200e18);
}
-}
-contract BToken_Unit_IncreaseApproval is BToken_Unit_base {
- function test_increasesApprovalFromZero(
- address sender,
- address spender,
- uint256 amount
- ) public assumeNonZeroAddresses(sender, spender) {
- vm.prank(sender);
- bToken.increaseApproval(spender, amount);
- assertEq(bToken.allowance(sender, spender), amount);
+ function test_DecreaseApprovalRevertWhen_SenderIsAddressZero() external {
+ vm.startPrank(address(0));
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidApprover.selector, address(0)));
+ bToken.decreaseApproval(spender, 50e18);
}
- function test_increasesApprovalFromNonZero(
- address sender,
- address spender,
- uint128 existingAllowance,
- uint128 amount
- ) public assumeNonZeroAddresses(sender, spender) {
- vm.assume(existingAllowance > 0);
- vm.startPrank(sender);
- bToken.approve(spender, existingAllowance);
- bToken.increaseApproval(spender, amount);
- vm.stopPrank();
- assertEq(bToken.allowance(sender, spender), uint256(amount) + existingAllowance);
+ function test_DecreaseApprovalRevertWhen_SpenderIsAddressZero() external {
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidSpender.selector, address(0)));
+ bToken.decreaseApproval(address(0), 50e18);
}
-}
-contract BToken_Unit_DecreaseApproval is BToken_Unit_base {
- function test_decreaseApprovalToNonZero(
- address sender,
- address spender,
- uint256 existingAllowance,
- uint256 amount
- ) public assumeNonZeroAddresses(sender, spender) {
- existingAllowance = bound(existingAllowance, 1, type(uint256).max);
- amount = bound(amount, 0, existingAllowance - 1);
- vm.startPrank(sender);
- bToken.approve(spender, existingAllowance);
- bToken.decreaseApproval(spender, amount);
- vm.stopPrank();
- assertEq(bToken.allowance(sender, spender), existingAllowance - amount);
+ function test_DecreaseApprovalWhenDecrementIsBiggerThanCurrentApproval() external {
+ bToken.decreaseApproval(spender, 200e18);
+ // it decreases spender approval to 0
+ assertEq(bToken.allowance(caller, spender), 0);
}
- function test_decreaseApprovalToZero(
- address sender,
- address spender,
- uint256 existingAllowance,
- uint256 amount
- ) public assumeNonZeroAddresses(sender, spender) {
- amount = bound(amount, existingAllowance, type(uint256).max);
- vm.startPrank(sender);
- bToken.approve(spender, existingAllowance);
- bToken.decreaseApproval(spender, amount);
- vm.stopPrank();
- assertEq(bToken.allowance(sender, spender), 0);
+ function test_DecreaseApprovalWhenCalled() external {
+ // it emits Approval event
+ vm.expectEmit();
+ emit IERC20.Approval(caller, spender, 50e18);
+
+ bToken.decreaseApproval(spender, 50e18);
+ // it decreases spender approval
+ assertEq(bToken.allowance(caller, spender), 50e18);
}
-}
-contract BToken_Unit__push is BToken_Unit_base {
- function test_revertsOnInsufficientSelfBalance(
- address to,
- uint128 existingBalance,
- uint128 offset
- ) public assumeNonZeroAddress(to) {
- vm.assume(offset > 1);
- deal(address(bToken), address(bToken), existingBalance);
- vm.expectRevert(
- abi.encodeWithSelector(
- IERC20Errors.ERC20InsufficientBalance.selector,
- address(bToken),
- existingBalance,
- uint256(existingBalance) + offset
- )
- );
- bToken.call__push(to, uint256(existingBalance) + offset);
+ function test__pushRevertWhen_ContractDoesNotHaveEnoughBalance() external {
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, address(bToken), 0, 50e18));
+ bToken.call__push(target, 50e18);
}
- function test_sendsTokens(
- address to,
- uint128 existingBalance,
- uint256 transferAmount
- ) public assumeNonZeroAddress(to) {
- vm.assume(to != address(bToken));
- transferAmount = bound(transferAmount, 0, existingBalance);
- deal(address(bToken), address(bToken), existingBalance);
- bToken.call__push(to, transferAmount);
- assertEq(bToken.balanceOf(to), transferAmount);
- assertEq(bToken.balanceOf(address(bToken)), existingBalance - transferAmount);
+ function test__pushWhenCalled() external {
+ deal(address(bToken), address(bToken), initialBalance);
+ // it emits Transfer event
+ vm.expectEmit();
+ emit IERC20.Transfer(address(bToken), target, 50e18);
+
+ bToken.call__push(target, 50e18);
+
+ // it transfers tokens to recipient
+ assertEq(bToken.balanceOf(address(bToken)), 50e18);
+ assertEq(bToken.balanceOf(target), 50e18);
}
-}
-contract BToken_Unit__pull is BToken_Unit_base {
- function test_revertsOnInsufficientFromBalance(
- address from,
- uint128 existingBalance,
- uint128 offset
- ) public assumeNonZeroAddress(from) {
- vm.assume(offset > 1);
- deal(address(bToken), from, existingBalance);
- vm.expectRevert(
- abi.encodeWithSelector(
- IERC20Errors.ERC20InsufficientBalance.selector, from, existingBalance, uint256(existingBalance) + offset
- )
- );
- bToken.call__pull(from, uint256(existingBalance) + offset);
+ function test__pullRevertWhen_TargetDoesNotHaveEnoughBalance() external {
+ // it should revert
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, target, 0, 50e18));
+ bToken.call__pull(target, 50e18);
}
- function test_getsTokens(
- address from,
- uint128 existingBalance,
- uint256 transferAmount
- ) public assumeNonZeroAddress(from) {
- vm.assume(from != address(bToken));
- transferAmount = bound(transferAmount, 0, existingBalance);
- deal(address(bToken), address(from), existingBalance);
- bToken.call__pull(from, transferAmount);
- assertEq(bToken.balanceOf(address(bToken)), transferAmount);
- assertEq(bToken.balanceOf(from), existingBalance - transferAmount);
+ function test__pullWhenCalled() external {
+ deal(address(bToken), address(target), initialBalance);
+ // it emits Transfer event
+ vm.expectEmit();
+ emit IERC20.Transfer(target, address(bToken), 50e18);
+
+ bToken.call__pull(target, 50e18);
+
+ // it transfers tokens from sender
+ assertEq(bToken.balanceOf(target), 50e18);
+ assertEq(bToken.balanceOf(address(bToken)), 50e18);
}
}
diff --git a/test/unit/BToken.tree b/test/unit/BToken.tree
new file mode 100644
index 00000000..35f85bec
--- /dev/null
+++ b/test/unit/BToken.tree
@@ -0,0 +1,38 @@
+BToken::constructor
+└── when called
+ ├── it sets token name
+ └── it sets token symbol
+
+BToken::increaseApproval
+├── when sender is address zero
+│ └── it should revert
+├── when spender is address zero
+│ └── it should revert
+└── when called
+ ├── it emits Approval event
+ └── it increases spender approval
+
+BToken::decreaseApproval
+├── when sender is address zero
+│ └── it should revert
+├── when spender is address zero
+│ └── it should revert
+├── when decrement is bigger than current approval
+│ └── it decreases spender approval to 0
+└── when called
+ ├── it emits Approval event
+ └── it decreases spender approval
+
+BToken::_push
+├── when contract does not have enough balance
+│ └── it should revert
+└── when called
+ ├── it emits Transfer event
+ └── it transfers tokens to recipient
+
+BToken::_pull
+├── when target does not have enough balance
+│ └── it should revert
+└── when called
+ ├── it emits Transfer event
+ └── it transfers tokens from sender
diff --git a/test/utils/Pow.sol b/test/utils/Pow.sol
deleted file mode 100644
index e9142c57..00000000
--- a/test/utils/Pow.sol
+++ /dev/null
@@ -1,10 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.25;
-
-import {BNum} from 'contracts/BNum.sol';
-
-contract Pow is BNum {
- function pow(uint256 _base, uint256 _exp) public pure returns (uint256 _result) {
- _result = bpow(_base, _exp);
- }
-}
diff --git a/test/utils/Utils.sol b/test/utils/Utils.sol
deleted file mode 100644
index 19225f9d..00000000
--- a/test/utils/Utils.sol
+++ /dev/null
@@ -1,132 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.25;
-
-import {Test} from 'forge-std/Test.sol';
-import {LibString} from 'solmate/utils/LibString.sol';
-
-contract Utils is Test {
- using LibString for uint256;
-
- uint256 public constant TOKENS_AMOUNT = 3;
-
- address[TOKENS_AMOUNT] public tokens;
-
- function _getDeterministicTokenArray(uint256 _length) internal returns (address[] memory _tokenArray) {
- _tokenArray = new address[](_length);
- for (uint256 i = 0; i < _length; i++) {
- _tokenArray[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 _staticToDynamicUintArray(uint256[TOKENS_AMOUNT] memory _fixedUintArray)
- internal
- pure
- returns (uint256[] memory _memoryUintArray)
- {
- _memoryUintArray = new uint256[](_fixedUintArray.length);
- for (uint256 i = 0; i < _fixedUintArray.length; i++) {
- _memoryUintArray[i] = _fixedUintArray[i];
- }
- }
-
- /**
- * @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);
- }
-
- /**
- * @dev Write a uint256 value to an address 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 _value The value to write.
- */
- function _writeUintAtAddressMapping(
- address _target,
- uint256 _mappingSlotNumber,
- address _mappingKey,
- uint256 _value
- ) internal {
- bytes32 _slot = keccak256(abi.encode(_mappingKey, _mappingSlotNumber));
- _writeUintToStorage(_target, uint256(_slot), _value);
- }
-
- /**
- * @dev Load an array of type(uint256).max values into memory.
- * @param _length The length of the array.
- */
- function _maxArray(uint256 _length) internal pure returns (uint256[] memory _maxUintArray) {
- _maxUintArray = new uint256[](_length);
- for (uint256 i = 0; i < TOKENS_AMOUNT; i++) {
- _maxUintArray[i] = type(uint256).max;
- }
- }
-
- /**
- * @dev Load an array of 0 values into memory.
- * @param _length The length of the array.
- */
- function _zeroArray(uint256 _length) internal pure returns (uint256[] memory _zeroUintArray) {
- _zeroUintArray = new uint256[](_length);
- }
-}
diff --git a/yarn.lock b/yarn.lock
index 4c8f1e88..25aee9fe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -687,6 +687,10 @@ cosmiconfig@^9.0.0:
js-yaml "^4.1.0"
parse-json "^5.2.0"
+"cow-amm@github:cowprotocol/cow-amm.git#6566128":
+ version "0.0.0"
+ resolved "https://codeload.github.com/cowprotocol/cow-amm/tar.gz/6566128b6c73008062cf4a6d1957db602409b719"
+
cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"