|
| 1 | +import Test |
| 2 | +import BlockchainHelpers |
| 3 | + |
| 4 | +import "MOET" |
| 5 | +import "FlowALPv0" |
| 6 | +import "test_helpers.cdc" |
| 7 | + |
| 8 | +access(all) |
| 9 | +fun setup() { |
| 10 | + deployContracts() |
| 11 | + createAndStorePool(signer: PROTOCOL_ACCOUNT, defaultTokenIdentifier: MOET_TOKEN_IDENTIFIER, beFailed: false) |
| 12 | +} |
| 13 | + |
| 14 | +access(all) |
| 15 | +fun test_closePosition_clearsQueuedAsyncUpdateEntry() { |
| 16 | + // Regression target: |
| 17 | + // A position could remain in `positionsNeedingUpdates` after being closed. |
| 18 | + // Then `asyncUpdate()` would pop that stale pid and panic when trying to |
| 19 | + // update a position that no longer exists. |
| 20 | + // |
| 21 | + // This test recreates that exact sequence and asserts async callbacks |
| 22 | + // succeed after close. |
| 23 | + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0) |
| 24 | + |
| 25 | + // Keep deposit capacity low so new deposits can overflow active capacity and |
| 26 | + // be queued for async processing (which queues the position id as well). |
| 27 | + addSupportedTokenZeroRateCurve( |
| 28 | + signer: PROTOCOL_ACCOUNT, |
| 29 | + tokenTypeIdentifier: FLOW_TOKEN_IDENTIFIER, |
| 30 | + collateralFactor: 0.8, |
| 31 | + borrowFactor: 1.0, |
| 32 | + depositRate: 100.0, |
| 33 | + depositCapacityCap: 100.0 |
| 34 | + ) |
| 35 | + |
| 36 | + let user = Test.createAccount() |
| 37 | + setupMoetVault(user, beFailed: false) |
| 38 | + mintFlow(to: user, amount: 1_000.0) |
| 39 | + grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user) |
| 40 | + |
| 41 | + // Step 1: Open a position with a small initial deposit. |
| 42 | + // This consumes part of the token's active capacity. |
| 43 | + let openRes = _executeTransaction( |
| 44 | + "../transactions/flow-alp/position/create_position.cdc", |
| 45 | + [50.0, FLOW_VAULT_STORAGE_PATH, false], |
| 46 | + user |
| 47 | + ) |
| 48 | + Test.expect(openRes, Test.beSucceeded()) |
| 49 | + |
| 50 | + // Step 2: Deposit an amount that exceeds remaining active capacity. |
| 51 | + // The overflow is queued, and the position is put in the async update queue. |
| 52 | + let depositRes = _executeTransaction( |
| 53 | + "./transactions/position/deposit_to_position_by_id.cdc", |
| 54 | + [UInt64(0), 150.0, FLOW_VAULT_STORAGE_PATH, false], |
| 55 | + user |
| 56 | + ) |
| 57 | + Test.expect(depositRes, Test.beSucceeded()) |
| 58 | + |
| 59 | + // Step 3: Close the position before async callbacks drain the queue. |
| 60 | + // This is the key condition that previously left a stale pid behind. |
| 61 | + let closeRes = _executeTransaction( |
| 62 | + "../transactions/flow-alp/position/repay_and_close_position.cdc", |
| 63 | + [UInt64(0)], |
| 64 | + user |
| 65 | + ) |
| 66 | + Test.expect(closeRes, Test.beSucceeded()) |
| 67 | + |
| 68 | + // Step 4 (regression assertion): run async update callback. |
| 69 | + // Before the fix, this could panic when touching a removed position. |
| 70 | + // After the fix, stale entries are removed/skipped and callback succeeds. |
| 71 | + let asyncRes = _executeTransaction( |
| 72 | + "./transactions/flow-alp/pool-management/async_update_all.cdc", |
| 73 | + [], |
| 74 | + PROTOCOL_ACCOUNT |
| 75 | + ) |
| 76 | + Test.expect(asyncRes, Test.beSucceeded()) |
| 77 | + |
| 78 | + // Step 5: run one more callback to prove queue state remains clean. |
| 79 | + let asyncRes2 = _executeTransaction( |
| 80 | + "./transactions/flow-alp/pool-management/async_update_all.cdc", |
| 81 | + [], |
| 82 | + PROTOCOL_ACCOUNT |
| 83 | + ) |
| 84 | + Test.expect(asyncRes2, Test.beSucceeded()) |
| 85 | +} |
0 commit comments