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
11 changes: 6 additions & 5 deletions cadence/lib/FlowALPMath.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,12 @@ access(all) contract FlowALPMath {
return scaledInt % 2 == 1 ? self.roundUp(base) : base
}

/// Checks that the DEX price does not deviate from the oracle price by more than the given threshold.
/// The deviation is computed as the absolute difference divided by the smaller price, expressed in basis points.
/// Checks if the DEX price deviates from the oracle price by more than the allowed threshold (in basis points).
/// The deviation is measured relative to the oracle price, which is treated as the reference price.
access(all) view fun dexOraclePriceDeviationInRange(dexPrice: UFix64, oraclePrice: UFix64, maxDeviationBps: UInt16): Bool {
let diff: UFix64 = dexPrice < oraclePrice ? oraclePrice - dexPrice : dexPrice - oraclePrice
let diffPct: UFix64 = dexPrice < oraclePrice ? diff / dexPrice : diff / oraclePrice
// Deviation expressed as a percentage of the oracle price (reference)
let diffPct: UFix64 = diff / oraclePrice
let diffBps = UInt16(diffPct * 10_000.0)
return diffBps <= maxDeviationBps
}
Expand Down Expand Up @@ -146,7 +147,7 @@ access(all) contract FlowALPMath {
/// Effective Collateral is defined:
/// Ce = (Nc)(Pc)(Fc)
/// Where:
/// Ce = Effective Collateral
/// Ce = Effective Collateral
/// Nc = Number of Collateral Tokens
/// Pc = Collateral Token Price
/// Fc = Collateral Factor
Expand All @@ -162,7 +163,7 @@ access(all) contract FlowALPMath {
/// Effective Debt is defined:
/// De = (Nd)(Pd)(Fd)
/// Where:
/// De = Effective Debt
/// De = Effective Debt
/// Nd = Number of Debt Tokens
/// Pd = Debt Token Price
/// Fd = Borrow Factor
Expand Down
43 changes: 43 additions & 0 deletions cadence/tests/flowALPMath_dex_oracle_deviation_test.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Test
import "FlowALPMath"

access(all) fun setup() {
let err = Test.deployContract(
name: "FlowALPMath",
path: "../lib/FlowALPMath.cdc",
arguments: []
)
Test.expect(err, Test.beNil())
}

access(all) fun test_dex_oracle_deviation_boundary_exact_threshold() {
// Exactly at 300 bps (3%) — should pass
// Oracle: $1.00, DEX: $1.03 → deviation = |1.03-1.00|/1.00 = 3.0% = 300 bps
var res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 1.03, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(true, res)

// One basis point over — should fail
// Oracle: $1.00, DEX: $1.0301 → deviation = 3.01% = 301 bps
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 1.0301, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(false, res)

// DEX below oracle — exactly at threshold
// Oracle: $1.00, DEX: $0.97 → deviation = |0.97-1.00|/1.00 = 3.0% = 300 bps
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 0.97, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(true, res)

// One basis point over on the low side — should fail
// Oracle: $1.00, DEX: $0.9699 → deviation = |0.9699-1.00|/1.00 = 3.01% = 301 bps
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 0.9699, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(false, res)

// DEX: $0.971 → deviation = |0.971-1.00|/1.00 = 2.9% = 290 bps
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 0.971, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(true, res)

// Equal prices — zero deviation — always passes
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 1.0, oraclePrice: 1.0, maxDeviationBps: 0)
Test.assertEqual(true, res)
}


68 changes: 16 additions & 52 deletions cadence/tests/fork_oracle_failure_test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fun test_oracle_nil_price() {
// =============================================================================

// -----------------------------------------------------------------------------
/// Verifies that the protocol rejects a position when the PriceOracle returns a zero price.
/// Verifies that the protocol rejects a position when the PriceOracle returns a zero price.
/// A zero price would cause division-by-zero in health calculations
/// and incorrectly value collateral at $0. The PriceOracle interface
/// guarantees `result! > 0.0`, so setting price to 0.0 must cause the oracle to revert.
Expand Down Expand Up @@ -171,9 +171,9 @@ fun test_oracle_near_zero_price_extreme_health() {
// STEP 2: Crash FLOW to near-zero ($0.00000001)
setMockOraclePrice(signer: MAINNET_PROTOCOL_ACCOUNT, forTokenIdentifier: MAINNET_FLOW_TOKEN_ID, price: 0.00000001)

// Collateral:
// Collateral:
// FLOW: 1000 * $0.00000001 * 0.8 = $0.000008
// Debt:
// Debt:
// MOET: 500 * $1.00 / 1.0 = $500
//
// Health = $0.000008 / $500 = 0.000000016 (unhealty, essentially zero)
Expand Down Expand Up @@ -215,9 +215,9 @@ fun test_oracle_very_large_price_no_overflow() {
// STEP 2: Set WETH to extreme price (UFix64.max)
setMockOraclePrice(signer: MAINNET_PROTOCOL_ACCOUNT, forTokenIdentifier: MAINNET_WETH_TOKEN_ID, price: UFix64.max)

// Collateral:
// Collateral:
// WETH: 0.001 * $100,000,000 * 0.75 = $75,000
// Debt: 0$
// Debt: 0$
//
// Health = infinite (UFix128.max)
let health = getPositionHealth(pid: pid, beFailed: false)
Expand All @@ -228,42 +228,6 @@ fun test_oracle_very_large_price_no_overflow() {
Test.assertEqual(wethAmount, available)
}

// =============================================================================
// DEX-Oracle price deviation utility function
// =============================================================================

// -----------------------------------------------------------------------------
// dexOraclePriceDeviationInRange — Boundary Cases
// Tests the pure helper function that computes deviation in basis points.
// -----------------------------------------------------------------------------
access(all)
fun test_dex_oracle_deviation_boundary_exact_threshold() {
safeReset()

// Exactly at 300 bps (3%) — should pass
// Oracle: $1.00, DEX: $1.03 → deviation = |1.03-1.00|/1.00 = 3.0% = 300 bps
var res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 1.03, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(true, res)

// One basis point over — should fail
// Oracle: $1.00, DEX: $1.0301 → deviation = 3.01% = 301 bps
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 1.0301, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(false, res)

// DEX below oracle — same threshold applies
// Oracle: $1.00, DEX: $0.97 → deviation = |0.97-1.00|/0.97 = 3.09% = 309 bps
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 0.97, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(false, res)

// DEX: $0.971 → deviation = |0.971-1.00|/0.971 = 2.98% = 298 bps
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 0.971, oraclePrice: 1.0, maxDeviationBps: 300)
Test.assertEqual(true, res)

// Equal prices — zero deviation — always passes
res = FlowALPMath.dexOraclePriceDeviationInRange(dexPrice: 1.0, oraclePrice: 1.0, maxDeviationBps: 0)
Test.assertEqual(true, res)
}

// -----------------------------------------------------------------------------
// Governance Adjusts DEX Deviation Threshold
// Tests that governance can tighten or loosen the circuit breaker.
Expand Down Expand Up @@ -358,10 +322,10 @@ fun test_flash_crash_triggers_liquidation() {

borrowFromPosition(signer: user, positionId: pid, tokenTypeIdentifier: MAINNET_MOET_TOKEN_ID, vaultStoragePath: MAINNET_MOET_STORAGE_PATH, amount: 600.0, beFailed: false)

// Collateral:
// Collateral:
// FLOW: 1000 * $1.00 * 0.8 = $800
// Total collateral: $800
// Debt:
// Debt:
// MOET: 600 * $1.00 / 1.0 = $600
// Total debt: $600
//
Expand All @@ -381,10 +345,10 @@ fun test_flash_crash_triggers_liquidation() {
)

// New position state:
// Collateral:
// Collateral:
// FLOW: 1000 * $0.5 * 0.8 = $400
// Total collateral: $400
// Debt:
// Debt:
// MOET: 600 * $1.00 / 1.0 = $600
// Total debt: $600
//
Expand Down Expand Up @@ -431,7 +395,7 @@ fun test_flash_crash_triggers_liquidation() {
// Tests that a position immediately reflects the new higher health.
// Typical collateral factors are not sufficient to protect against sudden and dramatic price moves.
// FlowALP relies on Oracle implementations to smooth out underlying price information or return no price
// at all when price information sources disagree.
// at all when price information sources disagree.
// -----------------------------------------------------------------------------
access(all)
fun test_flash_pump_increase_doubles_health() {
Expand All @@ -455,11 +419,11 @@ fun test_flash_pump_increase_doubles_health() {

borrowFromPosition(signer: user, positionId: pid, tokenTypeIdentifier: MAINNET_MOET_TOKEN_ID, vaultStoragePath: MAINNET_MOET_STORAGE_PATH, amount: 500.0, beFailed: false)

// Collateral:
// Collateral:
// FLOW: 1000 * $1.00 * 0.8 = $800
// Total collateral: $800
//
// Debt:
// Debt:
// MOET: 500 * $1.00 / 1.0 = $500
// Total debt: $500
//
Expand All @@ -473,11 +437,11 @@ fun test_flash_pump_increase_doubles_health() {
setMockOraclePrice(signer: MAINNET_PROTOCOL_ACCOUNT, forTokenIdentifier: MAINNET_FLOW_TOKEN_ID, price: 2.0)

// New position state:
// Collateral:
// Collateral:
// FLOW: 1000 * $2.00 * 0.8 = $1600
// Total collateral: $1600
//
// Debt:
// Debt:
// MOET: 500 * $1.00 / 1.0 = $500
// Total debt: $500
//
Expand All @@ -499,11 +463,11 @@ fun test_flash_pump_increase_doubles_health() {
setMockOraclePrice(signer: MAINNET_PROTOCOL_ACCOUNT, forTokenIdentifier: MAINNET_FLOW_TOKEN_ID, price: 1.0)

// Position after correction:
// Collateral:
// Collateral:
// FLOW: 1000 * $1.00 * 0.8 = $800
// Total collateral: $800
//
// Debt:
// Debt:
// MOET: (500+900) * $1.00 / 1.0 = $1400
// Total debt: $1400
//
Expand Down
Loading