Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions cadence/contracts/FlowCreditMarket.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,8 @@ access(all) contract FlowCreditMarket {

/// Funds that have been deposited but must be asynchronously added to the Pool's reserves and recorded
access(mapping ImplementationUpdates) var queuedDeposits: @{Type: {FungibleToken.Vault}}

/// Non-resource tracking of queued deposit amounts (for script-accessible queries)
access(mapping ImplementationUpdates) var queuedDepositAmounts: {Type: UFix64}
/// A DeFiActions Sink that if non-nil will enable the Pool to push overflown value automatically when the
/// position exceeds its maximum health based on the value of deposited collateral versus withdrawals
access(mapping ImplementationUpdates) var drawDownSink: {DeFiActions.Sink}?
Expand All @@ -426,6 +427,7 @@ access(all) contract FlowCreditMarket {
init() {
self.balances = {}
self.queuedDeposits <- {}
self.queuedDepositAmounts = {}
self.targetHealth = 1.3
self.minHealth = 1.1
self.maxHealth = 1.5
Expand Down Expand Up @@ -1436,6 +1438,31 @@ access(all) contract FlowCreditMarket {
)
}

/// Returns the queued deposit balances for a given position
access(all) fun getQueuedDeposits(pid: UInt64): {Type: UFix64} {
let position = self._borrowPosition(pid: pid)
let result: {Type: UFix64} = {}
for depositType in position.queuedDepositAmounts.keys {
result[depositType] = position.queuedDepositAmounts[depositType]!
}
return result
}

/// Rebuilds queued deposit amounts for a given position from queued deposits
access(EImplementation) fun syncQueuedDepositAmounts(pid: UInt64) {
let position = self._borrowPosition(pid: pid)

for existingType in position.queuedDepositAmounts.keys {
position.queuedDepositAmounts.remove(key: existingType)
}

for depositType in position.queuedDeposits.keys {
let queuedVault <- position.queuedDeposits.remove(key: depositType)!
position.queuedDepositAmounts[depositType] = queuedVault.balance
position.queuedDeposits[depositType] <-! queuedVault
}
}

/// Quote liquidation required repay and seize amounts to bring HF to liquidationTargetHF
/// using a single seizeType
access(all) fun quoteLiquidation(pid: UInt64, debtType: Type, seizeType: Type): FlowCreditMarket.LiquidationQuote {
Expand Down Expand Up @@ -2694,12 +2721,17 @@ access(all) contract FlowCreditMarket {

if depositAmount > depositLimit {
// The deposit is too big, so we need to queue the excess
let queuedDeposit <- from.withdraw(amount: depositAmount - depositLimit)
let queuedAmount = depositAmount - depositLimit
let queuedDeposit <- from.withdraw(amount: queuedAmount)

if position.queuedDeposits[type] == nil {
position.queuedDeposits[type] <-! queuedDeposit
position.queuedDepositAmounts[type] = queuedAmount
} else {
position.queuedDeposits[type]!.deposit(from: <-queuedDeposit)
let existingQueued <- position.queuedDeposits.remove(key: type)!
existingQueued.deposit(from: <-queuedDeposit)
position.queuedDepositAmounts[type] = existingQueued.balance
position.queuedDeposits[type] <-! existingQueued
}
}

Expand Down Expand Up @@ -3358,6 +3390,8 @@ access(all) contract FlowCreditMarket {
from: <-queuedVault,
pushToDrawDownSink: false
)
// Remove tracking since queue is now empty for this type
position.queuedDepositAmounts.remove(key: depositType)
} else {
// We can only deposit part of the queued deposit, so do that and leave the rest in the queue
// for the next time we run.
Expand All @@ -3369,6 +3403,7 @@ access(all) contract FlowCreditMarket {
)

// We need to update the queued vault to reflect the amount we used up
position.queuedDepositAmounts[depositType] = queuedVault.balance
position.queuedDeposits[depositType] <-! queuedVault
}
}
Expand Down
14 changes: 14 additions & 0 deletions cadence/scripts/flow-credit-market/get_queued_deposits.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "FlowCreditMarket"

/// Returns the queued deposit balances for a given position id
///
/// @param pid: The Position ID
/// @return A dictionary mapping token types to their queued deposit amounts
///
access(all)
fun main(pid: UInt64): {Type: UFix64} {
let protocolAddress = Type<@FlowCreditMarket.Pool>().address!
return getAccount(protocolAddress).capabilities.borrow<&FlowCreditMarket.Pool>(FlowCreditMarket.PoolPublicPath)
?.getQueuedDeposits(pid: pid)
?? panic("Could not find a configured FlowCreditMarket Pool in account \(protocolAddress) at path \(FlowCreditMarket.PoolPublicPath)")
}
72 changes: 72 additions & 0 deletions cadence/tests/queued_deposits_integration_test.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Test

import "MOET"
import "test_helpers.cdc"

access(all) let protocolAccount = Test.getAccount(0x0000000000000007)

access(all)
fun setup() {
deployContracts()
}

access(all)
fun test_queued_deposits_script_tracks_async_updates() {
createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false)

let user = Test.createAccount()
setupMoetVault(user, beFailed: false)
mintMoet(signer: protocolAccount, to: user.address, amount: 1_000.0, beFailed: false)

grantPoolCapToConsumer()

let openRes = _executeTransaction(
"./transactions/mock-flow-credit-market-consumer/create_wrapped_position.cdc",
[50.0, MOET.VaultStoragePath, false],
user
)
Test.expect(openRes, Test.beSucceeded())

let setFracRes = _executeTransaction(
"../transactions/flow-credit-market/pool-governance/set_deposit_limit_fraction.cdc",
[defaultTokenIdentifier, 0.0001],
protocolAccount
)
Test.expect(setFracRes, Test.beSucceeded())

let depositRes = _executeTransaction(
"./transactions/mock-flow-credit-market-consumer/deposit_to_wrapped_position.cdc",
[250.0, MOET.VaultStoragePath, false],
user
)
Test.expect(depositRes, Test.beSucceeded())

let queuedAfterDeposit = getQueuedDeposits(pid: 0, beFailed: false)
Test.assert(queuedAfterDeposit.length == 1)
let queuedAmount = queuedAfterDeposit[Type<@MOET.Vault>()]
?? panic("Missing queued deposit entry for MOET")
Test.assert(ufixEqualWithinVariance(150.0, queuedAmount))

let asyncRes = _executeTransaction(
"./transactions/flow-credit-market/pool-management/async_update_position.cdc",
[UInt64(0)],
protocolAccount
)
Test.expect(asyncRes, Test.beSucceeded())

let queuedAfterPartial = getQueuedDeposits(pid: 0, beFailed: false)
Test.assert(queuedAfterPartial.length == 1)
let queuedPartialAmount = queuedAfterPartial[Type<@MOET.Vault>()]
?? panic("Missing queued deposit entry after partial update")
Test.assert(ufixEqualWithinVariance(50.0, queuedPartialAmount))

let asyncRes2 = _executeTransaction(
"./transactions/flow-credit-market/pool-management/async_update_position.cdc",
[UInt64(0)],
protocolAccount
)
Test.expect(asyncRes2, Test.beSucceeded())

let queuedAfterFull = getQueuedDeposits(pid: 0, beFailed: false)
Test.assert(queuedAfterFull.length == 0)
}
9 changes: 9 additions & 0 deletions cadence/tests/test_helpers.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ fun getPositionDetails(pid: UInt64, beFailed: Bool): FlowCreditMarket.PositionDe
return res.returnValue as! FlowCreditMarket.PositionDetails
}

