Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ coverage.json
/.pr-body.md
/.github/pr_bodies/
lcov.info
flow.fork-test.json
1 change: 1 addition & 0 deletions integration/liquidation-fork-mainnet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tmp/
63 changes: 63 additions & 0 deletions integration/liquidation-fork-mainnet/flow.fork.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
}
}
310 changes: 310 additions & 0 deletions integration/liquidation-fork-mainnet/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
#!/bin/bash
#
# 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
#
# 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)"

# Working directory for this test - all temporary/generated files go here
WORK_DIR="$SCRIPT_DIR/tmp"
KEY_FILE="$WORK_DIR/test-key.pkey"
# 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"

# 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"

# Helper to attach config flags to flow command.
# Only references the copy and overlay configs — the original flow.json is never touched.
flow_cmd() {
flow "$@" -f "$BASE_CONFIG" -f "$FORK_CONFIG" -f "$DYNAMIC_CONFIG"
}

# Helper: send a transaction and assert success
send_tx() {
local description="$1"
shift
echo ">> $description"
if ! flow_cmd 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_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")
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 ""
}

echo "============================================"
echo " Manual Liquidation Smoke Test (Fork)"
echo "============================================"
echo ""

# --------------------------------------------------------------------------
# Step 0: Generate a test key pair
# --------------------------------------------------------------------------
echo "--- Step 0: Setup ---"

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 ""

# --------------------------------------------------------------------------
# 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 ""

# --------------------------------------------------------------------------
# Step 1: Switch pool to MockOracle and MockDexSwapper
# --------------------------------------------------------------------------
echo "--- Step 1: Configure pool with mock oracle and DEX ---"

send_tx "Set MockOracle on pool" \
"$SCRIPT_DIR/transactions/set_mock_oracle.cdc" \
--signer "$DEPLOYER"

send_tx "Set MockDexSwapper on pool" \
"$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" \
"$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" \
"$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"

echo ""

# --------------------------------------------------------------------------
# Step 2: Create user and liquidator accounts
# --------------------------------------------------------------------------
echo "--- Step 2: Create test accounts ---"

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_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 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}}' \
"$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}}' \
"$DYNAMIC_CONFIG" > "$WORK_DIR/flow.json.tmp" && mv "$WORK_DIR/flow.json.tmp" "$DYNAMIC_CONFIG"

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" \
"$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" \
"$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" \
"$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" \
"$PROJECT_DIR/cadence/transactions/moet/setup_vault.cdc" \
--signer fork-user

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" \
"$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_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)

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 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 ""

# Check position health
echo ">> Check position health (should be healthy, >= 1.0)"
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 ""

# --------------------------------------------------------------------------
# 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" \
"$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

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" \
"$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" \
"$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_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 ""

# --------------------------------------------------------------------------
# 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" \
"$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_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 ""

echo "============================================"
echo " ALL TESTS PASSED"
echo "============================================"
Loading
Loading