Skip to content
81 changes: 37 additions & 44 deletions cadence/contracts/TidalProtocol.cdc

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ access(all)
fun main(
pid: UInt64,
withdrawType: String,
targetHealth: UFix64,
targetHealth: UInt256,
depositType: String,
depositAmount: UFix64
): UFix64 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ access(all)
fun main(
pid: UInt64,
depositType: String,
targetHealth: UFix64,
targetHealth: UInt256,
withdrawType: String,
withdrawAmount: UFix64
): UFix64 {
Expand Down
2 changes: 1 addition & 1 deletion cadence/scripts/tidal-protocol/position_health.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "TidalProtocol"
/// @param pid: The Position ID
///
access(all)
fun main(pid: UInt64): UFix64 {
fun main(pid: UInt64): UInt256 {
let protocolAddress= Type<@TidalProtocol.Pool>().address!
return getAccount(protocolAddress).capabilities.borrow<&TidalProtocol.Pool>(TidalProtocol.PoolPublicPath)
?.positionHealth(pid: pid)
Expand Down
4 changes: 2 additions & 2 deletions cadence/tests/auto_borrow_behavior_test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ fun testAutoBorrowBehaviorWithTargetHealth() {

// Verify position health is at target
let health = getPositionHealth(pid: 0, beFailed: false)
Test.assert(health >= 1.29 && health <= 1.31,
message: "Expected health to be at target (1.3), but got \(health)")
Test.assert(equalWithinVariance(intTargetHealth, health),
message: "Expected health to be \(intTargetHealth), but got \(health)")

// Verify the user actually received the borrowed MOET in their Vault (draw-down sink)
let userMoetBalance = getBalance(address: user.address, vaultPublicPath: MOET.VaultPublicPath)!
Expand Down
23 changes: 10 additions & 13 deletions cadence/tests/funds_available_above_target_health_test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "test_helpers.cdc"

import "MOET"
import "TidalProtocol"
import "TidalProtocolUtils"

access(all) let protocolAccount = Test.getAccount(0x0000000000000007)
access(all) let userAccount = Test.createAccount()
Expand All @@ -14,10 +15,7 @@ access(all) var moetTokenIdentifier = "A.0000000000000007.MOET.Vault"
access(all) let flowVaultStoragePath = /storage/flowTokenVault
access(all) let wrapperStoragePath = /storage/tidalProtocolPositionWrapper

access(all) let minHealth = 1.1
access(all) let targetHealth = 1.3
access(all) let maxHealth = 1.5
access(all) let ceilingHealth = UFix64.max // the maximum health value when health is virtually infinite AKA debt ~0.0

access(all) let flowCollateralFactor = 0.8
access(all) let flowBorrowFactor = 1.0
access(all) let flowStartPrice = 1.0 // denominated in MOET
Expand Down Expand Up @@ -113,8 +111,8 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithPushFromHealthy() {
Test.assertEqual(TidalProtocol.BalanceDirection.Credit, flowPositionBalance.direction)
Test.assertEqual(TidalProtocol.BalanceDirection.Debit, moetBalance.direction)

Test.assertEqual(targetHealth, health)
// Test.assertEqual(ceilingHealth, health)
Test.assert(equalWithinVariance(intTargetHealth, health),
message: "Expected health to be \(intTargetHealth), but got \(health)")

log("[TEST] FLOW price set to \(flowStartPrice)")

Expand Down Expand Up @@ -287,16 +285,15 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithoutPushFromOvercollate
var actualAvailable = fundsAvailableAboveTargetHealthAfterDepositing(
pid: positionID,
withdrawType: moetTokenIdentifier,
targetHealth: targetHealth,
targetHealth: intTargetHealth,
depositType: flowTokenIdentifier,
depositAmount: depositAmount,
beFailed: false
)
log("[TEST] Depositing: \(depositAmount)")
log("[TEST] Expected Available: \(expectedAvailable)")
log("[TEST] Actual Available: \(actualAvailable)")
// getting error here - expected: 76.92307692, actual: 61.53846153
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable, plusMinus: nil),
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable),
message: "Values are not equal within variance - expected: \(expectedAvailable), actual: \(actualAvailable)")

log("..............................")
Expand All @@ -306,15 +303,15 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithoutPushFromOvercollate
actualAvailable = fundsAvailableAboveTargetHealthAfterDepositing(
pid: positionID,
withdrawType: moetTokenIdentifier,
targetHealth: targetHealth,
targetHealth: intTargetHealth,
depositType: flowTokenIdentifier,
depositAmount: depositAmount,
beFailed: false
)
log("[TEST] Depositing: \(depositAmount)")
log("[TEST] Expected Available: \(expectedAvailable)")
log("[TEST] Actual Available: \(actualAvailable)")
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable, plusMinus: nil),
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable),
message: "Values are not equal within variance - expected: \(expectedAvailable), actual: \(actualAvailable)")

