Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ jobs:
run: forge build

- name: Run audit tests
run: forge test -vvv
# Exclude the vulnerability-demonstration suites. Those contracts wire
# audit checks (which call `fail()` on detection) against intentionally
# vulnerable example contracts, so they are expected to fail by design
# and are not runnable under a stock `forge test` — they're demos, not
# invariants. Excluded contracts:
# - ExampleReentrancyAudit, ExampleAccessControlAudit (Example.t.sol)
# - ExampleGovernanceAudit (GovernanceExample.t.sol)
# - TestERC4626AdvancedCheck (ERC4626AdvancedCheck.t.sol)
# See tracking issue for packaging these as forge scripts / a separate
# `forge test --match-path test/demo/*` suite.
run: forge test -vvv --no-match-contract '^(Example|TestERC4626)'

slither-analysis:
runs-on: ubuntu-latest
Expand All @@ -40,15 +50,13 @@ jobs:
- name: Install Slither
run: pip install slither-analyzer

- name: Run Slither with custom detectors
- name: Run Slither
# Excludes + severity filters live in slither.config.json.
# Custom detectors in slither/detectors/ are not yet wired up as a
# pip-installable plugin — see slither/README.md for the follow-up.
run: |
slither . \
--config slither.config.json \
--python-path slither/detectors \
--exclude-assembly \
--exclude-shadowing \
--exclude-low \
--exclude-informational \
--sarif output.sarif || true

- name: Upload Slither results
Expand Down
11 changes: 3 additions & 8 deletions slither.config.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
{
"detectors_to_exclude": "assembly,shadowing-state",
"detectors_to_exclude": "assembly,shadowing-state,naming-convention,solc-version,pragma,low-level-calls",
"filter_paths": "lib/,test/",
"detectors_to_run": [
"custom-reentrancy-detector",
"custom-access-control-detector",
"custom-oracle-manipulation-detector",
"custom-flash-loan-detector",
"custom-governance-detector"
]
"exclude_informational": true,
"exclude_low": true
}
10 changes: 5 additions & 5 deletions src/checks/ERC4626AdvancedCheck.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ abstract contract ERC4626AdvancedCheck is VaultCheck {
// -------------------------------------------------------------------------
function test_flash_deposit_withdraw_no_leak() public {
address attacker = makeAddr("flash_attacker");
IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 amount = getLargeDepositAmount();

deal(address(underlying), attacker, amount);
Expand Down Expand Up @@ -63,7 +63,7 @@ abstract contract ERC4626AdvancedCheck is VaultCheck {
// -------------------------------------------------------------------------
function test_round_trip_tolerance() public {
address user = makeAddr("roundtrip_user");
IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 amount = getLargeDepositAmount();

deal(address(underlying), user, amount);
Expand Down Expand Up @@ -95,7 +95,7 @@ abstract contract ERC4626AdvancedCheck is VaultCheck {
// -------------------------------------------------------------------------
function test_convert_roundtrip() public {
address user = makeAddr("convert_user");
IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 amount = getLargeDepositAmount();

deal(address(underlying), user, amount);
Expand Down Expand Up @@ -142,7 +142,7 @@ abstract contract ERC4626AdvancedCheck is VaultCheck {
address alice = makeAddr("multi_alice");
address bob = makeAddr("multi_bob");
address carol = makeAddr("multi_carol");
IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 amount = getLargeDepositAmount();

deal(address(underlying), alice, amount);
Expand Down Expand Up @@ -199,7 +199,7 @@ abstract contract ERC4626AdvancedCheck is VaultCheck {
// -------------------------------------------------------------------------
function test_deposit_into_empty_vault() public {
address user = makeAddr("empty_vault_user");
IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 amount = getSmallDepositAmount();

deal(address(underlying), user, amount);
Expand Down
2 changes: 1 addition & 1 deletion src/checks/ReentrancyCheck.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ abstract contract ReentrancyCheck is ChecklistBase {
uint256 attackerBal = address(attacker).balance;
// If attacker got more than deposited, reentrancy succeeded
if (attackerBal > depositAmount) {
emit log("VULNERABILITY: Reentrancy detected — attacker extracted more than deposited");
emit log(unicode"VULNERABILITY: Reentrancy detected — attacker extracted more than deposited");
fail();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/checks/UpgradeCheck.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ abstract contract UpgradeCheck is ChecklistBase {
bytes32 implSlotValue = vm.load(proxy, IMPL_SLOT);
address storedImpl = address(uint160(uint256(implSlotValue)));

assertTrue(storedImpl != address(0), "VULNERABILITY: Implementation slot is zero — proxy not initialized");
assertTrue(storedImpl != address(0), unicode"VULNERABILITY: Implementation slot is zero — proxy not initialized");
assertEq(storedImpl, getImplementationAddress(), "Implementation slot doesn't match expected");
}

Expand Down
14 changes: 9 additions & 5 deletions src/checks/VaultCheck.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ interface IERC4626 {
function maxDeposit(address receiver) external view returns (uint256);
}

interface IERC20 {
// Minimal IERC20 used by VaultCheck & ERC4626AdvancedCheck. Named `IERC20Minimal`
// so it does not collide with OpenZeppelin's `IERC20` when a test imports both
// this file and an OZ-based vault (e.g. `VulnerableERC4626.sol`) into the same
// compilation unit.
interface IERC20Minimal {
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
Expand Down Expand Up @@ -58,7 +62,7 @@ abstract contract VaultCheck is ChecklistBase {
address attacker = makeAddr("inflation_attacker");
address victim = makeAddr("inflation_victim");

IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint8 dec = underlying.decimals();
uint256 smallDep = getSmallDepositAmount();
uint256 donation = 10 ** (dec + 3); // 1000 tokens worth of underlying
Expand Down Expand Up @@ -106,7 +110,7 @@ abstract contract VaultCheck is ChecklistBase {
address alice = makeAddr("alice");
address attacker = makeAddr("donate_attacker");

IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 aliceDeposit = getLargeDepositAmount();
uint256 attackerDeposit = getLargeDepositAmount();
uint256 donationAmount = getLargeDepositAmount();
Expand Down Expand Up @@ -178,7 +182,7 @@ abstract contract VaultCheck is ChecklistBase {
// -------------------------------------------------------------------------
function test_preview_deposit_accuracy() public {
address user = makeAddr("preview_user");
IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 amount = getLargeDepositAmount();

deal(address(underlying), user, amount);
Expand All @@ -202,7 +206,7 @@ abstract contract VaultCheck is ChecklistBase {

function test_preview_redeem_accuracy() public {
address user = makeAddr("redeem_user");
IERC20 underlying = IERC20(vault.asset());
IERC20Minimal underlying = IERC20Minimal(vault.asset());
uint256 amount = getLargeDepositAmount();

deal(address(underlying), user, amount);
Expand Down
Loading