Skip to content

Commit 3641469

Browse files
authored
Merge pull request #199 from onflow/lionel/fix-phantom-balance-types
Clear phantom debt/collateral types after exact zeroing
2 parents a414472 + 385387f commit 3641469

2 files changed

Lines changed: 127 additions & 2 deletions

File tree

cadence/contracts/FlowALPModels.cdc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2252,7 +2252,10 @@ access(all) contract FlowALPModels {
22522252
access(all) fun getCollateralTypes(): [Type] {
22532253
let types: [Type] = []
22542254
for type in self.balances.keys {
2255-
if self.balances[type]!.direction == BalanceDirection.Credit {
2255+
let balance = self.balances[type]!
2256+
// Ignore zero balances so exact repay/withdraw operations do not leave
2257+
// phantom token-type constraints.
2258+
if balance.direction == BalanceDirection.Credit && balance.scaledBalance > 0.0 {
22562259
types.append(type)
22572260
}
22582261
}
@@ -2264,7 +2267,10 @@ access(all) contract FlowALPModels {
22642267
access(all) fun getDebtTypes(): [Type] {
22652268
let types: [Type] = []
22662269
for type in self.balances.keys {
2267-
if self.balances[type]!.direction == BalanceDirection.Debit {
2270+
let balance = self.balances[type]!
2271+
// Ignore zero balances so exact repay/withdraw operations do not leave
2272+
// phantom token-type constraints.
2273+
if balance.direction == BalanceDirection.Debit && balance.scaledBalance > 0.0 {
22682274
types.append(type)
22692275
}
22702276
}

cadence/tests/debt_type_constraint_three_token_test.cdc

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,125 @@ fun testCannotBorrowSecondDebtType() {
141141
log("\n=== Test Complete: Debt Type Constraint Verified ===")
142142
}
143143

144+
/// Regression: exact debt repayment should clear debt-type constraints.
145+
/// After repaying FLOW debt to exactly zero, borrowing MOET as a new debt type should succeed.
146+
access(all)
147+
fun testExactRepayClearsDebtTypeConstraint() {
148+
Test.reset(to: snapshot)
149+
log("\n=== Test: Exact Repay Clears Debt Type Constraint ===\n")
150+
151+
// Provide FLOW reserves for initial FLOW borrow.
152+
let flowProvider = Test.createAccount()
153+
setupMoetVault(flowProvider, beFailed: false)
154+
transferFlowTokens(to: flowProvider, amount: 10_000.0)
155+
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, flowProvider)
156+
157+
let createFlowPos = executeTransaction(
158+
"../transactions/flow-alp/position/create_position.cdc",
159+
[5_000.0, FLOW_VAULT_STORAGE_PATH, false],
160+
flowProvider
161+
)
162+
Test.expect(createFlowPos, Test.beSucceeded())
163+
164+
let user = Test.createAccount()
165+
setupMoetVault(user, beFailed: false)
166+
setupDummyTokenVault(user)
167+
mintDummyToken(to: user, amount: 10_000.0)
168+
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user)
169+
170+
let createPosRes = executeTransaction(
171+
"../transactions/flow-alp/position/create_position.cdc",
172+
[5_000.0, DummyToken.VaultStoragePath, false],
173+
user
174+
)
175+
Test.expect(createPosRes, Test.beSucceeded())
176+
177+
let pid: UInt64 = 1
178+
179+
// Create FLOW debt, then repay exactly to zero.
180+
borrowFromPosition(
181+
signer: user,
182+
positionId: pid,
183+
tokenTypeIdentifier: FLOW_TOKEN_IDENTIFIER,
184+
amount: 300.0,
185+
beFailed: false
186+
)
187+
depositToPosition(
188+
signer: user,
189+
positionID: pid,
190+
amount: 300.0,
191+
vaultStoragePath: FLOW_VAULT_STORAGE_PATH,
192+
pushToDrawDownSink: false
193+
)
194+
195+
// If exact repay leaves a phantom FLOW debt type, this borrow would fail.
196+
borrowFromPosition(
197+
signer: user,
198+
positionId: pid,
199+
tokenTypeIdentifier: MOET_TOKEN_IDENTIFIER,
200+
amount: 100.0,
201+
beFailed: false
202+
)
203+
204+
let details = getPositionDetails(pid: pid, beFailed: false)
205+
let flowDebt = getDebitBalanceForType(details: details, vaultType: CompositeType(FLOW_TOKEN_IDENTIFIER)!)
206+
let moetDebt = getDebitBalanceForType(details: details, vaultType: CompositeType(MOET_TOKEN_IDENTIFIER)!)
207+
Test.assert(flowDebt == 0.0, message: "FLOW debt should be zero after exact repay")
208+
Test.assert(moetDebt >= 100.0 - 0.01, message: "MOET debt should be ~100 after new borrow")
209+
210+
log("\n=== Test Complete: Exact Repay Clears Debt Type Constraint ===")
211+
}
212+
213+
/// Regression: exact full collateral withdrawal should clear collateral-type constraints.
214+
/// After withdrawing FLOW collateral to exactly zero, depositing Dummy collateral should succeed.
215+
access(all)
216+
fun testExactFullWithdrawClearsCollateralTypeConstraint() {
217+
Test.reset(to: snapshot)
218+
log("\n=== Test: Exact Full Withdraw Clears Collateral Type Constraint ===\n")
219+
220+
let user = Test.createAccount()
221+
setupMoetVault(user, beFailed: false)
222+
setupDummyTokenVault(user)
223+
transferFlowTokens(to: user, amount: 2_000.0)
224+
mintDummyToken(to: user, amount: 2_000.0)
225+
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user)
226+
227+
let createPosRes = executeTransaction(
228+
"../transactions/flow-alp/position/create_position.cdc",
229+
[1_000.0, FLOW_VAULT_STORAGE_PATH, false],
230+
user
231+
)
232+
Test.expect(createPosRes, Test.beSucceeded())
233+
234+
let pid: UInt64 = 0
235+
236+
// Withdraw collateral exactly to zero.
237+
withdrawFromPosition(
238+
signer: user,
239+
positionId: pid,
240+
tokenTypeIdentifier: FLOW_TOKEN_IDENTIFIER,
241+
amount: 1_000.0,
242+
pullFromTopUpSource: false
243+
)
244+
245+
// If exact full withdraw leaves a phantom FLOW collateral type, this deposit would fail.
246+
depositToPosition(
247+
signer: user,
248+
positionID: pid,
249+
amount: 500.0,
250+
vaultStoragePath: DummyToken.VaultStoragePath,
251+
pushToDrawDownSink: false
252+
)
253+
254+
let details = getPositionDetails(pid: pid, beFailed: false)
255+
let flowCredit = getCreditBalanceForType(details: details, vaultType: CompositeType(FLOW_TOKEN_IDENTIFIER)!)
256+
let dummyCredit = getCreditBalanceForType(details: details, vaultType: CompositeType(DUMMY_TOKEN_IDENTIFIER)!)
257+
Test.assert(flowCredit == 0.0, message: "FLOW collateral should be zero after full withdrawal")
258+
Test.assert(dummyCredit >= 500.0 - 0.01, message: "Dummy collateral should be ~500 after deposit")
259+
260+
log("\n=== Test Complete: Exact Full Withdraw Clears Collateral Type Constraint ===")
261+
}
262+
144263
// Helper functions
145264

146265
access(all)

0 commit comments

Comments
 (0)