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
67 changes: 62 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ jobs:
run: forge soldeer install && yarn install

- name: Compile contracts with Forge
run: forge build --sizes
run: forge build --sizes --skip Fuzzer

#- name: Compile contracts with Hardhat
# run: npx hardhat compile

####################################################
### TESTS
### CONCRETE TESTS
####################################################
foundry-unit-tests:
name: Foundry Unit-Tests
Expand Down Expand Up @@ -134,8 +134,11 @@ jobs:
- name: Run non-invariant tests
run: forge test --summary --fail-fast --show-progress --mp 'test/smoke/**'

foundry-tests-invariants:
name: Foundry Invariants Tests
####################################################
### FUZZ TESTS
####################################################
foundry-tests-invariants-OethARM:
name: Foundry Invariants Tests - OethARM
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -156,6 +159,22 @@ jobs:
FOUNDRY_INVARIANT_FAIL_ON_REVERT=false \
FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_OethARM \
forge test --summary --fail-fast --show-progress

foundry-tests-invariants-OriginARM:
name: Foundry Invariants Tests - OriginARM
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: "v1.4.4"

- name: Install Dependencies
run: forge soldeer install

- name: Run invariant tests (OriginARM)
run: |
Expand All @@ -164,9 +183,47 @@ jobs:
FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_OriginARM \
forge test --summary --fail-fast --show-progress

foundry-tests-invariants-EthenaARM:
name: Foundry Invariants Tests - EthenaARM
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: "v1.4.4"

- name: Install Dependencies
run: forge soldeer install

- name: Run invariant tests (EthenaARM)
run: |
FOUNDRY_PROFILE=ci \
FOUNDRY_INVARIANT_FAIL_ON_REVERT=true \
FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_EthenaARM \
forge test --summary --fail-fast --show-progress
forge test --summary --fail-fast --show-progress

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.21'

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Install Crytic Compile
run: pip install crytic-compile

- name: Install Medusa from Source
run: go install github.com/crytic/medusa@latest

- name: Run Medusa
working-directory: test/invariants/EthenaARM/
run: medusa fuzz --timeout 1800 --seq-len 500 --fail-fast


6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ soldeer.lock

# Coverage
lcov.info*
crytic-export

# Defender Actions
dist
Expand All @@ -37,4 +38,7 @@ cache_hardhat
build/deployments-fork*.json

# Reports. eg stats.html
*.html
*.html

# Other
.DS_Store
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ install:
clean:
@rm -rf broadcast cache out

clean-all:
@rm -rf broadcast cache out dependencies node_modules soldeer.lock yarn.lock lcov.info lcov.info.pruned artifacts cache_hardhat
# Remove every "crytic-export" directory anywhere in the project
clean-crytic:
@find . -type d -name crytic-export -exec rm -rf '{}' +

clean-all:
@rm -rf broadcast cache out dependencies node_modules soldeer.lock yarn.lock .lcov.info lcov.info pruned artifacts cache hardhat-node_modules
@$(MAKE) clean-crytic


