Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Medusa swap tests #1167

Merged
merged 42 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
24902af
init stateful fuzz harness
bohendo Sep 26, 2024
374ff25
Merge branch 'main' into medusa-fuzz-tests
joaobrunoah Oct 2, 2024
91003e0
Medusa Base Test
joaobrunoah Oct 3, 2024
c892b54
Implement Stateful tests with Medusa
joaobrunoah Oct 8, 2024
4cfd5bb
Merge branch 'main' into medusa-add-remove-test
joaobrunoah Oct 8, 2024
44e71fd
Lint
joaobrunoah Oct 9, 2024
b21e28b
Fix Medusa tests
joaobrunoah Oct 9, 2024
680bd02
Merge branch 'main' into medusa-add-remove-test
joaobrunoah Oct 9, 2024
cba2ae1
Merge branch 'main' into medusa-add-remove-test
joaobrunoah Oct 9, 2024
86d3f6f
Remove unused test contract
joaobrunoah Oct 9, 2024
de2c8d7
Remove stale comment
joaobrunoah Oct 9, 2024
7a8b83c
Improve documentation
joaobrunoah Oct 9, 2024
6502799
Remove Echidna configuration
joaobrunoah Oct 9, 2024
37af832
Replace Tabs by spaces
joaobrunoah Oct 14, 2024
dc7b881
Fix weights boundaries
joaobrunoah Oct 14, 2024
d20d6a9
Fix comments
joaobrunoah Oct 14, 2024
453b82a
Fix WETH token in Medusa
joaobrunoah Oct 14, 2024
4a90c76
Remove getBalancesLength function.
joaobrunoah Oct 14, 2024
97cf79d
Comments
joaobrunoah Oct 14, 2024
78c819f
Remove getBptRate implementation
joaobrunoah Oct 14, 2024
c46ac2e
Fix initial pool balances
joaobrunoah Oct 14, 2024
965170e
Comment
joaobrunoah Oct 14, 2024
bc7f83e
Set swap fee percentage to 0
joaobrunoah Oct 14, 2024
138080f
Merge branch 'main' into medusa-add-remove-test
joaobrunoah Oct 15, 2024
fb03881
Fix comment
joaobrunoah Oct 22, 2024
64eeb64
Merge branch 'main' into medusa-add-remove-test
joaobrunoah Oct 24, 2024
7bc5bd5
Fix constant
joaobrunoah Nov 27, 2024
d527849
Merge branch 'main' into medusa-add-remove-test
joaobrunoah Nov 27, 2024
d3c03d0
add main swap medusa tests
elshan-eth Dec 2, 2024
7a83ab8
fix swap medusa tests
elshan-eth Dec 3, 2024
9bb49e5
fix bound
elshan-eth Dec 3, 2024
bd5eb70
fix swap medusa tests
elshan-eth Dec 4, 2024
58ba4c8
fix medusa config
elshan-eth Dec 4, 2024
a291997
Merge branch 'main' into medusa-add-remove-test
joaobrunoah Dec 4, 2024
9a53d80
Fix medusa
joaobrunoah Dec 4, 2024
50603e2
add tests for stable and weighted pools
elshan-eth Dec 5, 2024
ae9b5c5
Merge branch 'medusa-add-remove-test' into medusa-swap-tests
elshan-eth Dec 5, 2024
0fb160d
Merge branch 'main' into medusa-swap-tests
joaobrunoah Dec 9, 2024
52e42f1
small fixes
elshan-eth Dec 17, 2024
7006a02
fix swap medusa test
elshan-eth Jan 6, 2025
8c83a6e
docs: fix typo and note Mac instructions for medusa
EndymionJkb Jan 7, 2025
91a0774
small fixes
elshan-eth Jan 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ smarter than Forge. That's because Medusa has an optimizer to create a path of t
random.

