Skip to content

Commit fe79268

Browse files
Update health precision from UFix64 to UInt256 with 18 decimals (#31)
* update health from UFix64 to UInt256 increasing decimal precision from 8 to 18 * update tests for compatibility with increased health precision * clean up lines commented during refactor * update test to use global constants * add funds_required_ tests & fix contract miscalculation * uncomment bypassed test cases * fix overflow in TidalProtocolUtils.mul * add undercollateralized scenarios to funds_required_for_target_health_test.cdc * fix fundsRequired miscalculation by accounting for collateral/deposit factors
1 parent bb80c66 commit fe79268

12 files changed

Lines changed: 722 additions & 103 deletions

cadence/contracts/TidalProtocol.cdc

Lines changed: 58 additions & 48 deletions
Large diffs are not rendered by default.

cadence/contracts/TidalProtocolUtils.cdc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,26 @@ access(all) contract TidalProtocolUtils {
186186
/// @param y: Second operand (scaled by 10^18)
187187
/// @return: Product scaled by 10^18
188188
access(all) view fun mul(_ x: UInt256, _ y: UInt256): UInt256 {
189-
return (x * y) / self.e18
189+
// multiply the two values as 18-decimal fixed-point numbers in a manner that avoids overflow in the cases where
190+
// the result would be greater than 2^256 but also avoids rounding to 0 in the cases where the result would be
191+
// less than 10^18
192+
// To avoid overflow, perform the multiplication in two steps if either x or y is large.
193+
// If both x and y are less than sqrt(UInt256.max), safe to multiply directly.
194+
// Otherwise, rearrange: (x * y) / e18 = x * (y / e18) if y >= e18, or y * (x / e18) if x >= e18.
195+
if x == 0 || y == 0 {
196+
return 0
197+
}
198+
// Calculate sqrt(2^256) = 2^128 using a bitshift, using the value as a threshold to avoid overflow
199+
let sqrtMax = UInt256(1) << 128
200+
if x < sqrtMax && y < sqrtMax {
201+
return (x * y) / self.e18
202+
}
203+
// Prefer to divide the larger value first to avoid loss of precision
204+
if x >= y {
205+
return x * (y / self.e18)
206+
} else {
207+
return y * (x / self.e18)
208+
}
190209
}
191210

192211
/// Divides two 18-decimal fixed-point numbers

cadence/scripts/tidal-protocol/funds_avail_above_target_health_after_deposit.cdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ access(all)
44
fun main(
55
pid: UInt64,
66
withdrawType: String,
7-
targetHealth: UFix64,
7+
targetHealth: UInt256,
88
depositType: String,
99
depositAmount: UFix64
1010
): UFix64 {

cadence/scripts/tidal-protocol/funds_req_for_target_health_after_withdraw.cdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ access(all)
44
fun main(
55
pid: UInt64,
66
depositType: String,
7-
targetHealth: UFix64,
7+
targetHealth: UInt256,
88
withdrawType: String,
99
withdrawAmount: UFix64
1010
): UFix64 {

cadence/scripts/tidal-protocol/position_health.cdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import "TidalProtocol"
55
/// @param pid: The Position ID
66
///
77
access(all)
8-
fun main(pid: UInt64): UFix64 {
8+
fun main(pid: UInt64): UInt256 {
99
let protocolAddress= Type<@TidalProtocol.Pool>().address!
1010
return getAccount(protocolAddress).capabilities.borrow<&TidalProtocol.Pool>(TidalProtocol.PoolPublicPath)
1111
?.positionHealth(pid: pid)

cadence/tests/auto_borrow_behavior_test.cdc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ fun testAutoBorrowBehaviorWithTargetHealth() {
8181

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

8787
// Verify the user actually received the borrowed MOET in their Vault (draw-down sink)
8888
let userMoetBalance = getBalance(address: user.address, vaultPublicPath: MOET.VaultPublicPath)!

cadence/tests/funds_available_above_target_health_test.cdc

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "test_helpers.cdc"
55

66
import "MOET"
77
import "TidalProtocol"
8+
import "TidalProtocolUtils"
89

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

17-
access(all) let minHealth = 1.1
18-
access(all) let targetHealth = 1.3
19-
access(all) let maxHealth = 1.5
20-
access(all) let ceilingHealth = UFix64.max // the maximum health value when health is virtually infinite AKA debt ~0.0
18+
2119
access(all) let flowCollateralFactor = 0.8
2220
access(all) let flowBorrowFactor = 1.0
2321
access(all) let flowStartPrice = 1.0 // denominated in MOET
@@ -97,7 +95,7 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithPushFromHealthy() {
9795
// assert expected starting point
9896
let balanceAfterBorrow = getBalance(address: userAccount.address, vaultPublicPath: MOET.VaultPublicPath)!
9997
let expectedBorrowAmount = (positionFundingAmount * flowCollateralFactor * flowStartPrice) / targetHealth
100-
Test.assert(balanceAfterBorrow >= expectedBorrowAmount - 0.01 && balanceAfterBorrow <= expectedBorrowAmount + 0.01,
98+
Test.assert(equalWithinVariance(expectedBorrowAmount, balanceAfterBorrow),
10199
message: "Expected MOET balance to be ~\(expectedBorrowAmount), but got \(balanceAfterBorrow)")
102100

103101
let evts = Test.eventsOfType(Type<TidalProtocol.Opened>())
@@ -113,8 +111,8 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithPushFromHealthy() {
113111
Test.assertEqual(TidalProtocol.BalanceDirection.Credit, flowPositionBalance.direction)
114112
Test.assertEqual(TidalProtocol.BalanceDirection.Debit, moetBalance.direction)
115113

116-
Test.assertEqual(targetHealth, health)
117-
// Test.assertEqual(ceilingHealth, health)
114+
Test.assert(equalWithinVariance(intTargetHealth, health),
115+
message: "Expected health to be \(intTargetHealth), but got \(health)")
118116

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

@@ -126,8 +124,8 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithPushFromHealthy() {
126124
runFundsAvailableAboveTargetHealthAfterDepositing(
127125
pid: positionID,
128126
existingBorrowed: expectedBorrowAmount,
129-
existingFlowCollateral: positionFundingAmount,
130-
currentFlowPrice: flowStartPrice,
127+
existingFLOWCollateral: positionFundingAmount,
128+
currentFLOWPrice: flowStartPrice,
131129
depositAmount: amount,
132130
withdrawIdentifier: moetTokenIdentifier,
133131
depositIdentifier: flowTokenIdentifier
@@ -141,8 +139,8 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithPushFromHealthy() {
141139
runFundsAvailableAboveTargetHealthAfterDepositing(
142140
pid: positionID,
143141
existingBorrowed: expectedBorrowAmount,
144-
existingFlowCollateral: positionFundingAmount,
145-
currentFlowPrice: flowStartPrice,
142+
existingFLOWCollateral: positionFundingAmount,
143+
currentFLOWPrice: flowStartPrice,
146144
depositAmount: amount,
147145
withdrawIdentifier: moetTokenIdentifier,
148146
depositIdentifier: flowTokenIdentifier
@@ -198,8 +196,8 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithoutPushFromHealthy() {
198196
runFundsAvailableAboveTargetHealthAfterDepositing(
199197
pid: positionID,
200198
existingBorrowed: expectedBorrowAmount,
201-
existingFlowCollateral: positionFundingAmount,
202-
currentFlowPrice: flowStartPrice,
199+
existingFLOWCollateral: positionFundingAmount,
200+
currentFLOWPrice: flowStartPrice,
203201
depositAmount: amount,
204202
withdrawIdentifier: moetTokenIdentifier,
205203
depositIdentifier: flowTokenIdentifier
@@ -213,8 +211,8 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithoutPushFromHealthy() {
213211
runFundsAvailableAboveTargetHealthAfterDepositing(
214212
pid: positionID,
215213
existingBorrowed: expectedBorrowAmount,
216-
existingFlowCollateral: positionFundingAmount,
217-
currentFlowPrice: flowStartPrice,
214+
existingFLOWCollateral: positionFundingAmount,
215+
currentFLOWPrice: flowStartPrice,
218216
depositAmount: amount,
219217
withdrawIdentifier: moetTokenIdentifier,
220218
depositIdentifier: flowTokenIdentifier
@@ -282,21 +280,19 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithoutPushFromOvercollate
282280

283281
log("..............................")
284282
var depositAmount = 0.0
285-
// var expectedAvailable = expectedAvailableAboveTarget + (depositAmount * flowCollateralFactor / targetHealth * flowBorrowFactor) * newPrice
286283
var expectedAvailable = (positionFundingAmount + depositAmount) * newPrice * flowCollateralFactor / targetHealth * flowBorrowFactor
287284
var actualAvailable = fundsAvailableAboveTargetHealthAfterDepositing(
288285
pid: positionID,
289286
withdrawType: moetTokenIdentifier,
290-
targetHealth: targetHealth,
287+
targetHealth: intTargetHealth,
291288
depositType: flowTokenIdentifier,
292289
depositAmount: depositAmount,
293290
beFailed: false
294291
)
295292
log("[TEST] Depositing: \(depositAmount)")
296293
log("[TEST] Expected Available: \(expectedAvailable)")
297294
log("[TEST] Actual Available: \(actualAvailable)")
298-
// getting error here - expected: 76.92307692, actual: 61.53846153
299-
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable, plusMinus: nil),
295+
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable),
300296
message: "Values are not equal within variance - expected: \(expectedAvailable), actual: \(actualAvailable)")
301297

302298
log("..............................")
@@ -306,15 +302,15 @@ fun testFundsAvailableAboveTargetHealthAfterDepositingWithoutPushFromOvercollate
306302
actualAvailable = fundsAvailableAboveTargetHealthAfterDepositing(
307303
pid: positionID,
308304
withdrawType: moetTokenIdentifier,
309-
targetHealth: targetHealth,
305+
targetHealth: intTargetHealth,
310306
depositType: flowTokenIdentifier,
311307
depositAmount: depositAmount,
312308
beFailed: false
313309
)
314310
log("[TEST] Depositing: \(depositAmount)")
315311
log("[TEST] Expected Available: \(expectedAvailable)")
316312
log("[TEST] Actual Available: \(actualAvailable)")
317-
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable, plusMinus: nil),
313+
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable),
318314
message: "Values are not equal within variance - expected: \(expectedAvailable), actual: \(actualAvailable)")
319315

320316
log("==============================")
@@ -330,20 +326,20 @@ access(all)
330326
fun runFundsAvailableAboveTargetHealthAfterDepositing(
331327
pid: UInt64,
332328
existingBorrowed: UFix64,
333-
existingFlowCollateral: UFix64,
334-
currentFlowPrice: UFix64,
329+
existingFLOWCollateral: UFix64,
330+
currentFLOWPrice: UFix64,
335331
depositAmount: UFix64,
336332
withdrawIdentifier: String,
337333
depositIdentifier: String
338334
) {
339335
log("..............................")
340-
let expectedTotalBorrowCapacity = (existingFlowCollateral + depositAmount) * currentFlowPrice * flowCollateralFactor / targetHealth * flowBorrowFactor
336+
let expectedTotalBorrowCapacity = (existingFLOWCollateral + depositAmount) * currentFLOWPrice * flowCollateralFactor / targetHealth * flowBorrowFactor
341337
let expectedAvailable = expectedTotalBorrowCapacity - existingBorrowed
342338

343339
let actualAvailable = fundsAvailableAboveTargetHealthAfterDepositing(
344340
pid: pid,
345341
withdrawType: withdrawIdentifier,
346-
targetHealth: targetHealth,
342+
targetHealth: intTargetHealth,
347343
depositType: depositIdentifier,
348344
depositAmount: depositAmount,
349345
beFailed: false
@@ -353,6 +349,6 @@ fun runFundsAvailableAboveTargetHealthAfterDepositing(
353349
log("[TEST] Depositing: \(depositAmount)")
354350
log("[TEST] Expected Available: \(expectedAvailable)")
355351
log("[TEST] Actual Available: \(actualAvailable)")
356-
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable, plusMinus: nil),
352+
Test.assert(equalWithinVariance(expectedAvailable, actualAvailable),
357353
message: "Values are not equal within variance - expected: \(expectedAvailable), actual: \(actualAvailable)")
358354
}

0 commit comments

Comments
 (0)