gas:
@forge test --gas-report
Expand Down
98 changes: 49 additions & 49 deletions test/invariants/EthenaARM/Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,79 +27,79 @@ abstract contract Base_Test_ {
/// --- CONTRACTS
//////////////////////////////////////////////////////
// --- Main contracts ---
Proxy public armProxy;
Proxy public morphoMarketProxy;
EthenaARM public arm;
MockMorpho public morpho;
MorphoMarket public market;
EthenaUnstaker[] public unstakers;
uint256[] public unstakerIndices;
Proxy internal armProxy;
Proxy internal morphoMarketProxy;
EthenaARM internal arm;
MockMorpho internal morpho;
MorphoMarket internal market;
EthenaUnstaker[] internal unstakers;
uint256[] internal unstakerIndices;

// --- Tokens ---
IERC20 public usde;
IStakedUSDe public susde;
IERC20 internal usde;
IStakedUSDe internal susde;

// --- Utils ---
Vm public vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
Vm internal vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

//////////////////////////////////////////////////////
/// --- USERS
//////////////////////////////////////////////////////
// --- Users with roles ---
address public deployer;
address public governor;
address public operator;
address public treasury;
address internal deployer;
address internal governor;
address internal operator;
address internal treasury;

// --- Regular users ---
address public alice;
address public bobby;
address public carol;
address public david;
address public elise;
address public frank;
address public grace;
address public harry;
address public dead;
address internal alice;
address internal bobby;
address internal carol;
address internal david;
address internal elise;
address internal frank;
address internal grace;
address internal harry;
address internal dead;

// --- Group of users ---
address[] public makers;
address[] public traders;
mapping(address => uint256[]) public pendingRequests;
address[] internal makers;
address[] internal traders;
mapping(address => uint256[]) internal pendingRequests;

//////////////////////////////////////////////////////
/// --- DEFAULT VALUES
//////////////////////////////////////////////////////
uint256 public constant MAKERS_COUNT = 3;
uint256 public constant TRADERS_COUNT = 3;
uint256 public constant UNSTAKERS_COUNT = 42;
uint256 public constant DEFAULT_CLAIM_DELAY = 10 minutes;
uint256 public constant DEFAULT_MIN_TOTAL_SUPPLY = 1e12;
uint256 public constant DEFAULT_ALLOCATE_THRESHOLD = 1e18;
uint256 public constant DEFAULT_MIN_SHARES_TO_REDEEM = 1e7;
uint256 internal constant MAKERS_COUNT = 3;
uint256 internal constant TRADERS_COUNT = 3;
uint256 internal constant UNSTAKERS_COUNT = 42;
uint256 internal constant DEFAULT_CLAIM_DELAY = 10 minutes;
uint256 internal constant DEFAULT_MIN_TOTAL_SUPPLY = 1e12;
uint256 internal constant DEFAULT_ALLOCATE_THRESHOLD = 1e18;
uint256 internal constant DEFAULT_MIN_SHARES_TO_REDEEM = 1e7;

/// @notice Indicates if labels have been set in the Vm.
function isLabelAvailable() external view virtual returns (bool);
function isAssumeAvailable() external view virtual returns (bool);
function isConsoleAvailable() external view virtual returns (bool);
bool public isLabelAvailable;
bool public isAssumeAvailable;
bool public isConsoleAvailable;

//////////////////////////////////////////////////////
/// --- GHOST VALUES
//////////////////////////////////////////////////////
// --- USDe values ---
uint256 public sumUSDeSwapIn;
uint256 public sumUSDeSwapOut;
uint256 public sumUSDeUserDeposit;
uint256 public sumUSDeUserRedeem;
uint256 public sumUSDeUserRequest;
uint256 public sumUSDeBaseRedeem;
uint256 public sumUSDeFeesCollected;
uint256 public sumUSDeMarketDeposit;
uint256 public sumUSDeMarketWithdraw;
mapping(address => uint256) public mintedUSDe;
uint256 internal sumUSDeSwapIn;
uint256 internal sumUSDeSwapOut;
uint256 internal sumUSDeUserDeposit;
uint256 internal sumUSDeUserRedeem;
uint256 internal sumUSDeUserRequest;
uint256 internal sumUSDeBaseRedeem;
uint256 internal sumUSDeFeesCollected;
uint256 internal sumUSDeMarketDeposit;
uint256 internal sumUSDeMarketWithdraw;
mapping(address => uint256) internal mintedUSDe;
// --- sUSDe values ---
uint256 public sumSUSDeSwapIn;
uint256 public sumSUSDeSwapOut;
uint256 public sumSUSDeBaseRedeem;
uint256 internal sumSUSDeSwapIn;
uint256 internal sumSUSDeSwapOut;
uint256 internal sumSUSDeBaseRedeem;
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ import {StdAssertions} from "forge-std/StdAssertions.sol";
/// - Each invariant function represents a critical system property to maintain
/// - Fuzzer will call targeted handlers randomly and check invariants after each call
contract FuzzerFoundry_EthenaARM is Properties, StdInvariant, StdAssertions {
bool public constant override isLabelAvailable = true;
bool public constant override isAssumeAvailable = true;
bool public constant override isConsoleAvailable = true;

//////////////////////////////////////////////////////
/// --- SETUP
//////////////////////////////////////////////////////
function setUp() public override {
super.setUp();
constructor() {
// --- Fuzzer configuration ---
isLabelAvailable = true;
isAssumeAvailable = true;
isConsoleAvailable = true;
}

function setUp() public {
// --- Common setup ---
_setup();

// --- Setup Fuzzer target ---
// Setup target
Expand Down Expand Up @@ -87,7 +91,7 @@ contract FuzzerFoundry_EthenaARM is Properties, StdInvariant, StdAssertions {
}

function afterInvariant() public {
targetAfterAll();
assertTrue(propertyAfterAll(), "Property After All failed");
_targetAfterAll();
assertTrue(_propertyAfterAll(), "Property After All failed");
}
}
22 changes: 22 additions & 0 deletions test/invariants/EthenaARM/FuzzerMedusa_EthenaARM.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

// Test imports
import {Properties} from "./Properties.sol";

/// @title FuzzerFoundry
/// @notice Concrete fuzzing contract implementing Foundry's invariant testing framework.
/// @dev This contract configures and executes property-based testing:
/// - Inherits from Properties to access handler functions and properties
/// - Configures fuzzer targeting (contracts, selectors, senders)
/// - Implements invariant test functions that call property validators
/// - Each invariant function represents a critical system property to maintain
/// - Fuzzer will call targeted handlers randomly and check invariants after each call
contract FuzzerMedusa_EthenaARM is Properties {
//////////////////////////////////////////////////////
/// --- SETUP
//////////////////////////////////////////////////////
constructor() {
_setup();
}
}
6 changes: 3 additions & 3 deletions test/invariants/EthenaARM/Properties.sol
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@ abstract contract Properties is TargetFunctions {
// ╔══════════════════════════════════════════════════════════════════════════════╗
// ║ ✦✦✦ AFTER ALL ✦✦✦ ║
// ╚══════════════════════════════════════════════════════════════════════════════╝
function propertyAfterAll() public returns (bool) {
function _propertyAfterAll() internal returns (bool) {
uint256 usdeBalance = usde.balanceOf(address(arm));
uint256 susdeBalance = susde.balanceOf(address(arm));
uint256 morphoBalance = morpho.balanceOf(address(arm));
uint256 armTotalAssets = arm.totalAssets();
if (this.isConsoleAvailable()) {
if (isConsoleAvailable) {
console.log("--- Final Balances ---");
console.log("ARM USDe balance:\t %18e", usdeBalance);
console.log("ARM sUSDe balance:\t %18e", susdeBalance);
Expand All @@ -214,7 +214,7 @@ abstract contract Properties is TargetFunctions {
uint256 totalMinted = mintedUSDe[user];
uint256 userBalance = usde.balanceOf(user);
if (!Math.approxGteAbs(userBalance, totalMinted, 1e1)) {
if (this.isConsoleAvailable()) {
if (isConsoleAvailable) {
console.log(">>> Property After All failed for user %s:", vm.getLabel(user));
console.log(" - User USDe balance: %18e", userBalance);
console.log(" - Total minted USDe: %18e", totalMinted);
Expand Down
6 changes: 3 additions & 3 deletions test/invariants/EthenaARM/Setup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ abstract contract Setup is Base_Test_ {
//////////////////////////////////////////////////////
/// --- SETUP
//////////////////////////////////////////////////////
function setUp() public virtual {
function _setup() internal virtual {
// 1. Setup a realistic test environnement.
_setUpRealisticEnvironnement();

Expand Down Expand Up @@ -159,7 +159,7 @@ abstract contract Setup is Base_Test_ {

function _labelAll() internal virtual {
// This only works with Foundry's Vm.label feature.
if (!this.isLabelAvailable()) return;
if (!isLabelAvailable) return;

// --- Proxies ---
vm.label(address(armProxy), "Proxy EthenaARM");
Expand Down Expand Up @@ -292,7 +292,7 @@ abstract contract Setup is Base_Test_ {

function assume(bool condition) internal returns (bool returnEarly) {
if (!condition) {
if (this.isAssumeAvailable()) vm.assume(false);
if (isAssumeAvailable) vm.assume(false);
else returnEarly = true;
}
}
Expand Down
Loading
Loading