To run Medusa tests, we first need to install Echidna. To do so, install `echidna` using the
[release page](https://github.com/crytic/echidna/releases) or `brew install echidna` in Mac. Notice that the Mac
installation will also install Crytic-compiller, so this step can be skipped when installing Medusa.
[release page](https://github.com/crytic/echidna/releases) or `brew install echidna` on a Mac. Notice that the Mac
installation will also install Crytic-compiler, so this step can be skipped when installing Medusa.

Then, install Medusa using this [installation guide](https://github.com/crytic/medusa/blob/master/docs/src/getting_started/installation.md#building-from-source) (or just copy the adequate precompiled binary to a directory within your `PATH`).
Then, install Medusa using this [installation guide](https://github.com/crytic/medusa/blob/master/docs/src/getting_started/installation.md#building-from-source), `brew install medusa` on a Mac. Alternatively, just copy the adequate precompiled binary to a directory within your `PATH`.

Finally, run `yarn test:medusa`. This command is available inside the packages `vault`, `pool-weighted` and
`pool-stable`.
Expand Down
2 changes: 1 addition & 1 deletion pkg/pool-stable/medusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"callSequenceLength": 10,
"corpusDirectory": "medusa-corpus",
"coverageEnabled": true,
"targetContracts": ["AddAndRemoveLiquidityStableMedusaTest"],
"targetContracts": ["AddAndRemoveLiquidityStableMedusaTest", "SwapMedusaTest"],
elshan-eth marked this conversation as resolved.
Show resolved Hide resolved
"predeployedContracts": {},
"targetContractsBalances": [],
"constructorArgs": {},
Expand Down
50 changes: 50 additions & 0 deletions pkg/pool-stable/test/foundry/fuzz/SwapStable.medusa.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

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

import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";

import { SwapMedusaTest } from "@balancer-labs/v3-vault/test/foundry/fuzz/Swap.medusa.sol";

import { StablePoolFactory } from "../../../contracts/StablePoolFactory.sol";
import { StablePool } from "../../../contracts/StablePool.sol";

contract SwapStableMedusaTest is SwapMedusaTest {
uint256 private constant DEFAULT_SWAP_FEE = 1e16;
uint256 internal constant _AMPLIFICATION_PARAMETER = 1000;

constructor() SwapMedusaTest() {}

function createPool(
IERC20[] memory tokens,
uint256[] memory initialBalances
) internal override returns (address newPool) {
StablePoolFactory factory = new StablePoolFactory(IVault(address(vault)), 365 days, "Factory v1", "Pool v1");
PoolRoleAccounts memory roleAccounts;

StablePool newPool = StablePool(
factory.create(
"Stable Pool",
"STABLE",
vault.buildTokenConfig(tokens),
_AMPLIFICATION_PARAMETER,
roleAccounts,
DEFAULT_SWAP_FEE, // Swap fee is set to 0 in the test constructor
address(0), // No hooks
false, // Do not enable donations
false, // Do not disable unbalanced add/remove liquidity
// NOTE: sends a unique salt.
bytes32(poolCreationNonce++)
)
);

// Initialize liquidity of stable pool.
medusa.prank(lp);
router.initialize(address(newPool), tokens, initialBalances, 0, false, bytes(""));

return address(newPool);
}
}
16 changes: 4 additions & 12 deletions pkg/pool-weighted/medusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@
"callSequenceLength": 10,
"corpusDirectory": "medusa-corpus",
"coverageEnabled": true,
"targetContracts": ["AddAndRemoveLiquidityWeightedMedusaTest"],
"targetContracts": ["AddAndRemoveLiquidityWeightedMedusaTest", "SwapMedusaTest"],
"predeployedContracts": {},
"targetContractsBalances": [],
"constructorArgs": {},
"deployerAddress": "0x30000",
"senderAddresses": [
"0x10000",
"0x20000",
"0x30000"
],
"senderAddresses": ["0x10000", "0x20000", "0x30000"],
"blockNumberDelayMax": 60480,
"blockTimestampDelayMax": 604800,
"blockGasLimit": 1250000000,
Expand Down Expand Up @@ -46,15 +42,11 @@
},
"propertyTesting": {
"enabled": true,
"testPrefixes": [
"property_"
]
"testPrefixes": ["property_"]
},
"optimizationTesting": {
"enabled": true,
"testPrefixes": [
"optimize_"
]
"testPrefixes": ["optimize_"]
},
"targetFunctionSignatures": [],
"excludeFunctionSignatures": []
Expand Down
63 changes: 63 additions & 0 deletions pkg/pool-weighted/test/foundry/fuzz/SwapWeighted.medusa.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

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

import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";

import { SwapMedusaTest } from "@balancer-labs/v3-vault/test/foundry/fuzz/Swap.medusa.sol";

import { WeightedPoolFactory } from "../../../contracts/WeightedPoolFactory.sol";
import { WeightedPool } from "../../../contracts/WeightedPool.sol";

contract SwapWeightedMedusaTest is SwapMedusaTest {
uint256 private constant DEFAULT_SWAP_FEE = 1e16;

uint256 private constant _WEIGHT1 = 33e16;
uint256 private constant _WEIGHT2 = 33e16;

constructor() SwapMedusaTest() {}

function createPool(
IERC20[] memory tokens,
uint256[] memory initialBalances
) internal override returns (address newPool) {
uint256[] memory weights = new uint256[](3);
weights[0] = _WEIGHT1;
weights[1] = _WEIGHT2;
// Sum of weights should equal 100%.
weights[2] = 100e16 - (weights[0] + weights[1]);

WeightedPoolFactory factory = new WeightedPoolFactory(
IVault(address(vault)),
365 days,
"Factory v1",
"Pool v1"
);
PoolRoleAccounts memory roleAccounts;

WeightedPool newPool = WeightedPool(
factory.create(
"Weighted Pool",
"WP",
vault.buildTokenConfig(tokens),
weights,
roleAccounts,
DEFAULT_SWAP_FEE, // Swap fee is set to 0 in the test constructor
address(0), // No hooks
false, // Do not enable donations
false, // Do not disable unbalanced add/remove liquidity
// NOTE: sends a unique salt.
bytes32(poolCreationNonce++)
)
);

// Initialize liquidity of weighted pool.
medusa.prank(lp);
router.initialize(address(newPool), tokens, initialBalances, 0, false, bytes(""));

return address(newPool);
}
}
16 changes: 4 additions & 12 deletions pkg/vault/medusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@
"callSequenceLength": 10,
"corpusDirectory": "medusa-corpus",
"coverageEnabled": true,
"targetContracts": ["AddAndRemoveLiquidityMedusaTest"],
"targetContracts": ["AddAndRemoveLiquidityMedusaTest", "SwapMedusaTest"],
"predeployedContracts": {},
"targetContractsBalances": [],
"constructorArgs": {},
"deployerAddress": "0x30000",
"senderAddresses": [
"0x10000",
"0x20000",
"0x30000"
],
"senderAddresses": ["0x10000", "0x20000", "0x30000"],
"blockNumberDelayMax": 60480,
"blockTimestampDelayMax": 604800,
"blockGasLimit": 1250000000,
Expand Down Expand Up @@ -46,15 +42,11 @@
},
"propertyTesting": {
"enabled": true,
"testPrefixes": [
"property_"
]
"testPrefixes": ["property_"]
},
"optimizationTesting": {
"enabled": true,
"testPrefixes": [
"optimize_"
]
"testPrefixes": ["optimize_"]
},
"targetFunctionSignatures": [],
"excludeFunctionSignatures": []
Expand Down
120 changes: 120 additions & 0 deletions pkg/vault/test/foundry/fuzz/Swap.medusa.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

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

import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
import { Rounding } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";

import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";

import { BasePoolMath } from "../../../contracts/BasePoolMath.sol";
import { BalancerPoolToken } from "../../../contracts/BalancerPoolToken.sol";

import "../utils/BaseMedusaTest.sol";

contract SwapMedusaTest is BaseMedusaTest {
using FixedPoint for uint256;

uint256 internal constant MIN_SWAP_AMOUNT = 1e6;
uint256 constant MAX_IN_RATIO = 0.3e18;

int256 internal initInvariant;

constructor() BaseMedusaTest() {
initInvariant = computeInvariant();
vault.manuallySetSwapFee(address(pool), 0);

emit Debug("prevInvariant", initInvariant);
}

function optimize_currentInvariant() public view returns (int256) {
return -int256(computeInvariant());
}

function property_currentInvariant() public returns (bool) {
int256 currentInvariant = computeInvariant();
return currentInvariant >= initInvariant;
}

function computeSwapExactIn(uint256 tokenIndexIn, uint256 tokenIndexOut, uint256 exactAmountIn) public {
elshan-eth marked this conversation as resolved.
Show resolved Hide resolved
(tokenIndexIn, tokenIndexOut) = boundTokenIndexes(tokenIndexIn, tokenIndexOut);

exactAmountIn = boundSwapAmount(exactAmountIn, tokenIndexIn);

(IERC20[] memory tokens, , , ) = vault.getPoolTokenInfo(address(pool));

emit Debug("token index in", tokenIndexIn);
emit Debug("token index out", tokenIndexOut);
emit Debug("exact amount in", exactAmountIn);

medusa.prank(alice);
router.swapSingleTokenExactIn(
address(pool),
tokens[tokenIndexIn],
tokens[tokenIndexOut],
exactAmountIn,
0,
MAX_UINT256,
false,
bytes("")
);

emit Debug("currentInvariant", computeInvariant());
}

function computeSwapExactOut(uint256 tokenIndexIn, uint256 tokenIndexOut, uint256 exactAmountOut) public {
(tokenIndexIn, tokenIndexOut) = boundTokenIndexes(tokenIndexIn, tokenIndexOut);

exactAmountOut = boundSwapAmount(exactAmountOut, tokenIndexOut);

(IERC20[] memory tokens, , , ) = vault.getPoolTokenInfo(address(pool));

emit Debug("token index in", tokenIndexIn);
emit Debug("token index out", tokenIndexOut);
emit Debug("exact amount out", exactAmountOut);

medusa.prank(alice);
router.swapSingleTokenExactOut(
address(pool),
tokens[tokenIndexIn],
tokens[tokenIndexOut],
exactAmountOut,
MAX_UINT256,
MAX_UINT256,
false,
bytes("")
);

emit Debug("currentInvariant", computeInvariant());
}

function computeInvariant() internal view returns (int256) {
(, , , uint256[] memory lastBalancesLiveScaled18) = vault.getPoolTokenInfo(address(pool));
return int256(pool.computeInvariant(lastBalancesLiveScaled18, Rounding.ROUND_UP));
}

function boundTokenIndexes(
uint256 tokenIndexInRaw,
uint256 tokenIndexOutRaw
) internal view returns (uint256 tokenIndexIn, uint256 tokenIndexOut) {
uint256 len = vault.getPoolTokens(address(pool)).length;

tokenIndexIn = bound(tokenIndexInRaw, 0, len - 1);
tokenIndexOut = bound(tokenIndexOutRaw, 0, len - 1);

if (tokenIndexIn == tokenIndexOut) {
tokenIndexOut = (tokenIndexOut + 1) % len;
}
}

function boundSwapAmount(
uint256 tokenAmountIn,
uint256 tokenIndex
) internal view returns (uint256 boundedAmountIn) {
(, , uint256[] memory balancesRaw, ) = vault.getPoolTokenInfo(address(pool));
boundedAmountIn = bound(tokenAmountIn, MIN_SWAP_AMOUNT, balancesRaw[tokenIndex].mulDown(MAX_IN_RATIO));
}
}
7 changes: 7 additions & 0 deletions pkg/vault/test/foundry/utils/BaseMedusaTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ contract BaseMedusaTest is Test {
// In certain places, console.log will not print to stdout the intended message, so we use this event to print
// messages and values.
event Debug(string, uint256);
event Debug(string, int256);

IPermit2 internal permit2;

Expand All @@ -59,6 +60,12 @@ contract BaseMedusaTest is Test {
IBasePool internal pool;
uint256 internal poolCreationNonce;

uint256 internal constant MAX_UINT128 = type(uint128).max;
uint256 internal constant MAX_UINT256 = type(uint256).max;

// PackedTokenBalance.sol defines a max of 128 bits to store the balance of the pool.
uint256 internal constant MAX_BALANCE = MAX_UINT128;

uint256 internal constant DEFAULT_USER_BALANCE = 1e18 * 1e18;
uint256 internal constant DEFAULT_INITIAL_POOL_BALANCE = 1e6 * 1e18;

Expand Down
Loading