|
| 1 | +import Test |
| 2 | +import BlockchainHelpers |
| 3 | + |
| 4 | +import "test_helpers.cdc" |
| 5 | + |
| 6 | +/// Tests that setMinHealth and setMaxHealth queue the position for async update |
| 7 | +/// when the new bounds make the current health out-of-range. |
| 8 | +/// |
| 9 | +/// Strategy: verify that asyncUpdate rebalances the position after the setter is called, |
| 10 | +/// which only happens if the position was queued. Without the fix, asyncUpdate would be a no-op. |
| 11 | +/// |
| 12 | +/// Default health bounds: minHealth=1.1, targetHealth=1.3, maxHealth=1.5 |
| 13 | +/// Setup: 100 FLOW collateral, collateralFactor=0.8, price=1.0 |
| 14 | +/// effectiveCollateral = 80, debt (at targetHealth) = 80/1.3 ≈ 61.538 |
| 15 | + |
| 16 | +access(all) var snapshot: UInt64 = 0 |
| 17 | + |
| 18 | +access(all) |
| 19 | +fun setup() { |
| 20 | + deployContracts() |
| 21 | + |
| 22 | + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0) |
| 23 | + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: MOET_TOKEN_IDENTIFIER, price: 1.0) |
| 24 | + |
| 25 | + createAndStorePool(signer: PROTOCOL_ACCOUNT, defaultTokenIdentifier: MOET_TOKEN_IDENTIFIER, beFailed: false) |
| 26 | + addSupportedTokenZeroRateCurve( |
| 27 | + signer: PROTOCOL_ACCOUNT, |
| 28 | + tokenTypeIdentifier: FLOW_TOKEN_IDENTIFIER, |
| 29 | + collateralFactor: 0.8, |
| 30 | + borrowFactor: 1.0, |
| 31 | + depositRate: 1_000_000.0, |
| 32 | + depositCapacityCap: 1_000_000.0 |
| 33 | + ) |
| 34 | + |
| 35 | + snapshot = getCurrentBlockHeight() |
| 36 | + Test.moveTime(by: 1.0) |
| 37 | +} |
| 38 | + |
| 39 | +access(all) |
| 40 | +fun beforeEach() { |
| 41 | + Test.reset(to: snapshot) |
| 42 | +} |
| 43 | + |
| 44 | +/// Drains the async update queue so all queued positions are processed. |
| 45 | +access(all) |
| 46 | +fun drainQueue() { |
| 47 | + let res = _executeTransaction( |
| 48 | + "./transactions/flow-alp/pool-management/process_update_queue.cdc", |
| 49 | + [], |
| 50 | + PROTOCOL_ACCOUNT |
| 51 | + ) |
| 52 | + Test.expect(res, Test.beSucceeded()) |
| 53 | +} |
| 54 | + |
| 55 | +/// Price of 1.1 → health ≈ 1.43, within (1.1, 1.5). |
| 56 | +/// Setting maxHealth to 1.35 (below current health) should queue the position so that |
| 57 | +/// asyncUpdate rebalances it back toward targetHealth (1.3). |
| 58 | +access(all) |
| 59 | +fun test_setMaxHealth_queues_position_when_health_exceeds_new_max() { |
| 60 | + let user = Test.createAccount() |
| 61 | + setupMoetVault(user, beFailed: false) |
| 62 | + mintFlow(to: user, amount: 1_000.0) |
| 63 | + |
| 64 | + createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) |
| 65 | + drainQueue() |
| 66 | + |
| 67 | + // Modest price increase → health ≈ 1.43, still within (1.1, 1.5) |
| 68 | + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.1) |
| 69 | + |
| 70 | + let healthBeforeSetter = getPositionHealth(pid: 0, beFailed: false) |
| 71 | + |
| 72 | + // Lower maxHealth to 1.35 — current health (1.43) now exceeds the new max |
| 73 | + let setRes = _executeTransaction( |
| 74 | + "../transactions/flow-alp/position/set_max_health.cdc", |
| 75 | + [0 as UInt64, 1.35 as UFix64], |
| 76 | + user |
| 77 | + ) |
| 78 | + Test.expect(setRes, Test.beSucceeded()) |
| 79 | + |
| 80 | + // asyncUpdate should rebalance the position back toward targetHealth (1.3) |
| 81 | + drainQueue() |
| 82 | + |
| 83 | + let healthAfter = getPositionHealth(pid: 0, beFailed: false) |
| 84 | + Test.assert(healthAfter < healthBeforeSetter, |
| 85 | + message: "Expected position to be rebalanced toward targetHealth after setMaxHealth + asyncUpdate, but health did not decrease") |
| 86 | +} |
| 87 | + |
| 88 | +/// Price of 0.9 → health ≈ 1.17, within (1.1, 1.3). |
| 89 | +/// Setting minHealth to 1.2 (above current health) should queue the position so that |
| 90 | +/// asyncUpdate rebalances it back toward targetHealth (1.3). |
| 91 | +access(all) |
| 92 | +fun test_setMinHealth_queues_position_when_health_falls_below_new_min() { |
| 93 | + let user = Test.createAccount() |
| 94 | + setupMoetVault(user, beFailed: false) |
| 95 | + mintFlow(to: user, amount: 1_000.0) |
| 96 | + |
| 97 | + createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) |
| 98 | + drainQueue() |
| 99 | + |
| 100 | + // Modest price drop → health ≈ 1.17, still within (1.1, 1.3) |
| 101 | + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 0.9) |
| 102 | + |
| 103 | + let healthBeforeSetter = getPositionHealth(pid: 0, beFailed: false) |
| 104 | + |
| 105 | + // Raise minHealth to 1.2 — current health (1.17) now falls below the new min |
| 106 | + let setRes = _executeTransaction( |
| 107 | + "../transactions/flow-alp/position/set_min_health.cdc", |
| 108 | + [0 as UInt64, 1.2 as UFix64], |
| 109 | + user |
| 110 | + ) |
| 111 | + Test.expect(setRes, Test.beSucceeded()) |
| 112 | + |
| 113 | + // asyncUpdate should rebalance the position back toward targetHealth (1.3) |
| 114 | + drainQueue() |
| 115 | + |
| 116 | + let healthAfter = getPositionHealth(pid: 0, beFailed: false) |
| 117 | + Test.assert(healthAfter > healthBeforeSetter, |
| 118 | + message: "Expected position to be rebalanced toward targetHealth after setMinHealth + asyncUpdate, but health did not increase") |
| 119 | +} |
0 commit comments