log("==============================")
Expand Down Expand Up @@ -343,7 +340,7 @@ fun runFundsAvailableAboveTargetHealthAfterDepositing(
let actualAvailable = fundsAvailableAboveTargetHealthAfterDepositing(
pid: pid,
withdrawType: withdrawIdentifier,
targetHealth: targetHealth,
targetHealth: intTargetHealth,
depositType: depositIdentifier,
depositAmount: depositAmount,
beFailed: false
Expand All @@ -353,6 +350,6 @@ fun runFundsAvailableAboveTargetHealthAfterDepositing(
log("[TEST] Depositing: \(depositAmount)")
log("[TEST] Expected Available: \(expectedAvailable)")
log("[TEST] Actual Available: \(actualAvailable)")
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable, plusMinus: nil),
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable),
message: "Values are not equal within variance - expected: \(expectedAvailable), actual: \(actualAvailable)")
}
2 changes: 1 addition & 1 deletion cadence/tests/rebalance_overcollateralised_test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fun testRebalanceOvercollateralised() {
let healthAfterPriceChange = getPositionHealth(pid: 0, beFailed: false)

// After a 20% price increase, health should be at least 1.5 (=960/615.38)
Test.assert(healthAfterPriceChange >= 1.5,
Test.assert(healthAfterPriceChange >= intMaxHealth,
message: "Expected health after price increase to be >= 1.5 but got ".concat(healthAfterPriceChange.toString()))

rebalancePosition(signer: protocolAccount, pid: 0, force: true, beFailed: false)
Expand Down
10 changes: 5 additions & 5 deletions cadence/tests/rebalance_undercollateralised_test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ fun testRebalanceUndercollateralised() {
let detailsAfterRebalance = getPositionDetails(pid: 0, beFailed: false)

// Expected debt after rebalance calculation based on contract's pay-down math
let effectiveCollateralAfterDrop: UFix64 = 1_000.0 * 0.8 * (1.0 - priceDropPct) // 640
let debtBefore: UFix64 = 615.38461538
let healthAfterPriceChangeVal: UFix64 = healthAfterPriceChange
let target: UFix64 = 1.3
let effectiveCollateralAfterDrop = 1_000.0 * 0.8 * (1.0 - priceDropPct) // 640
let debtBefore = 615.38461538
let healthAfterPriceChangeVal = healthAfterPriceChange
let target = 1.3

// Calculate required pay-down to restore health to target (1.3)
// Formula derived from: health = effectiveCollateral / effectiveDebt
Expand Down Expand Up @@ -109,6 +109,6 @@ fun testRebalanceUndercollateralised() {
log("Actual debt: ".concat(actualDebt.toString()))

// Ensure health is at least the minimum threshold (1.1)
Test.assert(healthAfterRebalance >= 1.1,
Test.assert(healthAfterRebalance >= intMinHealth,
message: "Health after rebalance should be at least the minimum (1.1) but was ".concat(healthAfterRebalance.toString()))
}
51 changes: 38 additions & 13 deletions cadence/tests/test_helpers.cdc
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import Test
import "TidalProtocol"

/* --- Global test constants --- */

access(all) let defaultTokenIdentifier = "A.0000000000000007.MOET.Vault"
access(all) let defaultVariance = 0.00000001
access(all) let defaultUFixVariance = 0.00000001
access(all) let defaultUIntVariance: UInt256 = 1_000_000_000

@sisyphusSmiling sisyphusSmiling Jul 15, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default variance of 0.00000001 (as int) which is necessary in instances where withdrawal amounts at target health are truncated - e.g. 1,000 FLOW == 800 effective collateral / 1.3 target health == 615.384615384615384615 available which when converted to UFix64 withdrawal amount leave 0.000000004615384615 available in excess of target health.


// Health values
access(all) let minHealth = 1.1
access(all) let targetHealth = 1.3
access(all) let maxHealth = 1.5
access(all) var intMinHealth: UInt256 = 1_100_000_000_000_000_000
access(all) var intTargetHealth: UInt256 = 1_300_000_000_000_000_000
access(all) var intMaxHealth: UInt256 = 1_500_000_000_000_000_000
access(all) let ceilingHealth = UInt256.max // the maximum health value when health is virtually infinite AKA debt ~0.0

/* --- Test execution helpers --- */

Expand Down Expand Up @@ -119,12 +131,12 @@ fun getAvailableBalance(pid: UInt64, vaultIdentifier: String, pullFromTopUpSourc
}

access(all)
fun getPositionHealth(pid: UInt64, beFailed: Bool): UFix64 {
fun getPositionHealth(pid: UInt64, beFailed: Bool): UInt256 {
let res = _executeScript("../scripts/tidal-protocol/position_health.cdc",
[pid]
)
Test.expect(res, beFailed ? Test.beFailed() : Test.beSucceeded())
return res.status == Test.ResultStatus.failed ? 0.0 : res.returnValue as! UFix64
return res.status == Test.ResultStatus.failed ? 0 : res.returnValue as! UInt256
}

access(all)
Expand All @@ -147,7 +159,7 @@ access(all)
fun fundsAvailableAboveTargetHealthAfterDepositing(
pid: UInt64,
withdrawType: String,
targetHealth: UFix64,
targetHealth: UInt256,
depositType: String,
depositAmount: UFix64,
beFailed: Bool
Expand Down Expand Up @@ -292,14 +304,27 @@ fun withdrawReserve(

/* --- Assertion Helpers --- */

access(all) fun equalWithinVariance(_ expected: UFix64, _ actual: UFix64, plusMinus: UFix64?): Bool {
let _variance = plusMinus ?? defaultVariance
if expected == actual {
return true
} else if expected == actual + _variance {
return true
} else if actual >= defaultVariance { // protect underflow
return expected == actual - defaultVariance
access(all) fun equalWithinVariance(_ expected: AnyStruct, _ actual: AnyStruct): Bool {
let expectedType = expected.getType()
let actualType = actual.getType()
if expectedType == Type<UFix64>() && actualType == Type<UFix64>() {
return ufixEqualWithinVariance(expected as! UFix64, actual as! UFix64)
} else if expectedType == Type<UInt256>() && actualType == Type<UInt256>() {
return uintEqualWithinVariance(expected as! UInt256, actual as! UInt256)
}
return false
panic("Expected and actual types do not match - expected: \(expectedType.identifier), actual: \(actualType.identifier)")
}

access(all) fun ufixEqualWithinVariance(_ expected: UFix64, _ actual: UFix64): Bool {
// return true if expected is within defaultUFixVariance of actual, false otherwise and protect for underflow`
let diff = Fix64(expected) - Fix64(actual)
// take the absolute value of the difference without relying on .abs()
let absDiff: UFix64 = diff < 0.0 ? UFix64(-1.0 * diff) : UFix64(diff)
return absDiff <= defaultUFixVariance
}

access(all) fun uintEqualWithinVariance(_ expected: UInt256, _ actual: UInt256): Bool {
let diff = Int256(expected) - Int256(actual)
let absDiff: UInt256 = diff < 0 ? UInt256(Int256(-1) * diff) : UInt256(diff)
return absDiff <= defaultUIntVariance
}
2 changes: 1 addition & 1 deletion cadence/tests/zero_debt_withdrawal_test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fun testZeroDebtFullWithdrawalAvailable() {

// 5. Ensure no debt: health should be exactly 1.0
let health = getPositionHealth(pid: pid, beFailed: false)
Test.assertEqual(UFix64.max, health)
Test.assertEqual(ceilingHealth, health)

// 6. available balance should equal original collateral (1000)
let available = getAvailableBalance(pid: pid, vaultIdentifier: flowTokenIdentifier, pullFromTopUpSource: true, beFailed: false)
Expand Down
Loading