From 34058e3bac4f0b2f0bfc44b7274be2d0ddef4d16 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 18 Feb 2026 16:19:49 -0800 Subject: [PATCH 1/6] Add manual liquidation smoke test for mainnet fork Shell script that exercises the full manual liquidation flow against a running mainnet fork: configures mock oracle/DEX, creates user and liquidator accounts, opens a position, verifies liquidation fails on a healthy position, drops the FLOW price, then verifies liquidation succeeds on the now-unhealthy position. Co-Authored-By: Claude Opus 4.6 --- fork-test/run_manual_liquidation_test.sh | 298 +++++++++++++++++++++ fork-test/transactions/set_mock_dex.cdc | 19 ++ fork-test/transactions/set_mock_oracle.cdc | 19 ++ 3 files changed, 336 insertions(+) create mode 100755 fork-test/run_manual_liquidation_test.sh create mode 100644 fork-test/transactions/set_mock_dex.cdc create mode 100644 fork-test/transactions/set_mock_oracle.cdc diff --git a/fork-test/run_manual_liquidation_test.sh b/fork-test/run_manual_liquidation_test.sh new file mode 100755 index 00000000..be34166b --- /dev/null +++ b/fork-test/run_manual_liquidation_test.sh @@ -0,0 +1,298 @@ +#!/bin/bash +# +# Manual Liquidation Smoke Test on Mainnet Fork +# +# Prerequisites: +# - A mainnet fork is running locally (ports 3569/8888/8080) +# - flow.json is configured with mainnet-fork network and accounts +# +# This test: +# 1. Switches the pool to use MockOracle and MockDexSwapper +# 2. Creates user and liquidator accounts +# 3. User opens a position (deposit FLOW, borrow MOET) +# 4. Attempts manual liquidation (should FAIL - position is healthy) +# 5. Drops FLOW price to make position unhealthy +# 6. Attempts manual liquidation (should SUCCEED) + +set -euo pipefail + +NETWORK="mainnet-fork" +DEPLOYER="mainnet-fork-deployer" # 6b00ff876c299c61 - FlowALP protocol account +FYV_DEPLOYER="mainnet-fork-fyv-deployer" # b1d63873c3cc9f79 - MockOracle/MockDex deployer +FLOW_SOURCE="fork-flow-source" # 92674150c9213fc9 - well-funded FLOW account + +FLOW_TOKEN_ID="A.1654653399040a61.FlowToken.Vault" +MOET_TOKEN_ID="A.6b00ff876c299c61.MOET.Vault" +MOET_VAULT_STORAGE_PATH="/storage/moetTokenVault_0x6b00ff876c299c61" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_DIR" + +# Temporary files for key management +TMPDIR_TEST="${TMPDIR:-/tmp}/fork-test-$$" +mkdir -p "$TMPDIR_TEST" +KEY_FILE="$TMPDIR_TEST/test-key.pkey" + +# Track whether we modified flow.json +FLOW_JSON_BACKUP="$TMPDIR_TEST/flow.json.backup" +MODIFIED_FLOW_JSON=false + +cleanup() { + if [ "$MODIFIED_FLOW_JSON" = true ] && [ -f "$FLOW_JSON_BACKUP" ]; then + echo "Restoring flow.json..." + cp "$FLOW_JSON_BACKUP" flow.json + fi + rm -rf "$TMPDIR_TEST" +} +trap cleanup EXIT + +# Helper: send a transaction and assert success +send_tx() { + local description="$1" + shift + echo ">> $description" + if ! flow transactions send "$@" --network "$NETWORK"; then + echo "FAIL: $description" + exit 1 + fi + echo "" +} + +# Helper: send a transaction and assert failure (reverted on-chain) +send_tx_expect_fail() { + local description="$1" + shift + echo ">> $description (expecting failure)" + # Capture output; the CLI may return non-zero for reverted txns + local result + result=$(flow transactions send "$@" --network "$NETWORK" --output json 2>&1) || true + # Check if the transaction had an error (statusCode != 0 means reverted) + local status_code + status_code=$(echo "$result" | jq -r '.statusCode // 0' 2>/dev/null || echo "1") + if [ "$status_code" = "0" ]; then + # Also check for explicit error field + local has_error + has_error=$(echo "$result" | jq -r 'if .error then "yes" else "no" end' 2>/dev/null || echo "no") + if [ "$has_error" = "no" ]; then + echo "FAIL: Expected transaction to fail but it succeeded: $description" + echo "$result" | jq . 2>/dev/null || echo "$result" + exit 1 + fi + fi + echo " (transaction failed as expected)" + echo "" +} + +# Helper: execute a script +run_script() { + local description="$1" + shift + echo ">> $description" + flow scripts execute "$@" --network "$NETWORK" + echo "" +} + +echo "============================================" +echo " Manual Liquidation Smoke Test (Fork)" +echo "============================================" +echo "" + +# -------------------------------------------------------------------------- +# Step 0: Generate a test key pair and back up flow.json +# -------------------------------------------------------------------------- +echo "--- Step 0: Setup ---" +cp flow.json "$FLOW_JSON_BACKUP" +MODIFIED_FLOW_JSON=true + +KEY_JSON=$(flow keys generate --output json --sig-algo ECDSA_P256) +PRIV_KEY=$(echo "$KEY_JSON" | jq -r '.private') +PUB_KEY=$(echo "$KEY_JSON" | jq -r '.public') +printf '%s' "$PRIV_KEY" > "$KEY_FILE" +echo "Generated test key pair" + +# Add the FLOW source account (well-funded mainnet account, using emulator key since fork doesn't check sigs) +jq '.accounts["fork-flow-source"] = {"address": "92674150c9213fc9", "key": {"type": "file", "location": "emulator-account.pkey"}}' \ + flow.json > "$TMPDIR_TEST/flow.json.tmp" && mv "$TMPDIR_TEST/flow.json.tmp" flow.json +echo "Added FLOW source account to flow.json" +echo "" + +# -------------------------------------------------------------------------- +# Step 1: Switch pool to MockOracle and MockDexSwapper +# -------------------------------------------------------------------------- +echo "--- Step 1: Configure pool with mock oracle and DEX ---" + +send_tx "Set MockOracle on pool" \ + ./fork-test/transactions/set_mock_oracle.cdc \ + --signer "$DEPLOYER" + +send_tx "Set MockDexSwapper on pool" \ + ./fork-test/transactions/set_mock_dex.cdc \ + --signer "$DEPLOYER" + +# Set FLOW price = 1.0 MOET in the oracle +send_tx "Set oracle price: FLOW = 1.0" \ + ./cadence/tests/transactions/mock-oracle/set_price.cdc \ + "$FLOW_TOKEN_ID" 1.0 \ + --signer "$FYV_DEPLOYER" + +# Set DEX price: 1 FLOW -> 1 MOET (priceRatio = 1.0) +send_tx "Set DEX price: FLOW/MOET = 1.0" \ + ./cadence/tests/transactions/mock-dex-swapper/set_mock_dex_price_for_pair.cdc \ + "$FLOW_TOKEN_ID" "$MOET_TOKEN_ID" "$MOET_VAULT_STORAGE_PATH" 1.0 \ + --signer "$DEPLOYER" + +echo "" + +# -------------------------------------------------------------------------- +# Step 2: Create user and liquidator accounts +# -------------------------------------------------------------------------- +echo "--- Step 2: Create test accounts ---" + +USER_RESULT=$(flow accounts create --key "$PUB_KEY" --signer "$DEPLOYER" --network "$NETWORK" --output json) +USER_ADDR=$(echo "$USER_RESULT" | jq -r '.address') +echo "Created user account: $USER_ADDR" + +LIQUIDATOR_RESULT=$(flow accounts create --key "$PUB_KEY" --signer "$DEPLOYER" --network "$NETWORK" --output json) +LIQUIDATOR_ADDR=$(echo "$LIQUIDATOR_RESULT" | jq -r '.address') +echo "Created liquidator account: $LIQUIDATOR_ADDR" + +# Add test accounts to flow.json (using the generated key) +jq --arg addr "${USER_ADDR#0x}" --arg keyfile "$KEY_FILE" \ + '.accounts["fork-user"] = {"address": $addr, "key": {"type": "file", "location": $keyfile}}' \ + flow.json > "$TMPDIR_TEST/flow.json.tmp" && mv "$TMPDIR_TEST/flow.json.tmp" flow.json + +jq --arg addr "${LIQUIDATOR_ADDR#0x}" --arg keyfile "$KEY_FILE" \ + '.accounts["fork-liquidator"] = {"address": $addr, "key": {"type": "file", "location": $keyfile}}' \ + flow.json > "$TMPDIR_TEST/flow.json.tmp" && mv "$TMPDIR_TEST/flow.json.tmp" flow.json + +echo "" + +# -------------------------------------------------------------------------- +# Step 3: Fund accounts +# -------------------------------------------------------------------------- +echo "--- Step 3: Fund accounts ---" + +# Transfer FLOW to user from well-funded account +send_tx "Transfer 1000 FLOW to user" \ + ./cadence/transactions/flowtoken/transfer_flowtoken.cdc \ + "$USER_ADDR" 1000.0 \ + --signer "$FLOW_SOURCE" + +# Setup MOET vault for liquidator +send_tx "Setup MOET vault for liquidator" \ + ./cadence/transactions/moet/setup_vault.cdc \ + --signer fork-liquidator + +# Mint MOET to liquidator (signer must have MOET Minter - the deployer account) +send_tx "Mint 1000 MOET to liquidator" \ + ./cadence/transactions/moet/mint_moet.cdc \ + "$LIQUIDATOR_ADDR" 1000.0 \ + --signer "$DEPLOYER" + +# Also setup MOET vault for user (needed for receiving borrowed MOET) +send_tx "Setup MOET vault for user" \ + ./cadence/transactions/moet/setup_vault.cdc \ + --signer fork-user + +# Setup FLOW receiver for liquidator (all accounts have FlowToken vault by default, +# but the liquidator account was just created so it should already have one) + +echo "" + +# -------------------------------------------------------------------------- +# Step 4: Grant beta access and create position +# -------------------------------------------------------------------------- +echo "--- Step 4: Grant beta access and open position ---" + +# Grant beta pool access to user (requires both admin and user as signers) +send_tx "Grant beta pool access to user" \ + ./cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc \ + --authorizer "$DEPLOYER",fork-user \ + --proposer "$DEPLOYER" \ + --payer "$DEPLOYER" + +# User creates position: deposit 1000 FLOW, borrow MOET +echo ">> User creates position (deposit 1000 FLOW)" +CREATE_POS_RESULT=$(flow transactions send \ + ./cadence/transactions/flow-alp/position/create_position.cdc \ + 1000.0 /storage/flowTokenVault true \ + --signer fork-user --network "$NETWORK" --output json) + +if echo "$CREATE_POS_RESULT" | jq -e '.error' > /dev/null 2>&1; then + echo "FAIL: User creates position" + echo "$CREATE_POS_RESULT" | jq -r '.error' + exit 1 +fi +echo " Position created successfully" + +# Extract position ID from the Rebalanced event (which contains the pid) +PID=$(echo "$CREATE_POS_RESULT" | jq '[.events[] | select(.type | contains("FlowALPv0.Rebalanced")) | .values.value.fields[] | select(.name == "pid") | .value.value | tonumber] | first') +echo "New position ID: $PID" +echo "" + +# Check position health +echo ">> Check position health (should be healthy, >= 1.0)" +HEALTH_JSON=$(flow scripts execute ./cadence/scripts/flow-alp/position_health.cdc "$PID" --network "$NETWORK" --output json) +HEALTH=$(echo "$HEALTH_JSON" | jq -r '.value') +echo "Position health: $HEALTH" +echo "" + +# -------------------------------------------------------------------------- +# Step 5: Attempt liquidation on healthy position (should FAIL) +# -------------------------------------------------------------------------- +echo "--- Step 5: Attempt liquidation on healthy position (expect failure) ---" + +send_tx_expect_fail "Manual liquidation on healthy position" \ + ./cadence/transactions/flow-alp/pool-management/manual_liquidation.cdc \ + "$PID" "$MOET_TOKEN_ID" "$FLOW_TOKEN_ID" 190.0 100.0 \ + --signer fork-liquidator + +echo "" + +# -------------------------------------------------------------------------- +# Step 6: Drop FLOW price to make position unhealthy +# -------------------------------------------------------------------------- +echo "--- Step 6: Drop FLOW price to 0.5 ---" + +send_tx "Set oracle price: FLOW = 0.5" \ + ./cadence/tests/transactions/mock-oracle/set_price.cdc \ + "$FLOW_TOKEN_ID" 0.5 \ + --signer "$FYV_DEPLOYER" + +send_tx "Set DEX price: FLOW/MOET = 0.5" \ + ./cadence/tests/transactions/mock-dex-swapper/set_mock_dex_price_for_pair.cdc \ + "$FLOW_TOKEN_ID" "$MOET_TOKEN_ID" "$MOET_VAULT_STORAGE_PATH" 0.5 \ + --signer "$DEPLOYER" + +# Check position health again +echo ">> Check position health (should be unhealthy, < 1.0)" +HEALTH_JSON=$(flow scripts execute ./cadence/scripts/flow-alp/position_health.cdc "$PID" --network "$NETWORK" --output json) +HEALTH=$(echo "$HEALTH_JSON" | jq -r '.value') +echo "Position health after price drop: $HEALTH" +echo "" + +# -------------------------------------------------------------------------- +# Step 7: Manual liquidation (should SUCCEED) +# -------------------------------------------------------------------------- +echo "--- Step 7: Manual liquidation on unhealthy position (expect success) ---" + +# Liquidator repays 100 MOET, seizes 190 FLOW +# DEX quote for 100 MOET at price 0.5: inAmount = 100/0.5 = 200 FLOW +# seizeAmount (190) < dexQuote (200) ✓ +# Post-liquidation health will be < target (1.05) ✓ +send_tx "Manual liquidation: repay 100 MOET, seize 190 FLOW" \ + ./cadence/transactions/flow-alp/pool-management/manual_liquidation.cdc \ + "$PID" "$MOET_TOKEN_ID" "$FLOW_TOKEN_ID" 190.0 100.0 \ + --signer fork-liquidator + +# Check post-liquidation health +echo ">> Check position health after liquidation" +HEALTH_JSON=$(flow scripts execute ./cadence/scripts/flow-alp/position_health.cdc "$PID" --network "$NETWORK" --output json) +HEALTH=$(echo "$HEALTH_JSON" | jq -r '.value') +echo "Position health after liquidation: $HEALTH" +echo "" + +echo "============================================" +echo " ALL TESTS PASSED" +echo "============================================" diff --git a/fork-test/transactions/set_mock_dex.cdc b/fork-test/transactions/set_mock_dex.cdc new file mode 100644 index 00000000..fcbe74dc --- /dev/null +++ b/fork-test/transactions/set_mock_dex.cdc @@ -0,0 +1,19 @@ +import "FlowALPv0" +import "MockDexSwapper" +import "DeFiActions" + +/// Sets the Pool's DEX swapper provider to MockDexSwapper.SwapperProvider. +/// Must be signed by the Pool governance account (the account storing the Pool resource). +transaction() { + let pool: auth(FlowALPv0.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) + ?? panic("Could not borrow reference to Pool from ".concat(FlowALPv0.PoolStoragePath.toString())) + } + + execute { + let dex = MockDexSwapper.SwapperProvider() + self.pool.setDEX(dex: dex) + } +} diff --git a/fork-test/transactions/set_mock_oracle.cdc b/fork-test/transactions/set_mock_oracle.cdc new file mode 100644 index 00000000..71828506 --- /dev/null +++ b/fork-test/transactions/set_mock_oracle.cdc @@ -0,0 +1,19 @@ +import "FlowALPv0" +import "MockOracle" +import "DeFiActions" + +/// Sets the Pool's price oracle to MockOracle.PriceOracle. +/// Must be signed by the Pool governance account (the account storing the Pool resource). +transaction() { + let pool: auth(FlowALPv0.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) + ?? panic("Could not borrow reference to Pool from ".concat(FlowALPv0.PoolStoragePath.toString())) + } + + execute { + let oracle = MockOracle.PriceOracle() + self.pool.setPriceOracle(oracle) + } +} From a4c5863a72262cbdc8ad893397aadd94f6783ed1 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 19 Feb 2026 10:12:16 -0800 Subject: [PATCH 2/6] Move fork test to integration/liquidation-fork-mainnet/ with isolated config - Relocated fork-test/ to integration/liquidation-fork-mainnet/ - Use a temporary copy of flow.json so the base config is never modified - Overlay extra config (network, accounts, deployments) via -f flag - Deploy FungibleTokenConnectors to fork (not present on mainnet) - Store all test artifacts in gitignored tmp/ directory Co-Authored-By: Claude Opus 4.6 --- .gitignore | 1 + .../liquidation-fork-mainnet/.gitignore | 1 + .../liquidation-fork-mainnet/run.sh | 198 ++++++++++++------ .../transactions/set_mock_dex.cdc | 0 .../transactions/set_mock_oracle.cdc | 0 5 files changed, 137 insertions(+), 63 deletions(-) create mode 100644 integration/liquidation-fork-mainnet/.gitignore rename fork-test/run_manual_liquidation_test.sh => integration/liquidation-fork-mainnet/run.sh (60%) rename {fork-test => integration/liquidation-fork-mainnet}/transactions/set_mock_dex.cdc (100%) rename {fork-test => integration/liquidation-fork-mainnet}/transactions/set_mock_oracle.cdc (100%) diff --git a/.gitignore b/.gitignore index 6528a0da..eb5da0a1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage.json /.pr-body.md /.github/pr_bodies/ lcov.info +flow.fork-test.json diff --git a/integration/liquidation-fork-mainnet/.gitignore b/integration/liquidation-fork-mainnet/.gitignore new file mode 100644 index 00000000..3fec32c8 --- /dev/null +++ b/integration/liquidation-fork-mainnet/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/fork-test/run_manual_liquidation_test.sh b/integration/liquidation-fork-mainnet/run.sh similarity index 60% rename from fork-test/run_manual_liquidation_test.sh rename to integration/liquidation-fork-mainnet/run.sh index be34166b..8eecca89 100755 --- a/fork-test/run_manual_liquidation_test.sh +++ b/integration/liquidation-fork-mainnet/run.sh @@ -26,33 +26,106 @@ MOET_TOKEN_ID="A.6b00ff876c299c61.MOET.Vault" MOET_VAULT_STORAGE_PATH="/storage/moetTokenVault_0x6b00ff876c299c61" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -cd "$PROJECT_DIR" - -# Temporary files for key management -TMPDIR_TEST="${TMPDIR:-/tmp}/fork-test-$$" -mkdir -p "$TMPDIR_TEST" -KEY_FILE="$TMPDIR_TEST/test-key.pkey" - -# Track whether we modified flow.json -FLOW_JSON_BACKUP="$TMPDIR_TEST/flow.json.backup" -MODIFIED_FLOW_JSON=false - -cleanup() { - if [ "$MODIFIED_FLOW_JSON" = true ] && [ -f "$FLOW_JSON_BACKUP" ]; then - echo "Restoring flow.json..." - cp "$FLOW_JSON_BACKUP" flow.json - fi - rm -rf "$TMPDIR_TEST" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Working directory for this test - all temporary/generated files go here +WORK_DIR="$SCRIPT_DIR/tmp" +KEY_FILE="$WORK_DIR/test-key.pkey" +# Extra accounts config (merged with project flow.json at runtime, never modifies the original) +EXTRA_CONFIG="$WORK_DIR/flow.json" + +# Clean previous run and create fresh working directory +rm -rf "$WORK_DIR" +mkdir -p "$WORK_DIR" + +# Initialize the extra config with mainnet-fork network, accounts, and deployments. +# This overlays onto the project flow.json so it never needs to be modified. +# Contracts that lack a "mainnet" alias in the base flow.json must be overridden here +# (with source + full aliases) so "fork": "mainnet" can resolve them on the fork network. +cat > "$EXTRA_CONFIG" <> $description" - if ! flow transactions send "$@" --network "$NETWORK"; then + if ! flow_cmd transactions send "$@" --network "$NETWORK"; then echo "FAIL: $description" exit 1 fi @@ -66,7 +139,7 @@ send_tx_expect_fail() { echo ">> $description (expecting failure)" # Capture output; the CLI may return non-zero for reverted txns local result - result=$(flow transactions send "$@" --network "$NETWORK" --output json 2>&1) || true + result=$(flow_cmd transactions send "$@" --network "$NETWORK" --output json 2>&1) || true # Check if the transaction had an error (statusCode != 0 means reverted) local status_code status_code=$(echo "$result" | jq -r '.statusCode // 0' 2>/dev/null || echo "1") @@ -84,37 +157,39 @@ send_tx_expect_fail() { echo "" } -# Helper: execute a script -run_script() { - local description="$1" - shift - echo ">> $description" - flow scripts execute "$@" --network "$NETWORK" - echo "" -} - echo "============================================" echo " Manual Liquidation Smoke Test (Fork)" echo "============================================" echo "" # -------------------------------------------------------------------------- -# Step 0: Generate a test key pair and back up flow.json +# Step 0: Generate a test key pair # -------------------------------------------------------------------------- echo "--- Step 0: Setup ---" -cp flow.json "$FLOW_JSON_BACKUP" -MODIFIED_FLOW_JSON=true KEY_JSON=$(flow keys generate --output json --sig-algo ECDSA_P256) PRIV_KEY=$(echo "$KEY_JSON" | jq -r '.private') PUB_KEY=$(echo "$KEY_JSON" | jq -r '.public') printf '%s' "$PRIV_KEY" > "$KEY_FILE" echo "Generated test key pair" +echo "" -# Add the FLOW source account (well-funded mainnet account, using emulator key since fork doesn't check sigs) -jq '.accounts["fork-flow-source"] = {"address": "92674150c9213fc9", "key": {"type": "file", "location": "emulator-account.pkey"}}' \ - flow.json > "$TMPDIR_TEST/flow.json.tmp" && mv "$TMPDIR_TEST/flow.json.tmp" flow.json -echo "Added FLOW source account to flow.json" +# -------------------------------------------------------------------------- +# Step 0.5: Deploy FungibleTokenConnectors (not on mainnet, needed by create_position) +# -------------------------------------------------------------------------- +echo "--- Step 0.5: Deploy FungibleTokenConnectors ---" +echo ">> Deploying FungibleTokenConnectors to DeFi deployer account" +DEPLOY_OUTPUT=$(flow_cmd accounts add-contract \ + "$PROJECT_DIR/FlowActions/cadence/contracts/connectors/FungibleTokenConnectors.cdc" \ + --signer mainnet-fork-defi-deployer --network "$NETWORK" 2>&1) || { + if echo "$DEPLOY_OUTPUT" | grep -q "contract already exists"; then + echo " (contract already deployed, skipping)" + else + echo "FAIL: Deploy FungibleTokenConnectors" + echo "$DEPLOY_OUTPUT" + exit 1 + fi +} echo "" # -------------------------------------------------------------------------- @@ -123,22 +198,22 @@ echo "" echo "--- Step 1: Configure pool with mock oracle and DEX ---" send_tx "Set MockOracle on pool" \ - ./fork-test/transactions/set_mock_oracle.cdc \ + "$SCRIPT_DIR/transactions/set_mock_oracle.cdc" \ --signer "$DEPLOYER" send_tx "Set MockDexSwapper on pool" \ - ./fork-test/transactions/set_mock_dex.cdc \ + "$SCRIPT_DIR/transactions/set_mock_dex.cdc" \ --signer "$DEPLOYER" # Set FLOW price = 1.0 MOET in the oracle send_tx "Set oracle price: FLOW = 1.0" \ - ./cadence/tests/transactions/mock-oracle/set_price.cdc \ + "$PROJECT_DIR/cadence/tests/transactions/mock-oracle/set_price.cdc" \ "$FLOW_TOKEN_ID" 1.0 \ --signer "$FYV_DEPLOYER" # Set DEX price: 1 FLOW -> 1 MOET (priceRatio = 1.0) send_tx "Set DEX price: FLOW/MOET = 1.0" \ - ./cadence/tests/transactions/mock-dex-swapper/set_mock_dex_price_for_pair.cdc \ + "$PROJECT_DIR/cadence/tests/transactions/mock-dex-swapper/set_mock_dex_price_for_pair.cdc" \ "$FLOW_TOKEN_ID" "$MOET_TOKEN_ID" "$MOET_VAULT_STORAGE_PATH" 1.0 \ --signer "$DEPLOYER" @@ -149,22 +224,22 @@ echo "" # -------------------------------------------------------------------------- echo "--- Step 2: Create test accounts ---" -USER_RESULT=$(flow accounts create --key "$PUB_KEY" --signer "$DEPLOYER" --network "$NETWORK" --output json) +USER_RESULT=$(flow_cmd accounts create --key "$PUB_KEY" --signer "$DEPLOYER" --network "$NETWORK" --output json) USER_ADDR=$(echo "$USER_RESULT" | jq -r '.address') echo "Created user account: $USER_ADDR" -LIQUIDATOR_RESULT=$(flow accounts create --key "$PUB_KEY" --signer "$DEPLOYER" --network "$NETWORK" --output json) +LIQUIDATOR_RESULT=$(flow_cmd accounts create --key "$PUB_KEY" --signer "$DEPLOYER" --network "$NETWORK" --output json) LIQUIDATOR_ADDR=$(echo "$LIQUIDATOR_RESULT" | jq -r '.address') echo "Created liquidator account: $LIQUIDATOR_ADDR" -# Add test accounts to flow.json (using the generated key) +# Add test accounts to our extra config (using the generated key) jq --arg addr "${USER_ADDR#0x}" --arg keyfile "$KEY_FILE" \ '.accounts["fork-user"] = {"address": $addr, "key": {"type": "file", "location": $keyfile}}' \ - flow.json > "$TMPDIR_TEST/flow.json.tmp" && mv "$TMPDIR_TEST/flow.json.tmp" flow.json + "$EXTRA_CONFIG" > "$WORK_DIR/flow.json.tmp" && mv "$WORK_DIR/flow.json.tmp" "$EXTRA_CONFIG" jq --arg addr "${LIQUIDATOR_ADDR#0x}" --arg keyfile "$KEY_FILE" \ '.accounts["fork-liquidator"] = {"address": $addr, "key": {"type": "file", "location": $keyfile}}' \ - flow.json > "$TMPDIR_TEST/flow.json.tmp" && mv "$TMPDIR_TEST/flow.json.tmp" flow.json + "$EXTRA_CONFIG" > "$WORK_DIR/flow.json.tmp" && mv "$WORK_DIR/flow.json.tmp" "$EXTRA_CONFIG" echo "" @@ -175,29 +250,26 @@ echo "--- Step 3: Fund accounts ---" # Transfer FLOW to user from well-funded account send_tx "Transfer 1000 FLOW to user" \ - ./cadence/transactions/flowtoken/transfer_flowtoken.cdc \ + "$PROJECT_DIR/cadence/transactions/flowtoken/transfer_flowtoken.cdc" \ "$USER_ADDR" 1000.0 \ --signer "$FLOW_SOURCE" # Setup MOET vault for liquidator send_tx "Setup MOET vault for liquidator" \ - ./cadence/transactions/moet/setup_vault.cdc \ + "$PROJECT_DIR/cadence/transactions/moet/setup_vault.cdc" \ --signer fork-liquidator # Mint MOET to liquidator (signer must have MOET Minter - the deployer account) send_tx "Mint 1000 MOET to liquidator" \ - ./cadence/transactions/moet/mint_moet.cdc \ + "$PROJECT_DIR/cadence/transactions/moet/mint_moet.cdc" \ "$LIQUIDATOR_ADDR" 1000.0 \ --signer "$DEPLOYER" # Also setup MOET vault for user (needed for receiving borrowed MOET) send_tx "Setup MOET vault for user" \ - ./cadence/transactions/moet/setup_vault.cdc \ + "$PROJECT_DIR/cadence/transactions/moet/setup_vault.cdc" \ --signer fork-user -# Setup FLOW receiver for liquidator (all accounts have FlowToken vault by default, -# but the liquidator account was just created so it should already have one) - echo "" # -------------------------------------------------------------------------- @@ -207,15 +279,15 @@ echo "--- Step 4: Grant beta access and open position ---" # Grant beta pool access to user (requires both admin and user as signers) send_tx "Grant beta pool access to user" \ - ./cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc \ + "$PROJECT_DIR/cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc" \ --authorizer "$DEPLOYER",fork-user \ --proposer "$DEPLOYER" \ --payer "$DEPLOYER" # User creates position: deposit 1000 FLOW, borrow MOET echo ">> User creates position (deposit 1000 FLOW)" -CREATE_POS_RESULT=$(flow transactions send \ - ./cadence/transactions/flow-alp/position/create_position.cdc \ +CREATE_POS_RESULT=$(flow_cmd transactions send \ + "$PROJECT_DIR/cadence/transactions/flow-alp/position/create_position.cdc" \ 1000.0 /storage/flowTokenVault true \ --signer fork-user --network "$NETWORK" --output json) @@ -233,7 +305,7 @@ echo "" # Check position health echo ">> Check position health (should be healthy, >= 1.0)" -HEALTH_JSON=$(flow scripts execute ./cadence/scripts/flow-alp/position_health.cdc "$PID" --network "$NETWORK" --output json) +HEALTH_JSON=$(flow_cmd scripts execute "$PROJECT_DIR/cadence/scripts/flow-alp/position_health.cdc" "$PID" --network "$NETWORK" --output json) HEALTH=$(echo "$HEALTH_JSON" | jq -r '.value') echo "Position health: $HEALTH" echo "" @@ -244,7 +316,7 @@ echo "" echo "--- Step 5: Attempt liquidation on healthy position (expect failure) ---" send_tx_expect_fail "Manual liquidation on healthy position" \ - ./cadence/transactions/flow-alp/pool-management/manual_liquidation.cdc \ + "$PROJECT_DIR/cadence/transactions/flow-alp/pool-management/manual_liquidation.cdc" \ "$PID" "$MOET_TOKEN_ID" "$FLOW_TOKEN_ID" 190.0 100.0 \ --signer fork-liquidator @@ -256,18 +328,18 @@ echo "" echo "--- Step 6: Drop FLOW price to 0.5 ---" send_tx "Set oracle price: FLOW = 0.5" \ - ./cadence/tests/transactions/mock-oracle/set_price.cdc \ + "$PROJECT_DIR/cadence/tests/transactions/mock-oracle/set_price.cdc" \ "$FLOW_TOKEN_ID" 0.5 \ --signer "$FYV_DEPLOYER" send_tx "Set DEX price: FLOW/MOET = 0.5" \ - ./cadence/tests/transactions/mock-dex-swapper/set_mock_dex_price_for_pair.cdc \ + "$PROJECT_DIR/cadence/tests/transactions/mock-dex-swapper/set_mock_dex_price_for_pair.cdc" \ "$FLOW_TOKEN_ID" "$MOET_TOKEN_ID" "$MOET_VAULT_STORAGE_PATH" 0.5 \ --signer "$DEPLOYER" # Check position health again echo ">> Check position health (should be unhealthy, < 1.0)" -HEALTH_JSON=$(flow scripts execute ./cadence/scripts/flow-alp/position_health.cdc "$PID" --network "$NETWORK" --output json) +HEALTH_JSON=$(flow_cmd scripts execute "$PROJECT_DIR/cadence/scripts/flow-alp/position_health.cdc" "$PID" --network "$NETWORK" --output json) HEALTH=$(echo "$HEALTH_JSON" | jq -r '.value') echo "Position health after price drop: $HEALTH" echo "" @@ -282,13 +354,13 @@ echo "--- Step 7: Manual liquidation on unhealthy position (expect success) ---" # seizeAmount (190) < dexQuote (200) ✓ # Post-liquidation health will be < target (1.05) ✓ send_tx "Manual liquidation: repay 100 MOET, seize 190 FLOW" \ - ./cadence/transactions/flow-alp/pool-management/manual_liquidation.cdc \ + "$PROJECT_DIR/cadence/transactions/flow-alp/pool-management/manual_liquidation.cdc" \ "$PID" "$MOET_TOKEN_ID" "$FLOW_TOKEN_ID" 190.0 100.0 \ --signer fork-liquidator # Check post-liquidation health echo ">> Check position health after liquidation" -HEALTH_JSON=$(flow scripts execute ./cadence/scripts/flow-alp/position_health.cdc "$PID" --network "$NETWORK" --output json) +HEALTH_JSON=$(flow_cmd scripts execute "$PROJECT_DIR/cadence/scripts/flow-alp/position_health.cdc" "$PID" --network "$NETWORK" --output json) HEALTH=$(echo "$HEALTH_JSON" | jq -r '.value') echo "Position health after liquidation: $HEALTH" echo "" diff --git a/fork-test/transactions/set_mock_dex.cdc b/integration/liquidation-fork-mainnet/transactions/set_mock_dex.cdc similarity index 100% rename from fork-test/transactions/set_mock_dex.cdc rename to integration/liquidation-fork-mainnet/transactions/set_mock_dex.cdc diff --git a/fork-test/transactions/set_mock_oracle.cdc b/integration/liquidation-fork-mainnet/transactions/set_mock_oracle.cdc similarity index 100% rename from fork-test/transactions/set_mock_oracle.cdc rename to integration/liquidation-fork-mainnet/transactions/set_mock_oracle.cdc From 920a2ff9c1e0c9c96803e037f540aef830cd41ef Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 19 Feb 2026 10:18:16 -0800 Subject: [PATCH 3/6] Extract fork config from run.sh into committed flow.fork.json Uses __PROJECT_DIR__ placeholder expanded at runtime via sed, keeping the template portable. Dynamic test accounts (fork-user, fork-liquidator) are still written to a separate runtime config. Co-Authored-By: Claude Opus 4.6 --- .../liquidation-fork-mainnet/flow.fork.json | 63 ++++++++++++ integration/liquidation-fork-mainnet/run.sh | 95 ++++--------------- 2 files changed, 81 insertions(+), 77 deletions(-) create mode 100644 integration/liquidation-fork-mainnet/flow.fork.json diff --git a/integration/liquidation-fork-mainnet/flow.fork.json b/integration/liquidation-fork-mainnet/flow.fork.json new file mode 100644 index 00000000..e9170760 --- /dev/null +++ b/integration/liquidation-fork-mainnet/flow.fork.json @@ -0,0 +1,63 @@ +{ + "contracts": { + "FungibleTokenConnectors": { + "source": "__PROJECT_DIR__/FlowActions/cadence/contracts/connectors/FungibleTokenConnectors.cdc", + "aliases": { + "mainnet": "6d888f175c158410", + "testing": "0000000000000006" + } + } + }, + "networks": { + "mainnet-fork": { + "host": "127.0.0.1:3569", + "fork": "mainnet" + } + }, + "accounts": { + "mainnet-fork-deployer": { + "address": "6b00ff876c299c61", + "key": { + "type": "file", + "location": "__PROJECT_DIR__/emulator-account.pkey" + } + }, + "mainnet-fork-fyv-deployer": { + "address": "b1d63873c3cc9f79", + "key": { + "type": "file", + "location": "__PROJECT_DIR__/emulator-account.pkey" + } + }, + "fork-flow-source": { + "address": "92674150c9213fc9", + "key": { + "type": "file", + "location": "__PROJECT_DIR__/emulator-account.pkey" + } + }, + "mainnet-fork-defi-deployer": { + "address": "6d888f175c158410", + "key": { + "type": "file", + "location": "__PROJECT_DIR__/emulator-account.pkey" + } + } + }, + "deployments": { + "mainnet-fork": { + "mainnet-fork-deployer": [ + "FlowALPMath", + { + "name": "MOET", + "args": [{ "value": "0.00000000", "type": "UFix64" }] + }, + "FlowALPv0" + ], + "mainnet-fork-fyv-deployer": [ + "MockDexSwapper", + "MockOracle" + ] + } + } +} diff --git a/integration/liquidation-fork-mainnet/run.sh b/integration/liquidation-fork-mainnet/run.sh index 8eecca89..1d6d1c08 100755 --- a/integration/liquidation-fork-mainnet/run.sh +++ b/integration/liquidation-fork-mainnet/run.sh @@ -31,82 +31,20 @@ PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" # Working directory for this test - all temporary/generated files go here WORK_DIR="$SCRIPT_DIR/tmp" KEY_FILE="$WORK_DIR/test-key.pkey" -# Extra accounts config (merged with project flow.json at runtime, never modifies the original) -EXTRA_CONFIG="$WORK_DIR/flow.json" +# Runtime fork config (expanded from committed flow.fork.json template) +FORK_CONFIG="$WORK_DIR/flow.fork.json" +# Runtime config for dynamically-created test accounts (fork-user, fork-liquidator) +DYNAMIC_CONFIG="$WORK_DIR/accounts.json" # Clean previous run and create fresh working directory rm -rf "$WORK_DIR" mkdir -p "$WORK_DIR" -# Initialize the extra config with mainnet-fork network, accounts, and deployments. -# This overlays onto the project flow.json so it never needs to be modified. -# Contracts that lack a "mainnet" alias in the base flow.json must be overridden here -# (with source + full aliases) so "fork": "mainnet" can resolve them on the fork network. -cat > "$EXTRA_CONFIG" < "$FORK_CONFIG" + +# Initialize the dynamic accounts config (empty, populated later) +echo '{}' > "$DYNAMIC_CONFIG" # Create a working copy of flow.json so the CLI never writes back to the original. # Place it at the project root level so existing relative paths (./cadence/...) still resolve. @@ -115,9 +53,12 @@ cp "$PROJECT_DIR/flow.json" "$BASE_CONFIG" # Ensure cleanup on exit trap 'rm -f "$BASE_CONFIG"' EXIT -# Helper to attach config flags to flow command +# Helper to attach config flags to flow command. +# BASE_CONFIG: working copy of project flow.json (relative paths resolve from project root) +# FORK_CONFIG: committed fork config (network, static accounts, deployments) +# DYNAMIC_CONFIG: runtime config for dynamically-created test accounts flow_cmd() { - flow "$@" -f "$BASE_CONFIG" -f "$EXTRA_CONFIG" + flow "$@" -f "$BASE_CONFIG" -f "$FORK_CONFIG" -f "$DYNAMIC_CONFIG" } # Helper: send a transaction and assert success @@ -235,11 +176,11 @@ echo "Created liquidator account: $LIQUIDATOR_ADDR" # Add test accounts to our extra config (using the generated key) jq --arg addr "${USER_ADDR#0x}" --arg keyfile "$KEY_FILE" \ '.accounts["fork-user"] = {"address": $addr, "key": {"type": "file", "location": $keyfile}}' \ - "$EXTRA_CONFIG" > "$WORK_DIR/flow.json.tmp" && mv "$WORK_DIR/flow.json.tmp" "$EXTRA_CONFIG" + "$DYNAMIC_CONFIG" > "$WORK_DIR/flow.json.tmp" && mv "$WORK_DIR/flow.json.tmp" "$DYNAMIC_CONFIG" jq --arg addr "${LIQUIDATOR_ADDR#0x}" --arg keyfile "$KEY_FILE" \ '.accounts["fork-liquidator"] = {"address": $addr, "key": {"type": "file", "location": $keyfile}}' \ - "$EXTRA_CONFIG" > "$WORK_DIR/flow.json.tmp" && mv "$WORK_DIR/flow.json.tmp" "$EXTRA_CONFIG" + "$DYNAMIC_CONFIG" > "$WORK_DIR/flow.json.tmp" && mv "$WORK_DIR/flow.json.tmp" "$DYNAMIC_CONFIG" echo "" @@ -298,8 +239,8 @@ if echo "$CREATE_POS_RESULT" | jq -e '.error' > /dev/null 2>&1; then fi echo " Position created successfully" -# Extract position ID from the Rebalanced event (which contains the pid) -PID=$(echo "$CREATE_POS_RESULT" | jq '[.events[] | select(.type | contains("FlowALPv0.Rebalanced")) | .values.value.fields[] | select(.name == "pid") | .value.value | tonumber] | first') +# Extract position ID from the Opened event +PID=$(echo "$CREATE_POS_RESULT" | jq '[.events[] | select(.type | contains("FlowALPv0.Opened")) | .values.value.fields[] | select(.name == "pid") | .value.value | tonumber] | first') echo "New position ID: $PID" echo "" From 9f3364bd14c5ffe4776872bac55164f0c6998540 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 19 Feb 2026 10:29:53 -0800 Subject: [PATCH 4/6] Protect flow.json from CLI write-back during fork tests Make flow.json read-only while the test runs instead of creating a temporary copy. This prevents the Flow CLI from writing merged config (fork accounts, networks, deployments) back into the project file. Remove the now-unnecessary flow.fork-test.json gitignore entry. Co-Authored-By: Claude Opus 4.6 --- .gitignore | 1 - integration/liquidation-fork-mainnet/run.sh | 22 +++++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index eb5da0a1..6528a0da 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ coverage.json /.pr-body.md /.github/pr_bodies/ lcov.info -flow.fork-test.json diff --git a/integration/liquidation-fork-mainnet/run.sh b/integration/liquidation-fork-mainnet/run.sh index 1d6d1c08..b1bebb0a 100755 --- a/integration/liquidation-fork-mainnet/run.sh +++ b/integration/liquidation-fork-mainnet/run.sh @@ -2,9 +2,9 @@ # # Manual Liquidation Smoke Test on Mainnet Fork # -# Prerequisites: -# - A mainnet fork is running locally (ports 3569/8888/8080) -# - flow.json is configured with mainnet-fork network and accounts +# A mainnet fork must be running locally before running this test (ports 3569/8888/8080). +# - Run `flow init` to create a new project with default settings +# - Run `flow emulator --fork mainnet` within the new project directory # # This test: # 1. Switches the pool to use MockOracle and MockDexSwapper @@ -46,19 +46,15 @@ sed "s|__PROJECT_DIR__|$PROJECT_DIR|g" "$SCRIPT_DIR/flow.fork.json" > "$FORK_CON # Initialize the dynamic accounts config (empty, populated later) echo '{}' > "$DYNAMIC_CONFIG" -# Create a working copy of flow.json so the CLI never writes back to the original. -# Place it at the project root level so existing relative paths (./cadence/...) still resolve. -BASE_CONFIG="$PROJECT_DIR/flow.fork-test.json" -cp "$PROJECT_DIR/flow.json" "$BASE_CONFIG" -# Ensure cleanup on exit -trap 'rm -f "$BASE_CONFIG"' EXIT +# Make project flow.json read-only so the CLI can't write merged config back to it. +# The CLI auto-discovers ./flow.json from cwd; this prevents it from being modified. +chmod a-w "$PROJECT_DIR/flow.json" +trap 'chmod u+w "$PROJECT_DIR/flow.json"' EXIT # Helper to attach config flags to flow command. -# BASE_CONFIG: working copy of project flow.json (relative paths resolve from project root) -# FORK_CONFIG: committed fork config (network, static accounts, deployments) -# DYNAMIC_CONFIG: runtime config for dynamically-created test accounts +# The project flow.json is read-only to prevent the CLI from writing merged config back. flow_cmd() { - flow "$@" -f "$BASE_CONFIG" -f "$FORK_CONFIG" -f "$DYNAMIC_CONFIG" + flow "$@" -f "$PROJECT_DIR/flow.json" -f "$FORK_CONFIG" -f "$DYNAMIC_CONFIG" } # Helper: send a transaction and assert success From 0fe2be6ecb48df6ece0bb947bf4f16168d0aed76 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 19 Feb 2026 10:37:24 -0800 Subject: [PATCH 5/6] Copy flow.json to project root under different name for fork tests Instead of chmod or sed-rewriting paths, copy flow.json to flow.fork-test.json at the project root so relative paths resolve naturally. Deleted on exit via trap; gitignored for interrupted runs. Co-Authored-By: Claude Opus 4.6 --- .gitignore | 1 + integration/liquidation-fork-mainnet/run.sh | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 6528a0da..eb5da0a1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage.json /.pr-body.md /.github/pr_bodies/ lcov.info +flow.fork-test.json diff --git a/integration/liquidation-fork-mainnet/run.sh b/integration/liquidation-fork-mainnet/run.sh index b1bebb0a..59f36f3e 100755 --- a/integration/liquidation-fork-mainnet/run.sh +++ b/integration/liquidation-fork-mainnet/run.sh @@ -40,21 +40,22 @@ DYNAMIC_CONFIG="$WORK_DIR/accounts.json" rm -rf "$WORK_DIR" mkdir -p "$WORK_DIR" +# Copy project flow.json to a sibling file at the project root so relative paths +# (./cadence/...) still resolve. Cleaned up on exit; gitignored in case of interruption. +BASE_CONFIG="$PROJECT_DIR/flow.fork-test.json" +cp "$PROJECT_DIR/flow.json" "$BASE_CONFIG" +trap 'rm -f "$BASE_CONFIG"' EXIT + # Expand __PROJECT_DIR__ placeholder in the committed config template sed "s|__PROJECT_DIR__|$PROJECT_DIR|g" "$SCRIPT_DIR/flow.fork.json" > "$FORK_CONFIG" # Initialize the dynamic accounts config (empty, populated later) echo '{}' > "$DYNAMIC_CONFIG" -# Make project flow.json read-only so the CLI can't write merged config back to it. -# The CLI auto-discovers ./flow.json from cwd; this prevents it from being modified. -chmod a-w "$PROJECT_DIR/flow.json" -trap 'chmod u+w "$PROJECT_DIR/flow.json"' EXIT - # Helper to attach config flags to flow command. -# The project flow.json is read-only to prevent the CLI from writing merged config back. +# Only references the copy and overlay configs — the original flow.json is never touched. flow_cmd() { - flow "$@" -f "$PROJECT_DIR/flow.json" -f "$FORK_CONFIG" -f "$DYNAMIC_CONFIG" + flow "$@" -f "$BASE_CONFIG" -f "$FORK_CONFIG" -f "$DYNAMIC_CONFIG" } # Helper: send a transaction and assert success From 4a5057651fad37191092426ca725abcd60e439b7 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 19 Feb 2026 11:01:40 -0800 Subject: [PATCH 6/6] Apply suggestion from @jordanschalm --- integration/liquidation-fork-mainnet/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/liquidation-fork-mainnet/run.sh b/integration/liquidation-fork-mainnet/run.sh index 59f36f3e..16d8ad2d 100755 --- a/integration/liquidation-fork-mainnet/run.sh +++ b/integration/liquidation-fork-mainnet/run.sh @@ -2,6 +2,8 @@ # # Manual Liquidation Smoke Test on Mainnet Fork # +# This test should be run from the repo root: `./integration/liquidation-fork-mainnet/run.sh` +# # A mainnet fork must be running locally before running this test (ports 3569/8888/8080). # - Run `flow init` to create a new project with default settings # - Run `flow emulator --fork mainnet` within the new project directory