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

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 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
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ slither/
# Misc
.vscode/
.idea/

# Fuzz tests
crytic-export/
medusa-corpus/
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ $ yarn test:hardhat

Hardhat tests will also update snapshots for bytecode size and gas usage if applicable for a given package.

### Medusa tests

Medusa is a tool, developed by Trail of Bits, that allow us to execute stateful fuzz tests in contracts, in a way
smarter than Forge. That's because Medusa has an optimizer to create a path of transactions which is not completely
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.

Then, install Medusa using this [installation guide](https://github.com/crytic/medusa/blob/master/docs/src/getting_started/installation.md#building-from-source).

Finally, run `yarn fuzz:medusa`. This command is available inside the packages `vault`, `pool-weighted` and
`pool-stable`.

## Static analysis

To run [Slither](https://github.com/crytic/slither) static analyzer, Python 3.8+ is a requirement.
Expand Down
89 changes: 89 additions & 0 deletions pkg/interfaces/contracts/test/IStdMedusaCheats.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

interface IStdMedusaCheats {
// Set block.timestamp
function warp(uint256) external;

// Set block.number
function roll(uint256) external;

// Set block.basefee
function fee(uint256) external;

// Set block.difficulty and block.prevrandao
function difficulty(uint256) external;

// Set block.chainid
function chainId(uint256) external;

// Sets the block.coinbase
function coinbase(address) external;

// Loads a storage slot from an address
function load(address account, bytes32 slot) external returns (bytes32);

// Stores a value to an address' storage slot
function store(address account, bytes32 slot, bytes32 value) external;

// Sets the *next* call's msg.sender to be the input address
function prank(address) external;

// Set msg.sender to the input address until the current call exits
function prankHere(address) external;

// Sets an address' balance
function deal(address who, uint256 newBalance) external;

// Sets an address' code
function etch(address who, bytes calldata code) external;

// Signs data
function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);

// Computes address for a given private key
function addr(uint256 privateKey) external returns (address);

// Gets the nonce of an account
function getNonce(address account) external returns (uint64);

// Sets the nonce of an account
// The new nonce must be higher than the current nonce of the account
function setNonce(address account, uint64 nonce) external;

// Performs a foreign function call via terminal
function ffi(string[] calldata) external returns (bytes memory);

// Take a snapshot of the current state of the EVM
function snapshot() external returns (uint256);

// Revert state back to a snapshot
function revertTo(uint256) external returns (bool);

// Convert Solidity types to strings
function toString(address) external returns (string memory);

function toString(bytes calldata) external returns (string memory);

function toString(bytes32) external returns (string memory);

function toString(bool) external returns (string memory);

function toString(uint256) external returns (string memory);

function toString(int256) external returns (string memory);

// Convert strings into Solidity types
function parseBytes(string memory) external returns (bytes memory);

function parseBytes32(string memory) external returns (bytes32);

function parseAddress(string memory) external returns (address);

function parseUint(string memory) external returns (uint256);

function parseInt(string memory) external returns (int256);

function parseBool(string memory) external returns (bool);
}
84 changes: 84 additions & 0 deletions pkg/pool-stable/medusa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"fuzzing": {
"workers": 10,
"workerResetLimit": 50,
"timeout": 0,
"testLimit": 100000,
"shrinkLimit": 5000,
"callSequenceLength": 10,
"corpusDirectory": "medusa-corpus",
"coverageEnabled": true,
"targetContracts": ["AddAndRemoveLiquidityStableMedusaTest", "SwapMedusaTest"],
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Older versions of Medusa conflicted here (I was using 0.1.16 and it did not work), but upgrading to 0.1.18 made it work. Much better to have actions in different files.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did you mean we need to have two medusa.json settings for each medusa test file?

"predeployedContracts": {},
"targetContractsBalances": [],
"constructorArgs": {},
"deployerAddress": "0x30000",
"senderAddresses": [
"0x10000",
"0x20000",
"0x30000"
],
"blockNumberDelayMax": 60480,
"blockTimestampDelayMax": 604800,
"blockGasLimit": 1250000000,
"transactionGasLimit": 125000000,
"testing": {
"stopOnFailedTest": false,
"stopOnFailedContractMatching": false,
"stopOnNoTests": true,
"testAllContracts": false,
"traceAll": false,
"assertionTesting": {
"enabled": true,
"testViewMethods": false,
"panicCodeConfig": {
"failOnCompilerInsertedPanic": false,
"failOnAssertion": true,
"failOnArithmeticUnderflow": false,
"failOnDivideByZero": false,
"failOnEnumTypeConversionOutOfBounds": false,
"failOnIncorrectStorageAccess": false,
"failOnPopEmptyArray": false,
"failOnOutOfBoundsArrayAccess": false,
"failOnAllocateTooMuchMemory": false,
"failOnCallUninitializedVariable": false
}
},
"propertyTesting": {
"enabled": true,
"testPrefixes": [
"property_"
]
},
"optimizationTesting": {
"enabled": true,
"testPrefixes": [
"optimize_"
]
},
"targetFunctionSignatures": [],
"excludeFunctionSignatures": []
},
"chainConfig": {
"codeSizeCheckDisabled": true,
"cheatCodes": {
"cheatCodesEnabled": true,
"enableFFI": true
}
}
},
"compilation": {
"platform": "crytic-compile",
"platformConfig": {
"target": ".",
"solcVersion": "",
"exportDirectory": "",
"args": ["--foundry-out-directory=forge-artifacts", "--foundry-compile-all"]
}
},
"logging": {
"level": "info",
"logDirectory": "",
"noColor": false
}
}
1 change: 1 addition & 0 deletions pkg/pool-stable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"test:hardhat": "hardhat test",
"test:forge": "yarn build && REUSING_HARDHAT_ARTIFACTS=true forge test --ffi -vvv",
"test:stress": "FOUNDRY_PROFILE=intense forge test --ffi -vvv",
"fuzz:medusa": "medusa fuzz --config medusa.json",
"coverage": "./coverage.sh forge",
"coverage:hardhat": "./coverage.sh hardhat",
"coverage:all": "./coverage.sh all",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import "forge-std/Test.sol";

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 { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";

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

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

contract AddAndRemoveLiquidityStableMedusaTest is AddAndRemoveLiquidityMedusaTest {
uint256 private constant DEFAULT_SWAP_FEE = 1e16;
uint256 internal constant _AMPLIFICATION_PARAMETER = 1000;

constructor() AddAndRemoveLiquidityMedusaTest() {
maxRateTolerance = 0;
}

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);
}
}
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);
}
}
76 changes: 76 additions & 0 deletions pkg/pool-weighted/medusa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"fuzzing": {
"workers": 10,
"workerResetLimit": 50,
"timeout": 0,
"testLimit": 100000,
"shrinkLimit": 5000,
"callSequenceLength": 10,
"corpusDirectory": "medusa-corpus",
"coverageEnabled": true,
"targetContracts": ["AddAndRemoveLiquidityWeightedMedusaTest", "SwapMedusaTest"],
"predeployedContracts": {},
"targetContractsBalances": [],
"constructorArgs": {},
"deployerAddress": "0x30000",
"senderAddresses": ["0x10000", "0x20000", "0x30000"],
"blockNumberDelayMax": 60480,
"blockTimestampDelayMax": 604800,
"blockGasLimit": 1250000000,
"transactionGasLimit": 125000000,
"testing": {
"stopOnFailedTest": false,
"stopOnFailedContractMatching": false,
"stopOnNoTests": true,
"testAllContracts": false,
"traceAll": false,
"assertionTesting": {
"enabled": true,
"testViewMethods": false,
"panicCodeConfig": {
"failOnCompilerInsertedPanic": false,
"failOnAssertion": true,
"failOnArithmeticUnderflow": false,
"failOnDivideByZero": false,
"failOnEnumTypeConversionOutOfBounds": false,
"failOnIncorrectStorageAccess": false,
"failOnPopEmptyArray": false,
"failOnOutOfBoundsArrayAccess": false,
"failOnAllocateTooMuchMemory": false,
"failOnCallUninitializedVariable": false
}
},
"propertyTesting": {
"enabled": true,
"testPrefixes": ["property_"]
},
"optimizationTesting": {
"enabled": true,
"testPrefixes": ["optimize_"]
},
"targetFunctionSignatures": [],
"excludeFunctionSignatures": []
},
"chainConfig": {
"codeSizeCheckDisabled": true,
"cheatCodes": {
"cheatCodesEnabled": true,
"enableFFI": true
}
}
},
"compilation": {
"platform": "crytic-compile",
"platformConfig": {
"target": ".",
"solcVersion": "",
"exportDirectory": "",
"args": ["--foundry-out-directory=forge-artifacts", "--foundry-compile-all"]
}
},
"logging": {
"level": "info",
"logDirectory": "",
"noColor": false
}
}
Loading
Loading