access(all)
fun getQueuedDeposits(pid: UInt64, beFailed: Bool): {Type: UFix64} {
let res = _executeScript("../scripts/flow-credit-market/get_queued_deposits.cdc",
[pid]
)
Test.expect(res, beFailed ? Test.beFailed() : Test.beSucceeded())
return res.returnValue as! {Type: UFix64}
}

access(all)
fun poolExists(address: Address): Bool {
let res = _executeScript("../scripts/flow-credit-market/pool_exists.cdc", [address])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "FlowCreditMarket"

/// TEST TRANSACTION - DO NOT USE IN PRODUCTION
///
/// Runs a single async update for the provided position ID.
transaction(pid: UInt64) {
let pool: auth(FlowCreditMarket.EImplementation) &FlowCreditMarket.Pool

prepare(signer: auth(BorrowValue) &Account) {
self.pool = signer.storage.borrow<auth(FlowCreditMarket.EImplementation) &FlowCreditMarket.Pool>(
from: FlowCreditMarket.PoolStoragePath
) ?? panic("Could not borrow Pool at \(FlowCreditMarket.PoolStoragePath)")
}

execute {
self.pool.asyncUpdatePosition(pid: pid)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import "FlowCreditMarket"

/// Rebuilds queued deposit amounts for a given position ID.
transaction(pid: UInt64) {
let pool: auth(FlowCreditMarket.EImplementation) &FlowCreditMarket.Pool

prepare(signer: auth(BorrowValue) &Account) {
self.pool = signer.storage.borrow<auth(FlowCreditMarket.EImplementation) &FlowCreditMarket.Pool>(
from: FlowCreditMarket.PoolStoragePath
) ?? panic("Could not borrow Pool at \(FlowCreditMarket.PoolStoragePath)")
}

execute {
self.pool.syncQueuedDepositAmounts(pid: pid)
}
}
Loading