-
Notifications
You must be signed in to change notification settings - Fork 2
Multi position per user scenarios testing #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mts1715
merged 21 commits into
main
from
taras/147-multi-position-per-user-scenarios-testing
Mar 10, 2026
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
9036283
Add mainnet fork test for multiple positions per user and batch liqui…
mts1715 4c2bf16
Add testMassUnhealthyLiquidations — a system-wide stress test that cr…
mts1715 bf31254
Merge remote-tracking branch 'origin/main' into taras/147-multi-posit…
mts1715 441c58e
fixes after merge
mts1715 86086fa
code style fix
mts1715 526704a
constant naming fix
mts1715 13c50e5
moved helper methods to test_helpers.cdc
mts1715 e7216bd
Update cadence/tests/fork_multiple_positions_per_user.cdc
mts1715 d064bc7
fix naming *_test.cdc, move not production script to test dir
mts1715 887ae21
flow.json: fix block height
mts1715 0df9e00
fix block height in flow.json, add actions/cache for ./imports keyed …
mts1715 c9cc894
add more description comments to test
mts1715 0e5577c
move scripts only for test to "cadence/test/transactions"
mts1715 5bc3cf1
remove useless transaction from test
mts1715 8fd216c
Merge branch 'main' into taras/147-multi-position-per-user-scenarios-…
mts1715 e5e5824
Merge branch 'main' into taras/147-multi-position-per-user-scenarios-…
mts1715 d7cefa5
Merge branch 'main' into taras/147-multi-position-per-user-scenarios-…
Kay-Zee d98a0aa
fix borrow asset to MOET instead of FLOW, fix liqudate transactions t…
mts1715 828600a
use constants instead of magic numbers in fork_multiple_positions_per…
mts1715 6e9d39c
Merge remote-tracking branch 'origin/main' into taras/147-multi-posit…
mts1715 d1ff732
Merge branch 'main' into taras/147-multi-position-per-user-scenarios-…
mts1715 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
987 changes: 987 additions & 0 deletions
987
cadence/tests/fork_multiple_positions_per_user_test.cdc
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import "MockOracle" | ||
mts1715 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| access(all) fun main(tokenIdentifier: String): UFix64? { | ||
| let tokenType = CompositeType(tokenIdentifier) | ||
| ?? panic("Invalid token identifier: ".concat(tokenIdentifier)) | ||
|
|
||
| let oracle = MockOracle.PriceOracle() | ||
| return oracle.price(ofToken: tokenType) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
cadence/tests/transactions/flow-alp/pool-management/batch_liquidate_via_mock_dex.cdc
mts1715 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import "FungibleToken" | ||
| import "FungibleTokenMetadataViews" | ||
| import "MetadataViews" | ||
|
|
||
| import "FlowALPv0" | ||
| import "MockDexSwapper" | ||
|
|
||
| /// TEST-ONLY: Batch liquidate multiple positions using the stored MockDexSwapper as the debt | ||
| /// repayment source. The swapper's vaultSource (configured via setMockDexPriceForPair) withdraws | ||
| /// the required debt tokens, so the transaction signer needs no debt tokens upfront. | ||
| /// | ||
| /// Positions are liquidated in the order provided (caller is responsible for ordering by priority). | ||
| /// | ||
| /// pids: Array of position IDs to liquidate | ||
| /// debtVaultIdentifier: e.g., Type<@FlowToken.Vault>().identifier | ||
| /// seizeVaultIdentifiers: Array of collateral vault identifiers to seize (one per position) | ||
| /// seizeAmounts: Array of collateral amounts to seize from each position | ||
| /// repayAmounts: Array of debt amounts to repay for each position (sourced from the DEX) | ||
| transaction( | ||
| pids: [UInt64], | ||
| repayVaultIdentifier: String, | ||
| seizeVaultIdentifiers: [String], | ||
| seizeAmounts: [UFix64], | ||
| repayAmounts: [UFix64] | ||
mts1715 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ) { | ||
| let pool: &FlowALPv0.Pool | ||
| let debtType: Type | ||
| let signerAccount: auth(BorrowValue) &Account | ||
|
|
||
| prepare(signer: auth(BorrowValue) &Account) { | ||
| let protocolAddress = Type<@FlowALPv0.Pool>().address! | ||
| self.pool = getAccount(protocolAddress).capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) | ||
| ?? panic("Could not borrow Pool at \(FlowALPv0.PoolPublicPath)") | ||
|
|
||
| self.debtType = CompositeType(repayVaultIdentifier) | ||
| ?? panic("Invalid debtVaultIdentifier: \(repayVaultIdentifier)") | ||
|
|
||
| self.signerAccount = signer | ||
| } | ||
|
|
||
| execute { | ||
| let numPositions = pids.length | ||
| assert(seizeVaultIdentifiers.length == numPositions, message: "seizeVaultIdentifiers length mismatch") | ||
| assert(seizeAmounts.length == numPositions, message: "seizeAmounts length mismatch") | ||
| assert(repayAmounts.length == numPositions, message: "repayAmounts length mismatch") | ||
|
|
||
| var totalRepaid = 0.0 | ||
|
|
||
| for idx in InclusiveRange(0, numPositions - 1) { | ||
| let pid = pids[idx] | ||
| let seizeVaultIdentifier = seizeVaultIdentifiers[idx] | ||
| let seizeAmount = seizeAmounts[idx] | ||
| let repayAmount = repayAmounts[idx] | ||
|
|
||
| let seizeType = CompositeType(seizeVaultIdentifier) | ||
| ?? panic("Invalid seizeVaultIdentifier: \(seizeVaultIdentifier)") | ||
|
|
||
| // Retrieve the stored MockDexSwapper for this collateral → debt pair. | ||
| // The swapper's vaultSource (protocolAccount's vault) provides the debt tokens. | ||
| let swapper = MockDexSwapper.getSwapper(inType: seizeType, outType: self.debtType) | ||
| ?? panic("No MockDexSwapper configured for \(seizeVaultIdentifier) -> \(repayVaultIdentifier)") | ||
|
|
||
| // Build an exact quote for the repayAmount we need from the swapper's vaultSource | ||
| let swapQuote = MockDexSwapper.BasicQuote( | ||
| inType: seizeType, | ||
mts1715 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| outType: self.debtType, | ||
| inAmount: 0.0, | ||
| outAmount: repayAmount | ||
| ) | ||
|
|
||
| // Create an empty collateral vault as a dummy swap input — MockDexSwapper burns it | ||
| // and withdraws repayAmount debt tokens from its configured vaultSource instead. | ||
| let seizeVaultData = MetadataViews.resolveContractViewFromTypeIdentifier( | ||
| resourceTypeIdentifier: seizeVaultIdentifier, | ||
| viewType: Type<FungibleTokenMetadataViews.FTVaultData>() | ||
| ) as? FungibleTokenMetadataViews.FTVaultData | ||
| ?? panic("Could not resolve FTVaultData for \(seizeVaultIdentifier)") | ||
| let emptyCollateralVault <- seizeVaultData.createEmptyVault() | ||
|
|
||
| // Swap: burns emptyCollateralVault, withdraws repayAmount from vaultSource | ||
| let repayVault <- swapper.swap(quote: swapQuote, inVault: <-emptyCollateralVault) | ||
|
|
||
| // Execute the liquidation: pool seizes collateral, caller provides repayment | ||
| let seizedVault <- self.pool.manualLiquidation( | ||
| pid: pid, | ||
| debtType: self.debtType, | ||
| seizeType: seizeType, | ||
| seizeAmount: seizeAmount, | ||
| repayment: <-repayVault | ||
| ) | ||
|
|
||
| totalRepaid = totalRepaid + repayAmount | ||
|
|
||
| // Deposit seized collateral back to liquidator | ||
| let liquidatorVault = self.signerAccount.storage.borrow<&{FungibleToken.Vault}>(from: seizeVaultData.storagePath) | ||
| ?? panic("No vault at \(seizeVaultData.storagePath) to deposit seized collateral") | ||
| liquidatorVault.deposit(from: <-seizedVault) | ||
| } | ||
|
|
||
| log("Batch DEX liquidation completed: \(numPositions) positions, total repaid: \(totalRepaid)") | ||
| } | ||
| } | ||
90 changes: 90 additions & 0 deletions
90
cadence/tests/transactions/flow-alp/pool-management/batch_manual_liquidation.cdc
holyfuchs marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import "FungibleToken" | ||
| import "FungibleTokenMetadataViews" | ||
| import "MetadataViews" | ||
|
|
||
| import "FlowALPv0" | ||
|
|
||
| /// Batch liquidate multiple positions in a single transaction | ||
| /// | ||
| /// pids: Array of position IDs to liquidate | ||
| /// repaymentVaultIdentifier: e.g., Type<@FlowToken.Vault>().identifier | ||
| /// seizeVaultIdentifiers: Array of collateral vault identifiers to seize | ||
| /// seizeAmounts: Array of max seize amounts for each position | ||
| /// repayAmounts: Array of repay amounts for each position | ||
| transaction( | ||
| pids: [UInt64], | ||
| repaymentVaultIdentifier: String, | ||
| seizeVaultIdentifiers: [String], | ||
| seizeAmounts: [UFix64], | ||
| repayAmounts: [UFix64] | ||
| ) { | ||
| let pool: &FlowALPv0.Pool | ||
| let repaymentType: Type | ||
| let repaymentVaultRef: auth(FungibleToken.Withdraw) &{FungibleToken.Vault} | ||
| let signerAccount: auth(BorrowValue) &Account | ||
|
|
||
| prepare(signer: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account) { | ||
| self.signerAccount = signer | ||
|
|
||
| let protocolAddress = Type<@FlowALPv0.Pool>().address! | ||
| self.pool = getAccount(protocolAddress).capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) | ||
| ?? panic("Could not borrow Pool at \(FlowALPv0.PoolPublicPath)") | ||
|
|
||
| self.repaymentType = CompositeType(repaymentVaultIdentifier) ?? panic("Invalid repaymentVaultIdentifier: \(repaymentVaultIdentifier)") | ||
|
|
||
| let repaymentVaultData = MetadataViews.resolveContractViewFromTypeIdentifier( | ||
| resourceTypeIdentifier: repaymentVaultIdentifier, | ||
| viewType: Type<FungibleTokenMetadataViews.FTVaultData>() | ||
| ) as? FungibleTokenMetadataViews.FTVaultData | ||
| ?? panic("Could not construct valid FT type and view from identifier \(repaymentVaultIdentifier)") | ||
|
|
||
| self.repaymentVaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(from: repaymentVaultData.storagePath) | ||
| ?? panic("no repayment vault in storage at path \(repaymentVaultData.storagePath)") | ||
| } | ||
|
|
||
| execute { | ||
| let numPositions = pids.length | ||
| assert(seizeVaultIdentifiers.length == numPositions, message: "seizeVaultIdentifiers length mismatch") | ||
| assert(seizeAmounts.length == numPositions, message: "seizeAmounts length mismatch") | ||
| assert(repayAmounts.length == numPositions, message: "repayAmounts length mismatch") | ||
|
|
||
| var totalRepaid = 0.0 | ||
|
|
||
| for i in InclusiveRange(0, numPositions - 1) { | ||
| let pid = pids[i] | ||
| let seizeVaultIdentifier = seizeVaultIdentifiers[i] | ||
| let seizeAmount = seizeAmounts[i] | ||
| let repayAmount = repayAmounts[i] | ||
|
|
||
| let seizeType = CompositeType(seizeVaultIdentifier) | ||
| ?? panic("Invalid seizeVaultIdentifier: \(seizeVaultIdentifier)") | ||
|
|
||
| assert(self.repaymentVaultRef.balance >= repayAmount, | ||
| message: "Insufficient repayment token balance for position \(pid)") | ||
|
|
||
| let repay <- self.repaymentVaultRef.withdraw(amount: repayAmount) | ||
|
|
||
| let seizedVault <- self.pool.manualLiquidation( | ||
| pid: pid, | ||
| debtType: self.repaymentType, | ||
| seizeType: seizeType, | ||
| seizeAmount: seizeAmount, | ||
| repayment: <-repay | ||
| ) | ||
|
|
||
| totalRepaid = totalRepaid + repayAmount | ||
|
|
||
| // Deposit seized collateral back to liquidator | ||
| let seizeVaultData = MetadataViews.resolveContractViewFromTypeIdentifier( | ||
| resourceTypeIdentifier: seizeVaultIdentifier, | ||
| viewType: Type<FungibleTokenMetadataViews.FTVaultData>() | ||
| ) as? FungibleTokenMetadataViews.FTVaultData | ||
| ?? panic("Could not resolve FTVaultData for \(seizeVaultIdentifier)") | ||
| let liquidatorVault = self.signerAccount.storage.borrow<&{FungibleToken.Vault}>(from: seizeVaultData.storagePath) | ||
| ?? panic("No vault at \(seizeVaultData.storagePath) to deposit seized collateral") | ||
| liquidatorVault.deposit(from: <-seizedVault) | ||
| } | ||
|
|
||
| log("Batch liquidation completed: \(numPositions) positions, total repaid: \(totalRepaid)") | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.