diff --git a/cadence/tests/simulation_oct_10_2025_cascade.cdc b/cadence/tests/simulation_oct_10_2025_cascade.cdc new file mode 100644 index 00000000..5dcd107d --- /dev/null +++ b/cadence/tests/simulation_oct_10_2025_cascade.cdc @@ -0,0 +1,469 @@ +#test_fork(network: "mainnet-fork", height: 143292255) + +import Test +import BlockchainHelpers + +import "test_helpers.cdc" +import "evm_state_helpers.cdc" +import "simulation_oct_10_2025_cascade_helpers.cdc" + +import "FlowYieldVaults" +import "FlowToken" +import "MOET" +import "FlowYieldVaultsStrategiesV2" +import "FlowALPv0" +import "DeFiActions" + + +// ============================================================================ +// CADENCE ACCOUNTS +// ============================================================================ + +access(all) let flowYieldVaultsAccount = Test.getAccount(0xb1d63873c3cc9f79) +access(all) let flowALPAccount = Test.getAccount(0x6b00ff876c299c61) +access(all) let bandOracleAccount = Test.getAccount(0x6801a6222ebf784a) +access(all) let whaleFlowAccount = Test.getAccount(0x92674150c9213fc9) +access(all) let coaOwnerAccount = Test.getAccount(0xe467b9dd11fa00df) + +// WBTC on Flow EVM: 717dae2baf7656be9a9b01dee31d571a9d4c9579 +access(all) let WBTC_TOKEN_ID = "A.1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault" +access(all) let WBTC_TYPE = CompositeType(WBTC_TOKEN_ID)! + +// 0x01b7e73CDAd95D407e8696E04194a75F19744801 + +access(all) var strategyIdentifier = Type<@FlowYieldVaultsStrategiesV2.FUSDEVStrategy>().identifier +access(all) var wbtcTokenIdentifier = WBTC_TOKEN_ID + +// ============================================================================ +// PROTOCOL ADDRESSES +// ============================================================================ + +access(all) let factoryAddress = "0xca6d7Bb03334bBf135902e1d919a5feccb461632" + +// ============================================================================ +// VAULT & TOKEN ADDRESSES +// ============================================================================ + +access(all) let morphoVaultAddress = "0xd069d989e2F44B70c65347d1853C0c67e10a9F8D" +access(all) let pyusd0Address = "0x99aF3EeA856556646C98c8B9b2548Fe815240750" +access(all) let moetAddress = "0x213979bB8A9A86966999b3AA797C1fcf3B967ae2" +access(all) let wbtcAddress = "0x717dae2baf7656be9a9b01dee31d571a9d4c9579" + +// ============================================================================ +// STORAGE SLOT CONSTANTS +// ============================================================================ + +access(all) let moetBalanceSlot = 0 as UInt256 +access(all) let pyusd0BalanceSlot = 1 as UInt256 +access(all) let fusdevBalanceSlot = 12 as UInt256 +access(all) let wbtcBalanceSlot = 5 as UInt256 + +access(all) let morphoVaultTotalSupplySlot = 11 as UInt256 +access(all) let morphoVaultTotalAssetsSlot = 15 as UInt256 + +// ============================================================================ +// SIMULATION CONSTANTS +// ============================================================================ + +access(all) let numAgents = 1 + +access(all) let fundingPerAgent = 1.0 + +access(all) let initialPrice = btc_oct_2025_prices[0] + +// Collateral factor for BTC in the FlowALP pool. +// This determines how much of the collateral value counts toward borrowing capacity. +// effectiveCollateral = collateralValue * collateralFactor +// effectiveHF = effectiveCollateral / debt +// See: lib/FlowALP/cadence/contracts/FlowALPv0.cdc:544-546 (InternalPosition.init defaults) +access(all) let collateralFactor = 0.8 + +// ============================================================================ +// SETUP +// ============================================================================ + +access(all) +fun setup() { + deployContractsForFork() + + // it removes all the existing scheduled transactions, it is useful to speed up the tests, + // because otherwise, the existing 41 scheduled transactions will be executed every time we + // move the time forward, and each taking about 40ms, which make the tests very slow + // resetting the transaction scheduler will not affect the test results, new scheduled + // transaction can also be created by the tests. + resetTransactionScheduler() + + setInfiniteLiquidity() + + let reserveAmount = 100_000_00.0 + transferFlow(signer: whaleFlowAccount, recipient: flowALPAccount.address, amount: reserveAmount) + mintMoet(signer: flowALPAccount, to: flowALPAccount.address, amount: reserveAmount, beFailed: false) + + transferFlow(signer: whaleFlowAccount, recipient: flowYieldVaultsAccount.address, amount: reserveAmount) + transferFlow(signer: whaleFlowAccount, recipient: coaOwnerAccount.address, amount: reserveAmount) +} + +// ============================================================================ +// HELPERS +// ============================================================================ + +access(all) +fun setInfiniteLiquidity() { + setPoolToPrice( + factoryAddress: factoryAddress, + tokenAAddress: pyusd0Address, + tokenBAddress: morphoVaultAddress, + fee: 100, + priceTokenBPerTokenA: 1.0, + tokenABalanceSlot: pyusd0BalanceSlot, + tokenBBalanceSlot: fusdevBalanceSlot, + signer: coaOwnerAccount + ) + + setPoolToPrice( + factoryAddress: factoryAddress, + tokenAAddress: moetAddress, + tokenBAddress: morphoVaultAddress, + fee: 100, + priceTokenBPerTokenA: 1.0, + tokenABalanceSlot: moetBalanceSlot, + tokenBBalanceSlot: fusdevBalanceSlot, + signer: coaOwnerAccount + ) + + setPoolToPrice( + factoryAddress: factoryAddress, + tokenAAddress: moetAddress, + tokenBAddress: pyusd0Address, + fee: 100, + priceTokenBPerTokenA: 1.0, + tokenABalanceSlot: moetBalanceSlot, + tokenBBalanceSlot: pyusd0BalanceSlot, + signer: coaOwnerAccount + ) +} + +access(all) fun getBTCCollateralFromPosition(pid: UInt64): UFix64 { + let positionDetails = getPositionDetails(pid: pid, beFailed: false) + for balance in positionDetails.balances { + if balance.vaultType == WBTC_TYPE { + if balance.direction == FlowALPv0.BalanceDirection.Credit { + return balance.balance + } + } + } + return 0.0 +} + +/// Compute deterministic YT (ERC4626 vault share) price at a given minute. +/// price = 1.0 + yieldAPR * (minute / 365 / 24 / 60) +/// basically negligible yield +access(all) fun ytPriceAtMinute(_ minute: Int): UFix64 { + return 1.0 + btc_oct_2025_constants.yieldAPR * (UFix64(minute) / 365.0 / 24.0 / 60.0) +} + +/// Update all prices for a given simulation minute. +access(all) fun applyPriceTick(btcPrice: UFix64, ytPrice: UFix64, user: Test.TestAccount) { + setBandOraclePrices(signer: bandOracleAccount, symbolPrices: { + "BTC": btcPrice, + "USD": 1.0 + }) + + let btcPool = btc_oct_2025_pools["pyusd_btc"]! + let ytPool = btc_oct_2025_pools["moet_fusdev"]! + + setPoolToPriceWithTVL( + factoryAddress: factoryAddress, + tokenAAddress: wbtcAddress, + tokenBAddress: pyusd0Address, + fee: 3000, + priceTokenBPerTokenA: UFix128(btcPrice), + tokenABalanceSlot: wbtcBalanceSlot, + tokenBBalanceSlot: pyusd0BalanceSlot, + tvl: btcPool.size, + concentration: btcPool.concentration, + tokenBPriceUSD: 1.0, + signer: coaOwnerAccount + ) + + setPoolToPriceWithTVL( + factoryAddress: factoryAddress, + tokenAAddress: moetAddress, + tokenBAddress: morphoVaultAddress, + fee: 100, + priceTokenBPerTokenA: UFix128(ytPrice), + tokenABalanceSlot: moetBalanceSlot, + tokenBBalanceSlot: fusdevBalanceSlot, + tvl: ytPool.size, + concentration: ytPool.concentration, + tokenBPriceUSD: 1.0, + signer: coaOwnerAccount + ) + + setVaultSharePrice( + vaultAddress: morphoVaultAddress, + assetAddress: pyusd0Address, + assetBalanceSlot: pyusd0BalanceSlot, + totalSupplySlot: morphoVaultTotalSupplySlot, + vaultTotalAssetsSlot: morphoVaultTotalAssetsSlot, + priceMultiplier: ytPrice, + signer: user + ) +} + +// ============================================================================ +// TEST: BTC Oct 10 2025 Cascade -- Minute rebalancing over one day with real prices +// ============================================================================ + +access(all) +fun test_Btc_Oct_10_2025_Cascade() { + let prices = btc_oct_2025_prices + + // Create agents + let users: [Test.TestAccount] = [] + let pids: [UInt64] = [] + let vaultIds: [UInt64] = [] + + // Apply initial pricing + applyPriceTick(btcPrice: initialPrice, ytPrice: ytPriceAtMinute(0), user: coaOwnerAccount) + + var i = 0 + while i < numAgents { + let user = Test.createAccount() + transferFlow(signer: whaleFlowAccount, recipient: user.address, amount: 10.0) + mintBTC(signer: user, amount: fundingPerAgent) + grantBeta(flowYieldVaultsAccount, user) + + setVaultSharePrice( + vaultAddress: morphoVaultAddress, + assetAddress: pyusd0Address, + assetBalanceSlot: pyusd0BalanceSlot, + totalSupplySlot: morphoVaultTotalSupplySlot, + vaultTotalAssetsSlot: morphoVaultTotalAssetsSlot, + priceMultiplier: 1.0, + signer: user + ) + + createYieldVault( + signer: user, + strategyIdentifier: strategyIdentifier, + vaultIdentifier: wbtcTokenIdentifier, + amount: fundingPerAgent, + beFailed: false + ) + + let pid = (getLastPositionOpenedEvent(Test.eventsOfType(Type())) as! FlowALPv0.Opened).pid + let yieldVaultIDs = getYieldVaultIDs(address: user.address)! + let vaultId = yieldVaultIDs[0] + + let agent = btc_oct_2025_agents[i] + + // forced initial rebalance needs infinite liquidity + setInfiniteLiquidity() + + // Step 1: Coerce position to the desired initial HF. + // Set temporary health params with targetHealth=initialHF, then force-rebalance. + // This makes the on-chain rebalancer push the position to exactly initialHF. + setPositionHealthParams( + signer: flowALPAccount, + pid: pid, + targetHealth: agent.initialHF, + minHealth: agent.initialHF - 0.01, + maxHealth: agent.initialHF + 0.01 + ) + rebalancePosition(signer: flowALPAccount, pid: pid, force: true, beFailed: false) + + // Step 2: Set the real health thresholds for the simulation. + setPositionHealthParams( + signer: flowALPAccount, + pid: pid, + targetHealth: agent.targetHF, + minHealth: agent.rebalancingHF, + maxHealth: agent.initialHF + ) + + users.append(user) + pids.append(pid) + vaultIds.append(vaultId) + + log(" Agent \(i): pid=\(pid) vaultId=\(vaultId)") + i = i + 1 + } + + log("\n=== BTC OCT 10 2025 CASCADE SIMULATION ===") + log("Agents: \(numAgents)") + log("Funding per agent: \(fundingPerAgent) BTC (~\(fundingPerAgent * initialPrice) MOET)") + log("Price points: \(prices.length)") + log("Initial BTC price: $\(prices[0])") + log("") + log("Rebalance Triggers:") + log(" HF (Position): triggers when HF < \(btc_oct_2025_agents[0].rebalancingHF) or HF > \(btc_oct_2025_agents[0].initialHF), rebalances to HF = \(btc_oct_2025_agents[0].targetHF)") + log(" VR (Vault): triggers when VR < 0.95 or VR > 1.05, rebalances to VR ~ 1.0") + + var liquidationCount = 0 + var previousBTCPrice = initialPrice + var lowestPrice = initialPrice + var highestPrice = initialPrice + var lowestHF = 100.0 + var prevVaultRebalanceCount = 0 + var prevPositionRebalanceCount = 0 + + let startTimestamp = getCurrentBlockTimestamp() + + var minute = 0 + while minute < prices.length { + let absolutePrice = prices[minute] + let ytPrice = ytPriceAtMinute(minute) + + if absolutePrice < lowestPrice { + lowestPrice = absolutePrice + } + if absolutePrice > highestPrice { + highestPrice = absolutePrice + } + + // Advance blockchain time by 1 minute per step + let expectedTimestamp = startTimestamp + UFix64(minute) * 60.0 + let currentTimestamp = getCurrentBlockTimestamp() + if expectedTimestamp > currentTimestamp { + Test.moveTime(by: Fix64(expectedTimestamp - currentTimestamp)) + } + + // Apply all price updates + applyPriceTick(btcPrice: absolutePrice, ytPrice: ytPrice, user: users[0]) + + // Calculate HF BEFORE rebalancing to see pre-rebalance state + // effectiveHF = (collateralValue * collateralFactor) / debt + // This is what determines rebalance triggers (minHealth=1.1, maxHealth=1.5) + var preRebalanceHFs: [UFix64] = [] + // Calculate vault ratio BEFORE rebalancing (single script call for efficiency) + // vaultRatio = currentValue / valueOfDeposits + // Triggers when ratio < 0.95 or ratio > 1.05 + var preVaultRatios: [UFix64] = [] + var a = 0 + while a < numAgents { + // Calculate HF BEFORE rebalancing + var preRebalanceHF: UFix64 = UFix64(getPositionHealth(pid: pids[a], beFailed: false)) + preRebalanceHFs.append(preRebalanceHF) + var preVaultRatio: UFix64 = 1.0 + let preMetrics = getAutoBalancerMetrics(id: vaultIds[a]) ?? [0.0, 0.0] + if preMetrics[1] > 0.0 { + preVaultRatio = preMetrics[0] / preMetrics[1] + } + preVaultRatios.append(preVaultRatio) + + a = a + 1 + } + + // Potentially rebalance all agents (not forced) + a = 0 + while a < numAgents { + rebalanceYieldVault(signer: flowYieldVaultsAccount, id: vaultIds[a], force: false, beFailed: false) + rebalancePosition(signer: flowALPAccount, pid: pids[a], force: false, beFailed: false) + a = a + 1 + } + + // Count actual rebalances that occurred + let currentVaultRebalanceCount = Test.eventsOfType(Type()).length + let currentPositionRebalanceCount = Test.eventsOfType(Type()).length + + // Calculate vault ratio AFTER rebalancing (single script call for efficiency) + var postVaultRatio: UFix64 = 1.0 + let postMetrics = getAutoBalancerMetrics(id: vaultIds[0]) ?? [0.0, 0.0] + if postMetrics[1] > 0.0 { + postVaultRatio = postMetrics[0] / postMetrics[1] + } + + // Calculate HF AFTER rebalancing + a = 0 + while a < numAgents { + // Calculate HF AFTER rebalancing + let postRebalanceHF = UFix64(getPositionHealth(pid: pids[a], beFailed: false)) + let preRebalanceHF = preRebalanceHFs[a] + let preVaultRatio = preVaultRatios[a] + // Calculate vault ratio AFTER rebalancing (single script call for efficiency) + var postVaultRatio: UFix64 = 1.0 + let postMetrics = getAutoBalancerMetrics(id: vaultIds[0]) ?? [0.0, 0.0] + if postMetrics[1] > 0.0 { + postVaultRatio = postMetrics[0] / postMetrics[1] + } + // Track lowest HF (use pre-rebalance to capture the actual low point) + if preRebalanceHF < lowestHF && preRebalanceHF > 0.0 { + lowestHF = preRebalanceHF + } + + // Log every hour + at price extremes + // Show both pre and post values to see rebalance effects + if a == 0 && (minute % 60 == 0 || absolutePrice == lowestPrice || absolutePrice == highestPrice) { + let vaultRebalancesSinceLastLog = currentVaultRebalanceCount - prevVaultRebalanceCount + let positionRebalancesSinceLastLog = currentPositionRebalanceCount - prevPositionRebalanceCount + let hours = minute / 60 + let minutes = minute % 60 + log(" [Time \(hours):\(minutes < 10 ? "0" : "")\(minutes) UTC] price=$\(absolutePrice) yt=\(ytPrice) HF=\(preRebalanceHF)->\(postRebalanceHF) VR=\(preVaultRatio)->\(postVaultRatio) vaultRebalances=\(vaultRebalancesSinceLastLog) positionRebalances=\(positionRebalancesSinceLastLog)") + prevVaultRebalanceCount = currentVaultRebalanceCount + prevPositionRebalanceCount = currentPositionRebalanceCount + } + + // Liquidation occurs when effectiveHF < 1.0 (check pre-rebalance) + if preRebalanceHF < 1.0 && preRebalanceHF > 0.0 { + liquidationCount = liquidationCount + 1 + log(" *** LIQUIDATION agent=\(a) on minute \(minute)! HF=\(preRebalanceHF) ***") + } + a = a + 1 + } + + previousBTCPrice = absolutePrice + minute = minute + 1 + } + + // Count actual rebalance events (not just attempts) + let vaultRebalanceEvents = Test.eventsOfType(Type()) + let positionRebalanceEvents = Test.eventsOfType(Type()) + let vaultRebalanceCount = vaultRebalanceEvents.length + let positionRebalanceCount = positionRebalanceEvents.length + + // Final state + let finalBTCCollateral = getBTCCollateralFromPosition(pid: pids[0]) + let finalDebt = getMOETDebtFromPosition(pid: pids[0]) + let finalYieldTokens = getAutoBalancerBalance(id: vaultIds[0])! + let finalYtPrice = ytPriceAtMinute(prices.length - 1) + // Compute effective HF to match contract's rebalancing logic + let finalEffectiveHF = (finalBTCCollateral * previousBTCPrice * collateralFactor) / finalDebt + + let collateralValueMOET = finalBTCCollateral * previousBTCPrice + let ytValueMOET = finalYieldTokens * finalYtPrice + + let priceUp = previousBTCPrice >= initialPrice + let priceChangeAbs = priceUp ? (previousBTCPrice - initialPrice) : (initialPrice - previousBTCPrice) + let priceChangePct = priceChangeAbs / initialPrice + let priceChangeSign = priceUp ? "+" : "-" + + log("\n=== SIMULATION RESULTS ===") + log("Agents: \(numAgents)") + log("Minutes simulated: \(prices.length)") + log("Rebalance attempts: \(prices.length * numAgents)") + log("Vault rebalances: \(vaultRebalanceCount)") + log("Position rebalances: \(positionRebalanceCount)") + log("Liquidation count: \(liquidationCount)") + log("") + log("--- Price ---") + log("Initial BTC price: $\(initialPrice)") + log("Lowest BTC price: $\(lowestPrice)") + log("Highest BTC price: $\(highestPrice)") + log("Final BTC price: $\(prices[prices.length - 1])") + log("Price change: \(priceChangeSign)\(priceChangePct)") + log("") + log("--- Position (effective HF with collateralFactor=\(collateralFactor)) ---") + log("Lowest HF observed: \(lowestHF)") + log("Final HF (agent 0): \(finalEffectiveHF)") + log("Final collateral: \(finalBTCCollateral) BTC (value: \(collateralValueMOET) MOET)") + log("Final debt: \(finalDebt) MOET") + log("Final yield tokens: \(finalYieldTokens) (value: \(ytValueMOET) MOET @ yt=\(finalYtPrice))") + log("===========================\n") + + Test.assertEqual(btc_oct_2025_expectedLiquidationCount, liquidationCount) + Test.assert(finalEffectiveHF > 1.0, message: "Expected final effective HF > 1.0 but got \(finalEffectiveHF)") + Test.assert(lowestHF > 1.0, message: "Expected lowest effective HF > 1.0 but got \(lowestHF)") + + log("=== TEST PASSED: Zero liquidations on Oct 10 2025 (\(numAgents) agents) ===") +} diff --git a/cadence/tests/simulation_oct_10_2025_cascade_helpers.cdc b/cadence/tests/simulation_oct_10_2025_cascade_helpers.cdc new file mode 100644 index 00000000..e096e6b2 --- /dev/null +++ b/cadence/tests/simulation_oct_10_2025_cascade_helpers.cdc @@ -0,0 +1,247 @@ +import Test + +access(all) struct SimAgent { + access(all) let count: Int + access(all) let initialHF: UFix64 + access(all) let rebalancingHF: UFix64 + access(all) let targetHF: UFix64 + access(all) let debtPerAgent: UFix64 + access(all) let totalSystemDebt: UFix64 + + init( + count: Int, + initialHF: UFix64, + rebalancingHF: UFix64, + targetHF: UFix64, + debtPerAgent: UFix64, + totalSystemDebt: UFix64 + ) { + self.count = count + self.initialHF = initialHF + self.rebalancingHF = rebalancingHF + self.targetHF = targetHF + self.debtPerAgent = debtPerAgent + self.totalSystemDebt = totalSystemDebt + } +} + +access(all) struct SimPool { + access(all) let size: UFix64 + access(all) let concentration: UFix64 + access(all) let feeTier: UFix64 + + init(size: UFix64, concentration: UFix64, feeTier: UFix64) { + self.size = size + self.concentration = concentration + self.feeTier = feeTier + } +} + +access(all) struct SimConstants { + access(all) let btcCollateralFactor: UFix64 + access(all) let btcLiquidationThreshold: UFix64 + access(all) let yieldAPR: UFix64 + access(all) let directMintYT: Bool + + init( + btcCollateralFactor: UFix64, + btcLiquidationThreshold: UFix64, + yieldAPR: UFix64, + directMintYT: Bool + ) { + self.btcCollateralFactor = btcCollateralFactor + self.btcLiquidationThreshold = btcLiquidationThreshold + self.yieldAPR = yieldAPR + self.directMintYT = directMintYT + } +} + +access(all) let btc_oct_2025_prices: [UFix64] = [ + 121713.11, 121713.11, 121713.11, 121713.11, 121713.11, 121783.92, 121783.92, 121783.92, 121783.92, 121783.92, + 121742.69, 121742.69, 121742.69, 121742.69, 121742.69, 121712.69, 121712.69, 121712.69, 121712.69, 121712.69, + 121807.78, 121807.78, 121807.78, 121807.78, 121807.78, 121803.13, 121803.13, 121803.13, 121803.13, 121803.13, + 121916.82, 121916.82, 121916.82, 121916.82, 121916.82, 121878.67, 121878.67, 121878.67, 121878.67, 121878.67, + 121846.02, 121846.02, 121846.02, 121846.02, 121846.02, 121776.41, 121776.41, 121776.41, 121776.41, 121776.41, + 121714.95, 121714.95, 121714.95, 121714.95, 121714.95, 121815.46, 121815.46, 121815.46, 121815.46, 121815.46, + 121807.64, 121807.64, 121807.64, 121807.64, 121807.64, 121897.44, 121897.44, 121897.44, 121897.44, 121897.44, + 121832.68, 121832.68, 121832.68, 121832.68, 121832.68, 121866.09, 121866.09, 121866.09, 121866.09, 121866.09, + 121818.98, 121818.98, 121818.98, 121818.98, 121818.98, 121766.88, 121766.88, 121766.88, 121766.88, 121766.88, + 121847.94, 121847.94, 121847.94, 121847.94, 121847.94, 121884.82, 121884.82, 121884.82, 121884.82, 121884.82, + 121764.33, 121764.33, 121764.33, 121764.33, 121764.33, 121651.03, 121651.03, 121651.03, 121651.03, 121651.03, + 121654.09, 121654.09, 121654.09, 121654.09, 121654.09, 121714.93, 121714.93, 121714.93, 121714.93, 121714.93, + 121721.57, 121721.57, 121721.57, 121721.57, 121721.57, 121540.2, 121540.2, 121540.2, 121540.2, 121540.2, + 121374.41, 121374.41, 121374.41, 121374.41, 121374.41, 121344.5, 121344.5, 121344.5, 121344.5, 121344.5, + 121411.23, 121411.23, 121411.23, 121411.23, 121411.23, 121416.83, 121416.83, 121416.83, 121416.83, 121416.83, + 121416.83, 121416.83, 121416.83, 121416.83, 121416.83, 121237.17, 121237.17, 121237.17, 121237.17, 121237.17, + 121232.15, 121232.15, 121232.15, 121232.15, 121232.15, 121316.75, 121316.75, 121316.75, 121316.75, 121316.75, + 121308.11, 121308.11, 121308.11, 121308.11, 121308.11, 121061.07, 121061.07, 121061.07, 121061.07, 121061.07, + 121133.38, 121133.38, 121133.38, 121133.38, 121133.38, 121053.33, 121053.33, 121053.33, 121053.33, 121053.33, + 121037.86, 121037.86, 121037.86, 121037.86, 121037.86, 121103.51, 121103.51, 121103.51, 121103.51, 121103.51, + 121231.04, 121231.04, 121231.04, 121231.04, 121231.04, 121389.99, 121389.99, 121389.99, 121389.99, 121389.99, + 121461.58, 121461.58, 121461.58, 121461.58, 121461.58, 121520.73, 121520.73, 121520.73, 121520.73, 121520.73, + 121540.9, 121540.9, 121540.9, 121540.9, 121540.9, 121555.89, 121555.89, 121555.89, 121555.89, 121555.89, + 121586.77, 121586.77, 121586.77, 121586.77, 121586.77, 121346.45, 121346.45, 121346.45, 121346.45, 121346.45, + 121155.06, 121155.06, 121155.06, 121155.06, 121155.06, 121156.48, 121156.48, 121156.48, 121156.48, 121156.48, + 121358.49, 121358.49, 121358.49, 121358.49, 121358.49, 121407.33, 121407.33, 121407.33, 121407.33, 121407.33, + 121314.82, 121314.82, 121314.82, 121314.82, 121314.82, 121347.71, 121347.71, 121347.71, 121347.71, 121347.71, + 121344.86, 121344.86, 121344.86, 121344.86, 121344.86, 121294.0, 121294.0, 121294.0, 121294.0, 121294.0, + 121159.02, 121159.02, 121159.02, 121159.02, 121159.02, 121181.88, 121181.88, 121181.88, 121181.88, 121181.88, + 121238.13, 121238.13, 121238.13, 121238.13, 121238.13, 121307.79, 121307.79, 121307.79, 121307.79, 121307.79, + 121304.5, 121304.5, 121304.5, 121304.5, 121304.5, 121163.96, 121163.96, 121163.96, 121163.96, 121163.96, + 121140.22, 121140.22, 121140.22, 121140.22, 121140.22, 121161.46, 121161.46, 121161.46, 121161.46, 121161.46, + 121213.86, 121213.86, 121213.86, 121213.86, 121213.86, 121393.9, 121393.9, 121393.9, 121393.9, 121393.9, + 121423.17, 121423.17, 121423.17, 121423.17, 121423.17, 121295.85, 121295.85, 121295.85, 121295.85, 121295.85, + 121351.58, 121351.58, 121351.58, 121351.58, 121351.58, 121480.12, 121480.12, 121480.12, 121480.12, 121480.12, + 121557.9, 121557.9, 121557.9, 121557.9, 121557.9, 121599.04, 121599.04, 121599.04, 121599.04, 121599.04, + 121632.86, 121632.86, 121632.86, 121632.86, 121632.86, 121667.52, 121667.52, 121667.52, 121667.52, 121667.52, + 121747.83, 121747.83, 121747.83, 121747.83, 121747.83, 121622.3, 121622.3, 121622.3, 121622.3, 121622.3, + 121679.34, 121679.34, 121679.34, 121679.34, 121679.34, 121675.27, 121675.27, 121675.27, 121675.27, 121675.27, + 121602.98, 121602.98, 121602.98, 121602.98, 121602.98, 121624.1, 121624.1, 121624.1, 121624.1, 121624.1, + 121605.49, 121605.49, 121605.49, 121605.49, 121605.49, 121727.75, 121727.75, 121727.75, 121727.75, 121727.75, + 121662.62, 121662.62, 121662.62, 121662.62, 121662.62, 121416.71, 121416.71, 121416.71, 121416.71, 121416.71, + 121450.08, 121450.08, 121450.08, 121450.08, 121450.08, 121323.83, 121323.83, 121323.83, 121323.83, 121323.83, + 121302.47, 121302.47, 121302.47, 121302.47, 121302.47, 121325.47, 121325.47, 121325.47, 121325.47, 121325.47, + 121322.07, 121322.07, 121322.07, 121322.07, 121322.07, 121312.85, 121312.85, 121312.85, 121312.85, 121312.85, + 121397.78, 121397.78, 121397.78, 121397.78, 121397.78, 121275.15, 121275.15, 121275.15, 121275.15, 121275.15, + 121204.55, 121204.55, 121204.55, 121204.55, 121204.55, 121184.15, 121184.15, 121184.15, 121184.15, 121184.15, + 121102.67, 121102.67, 121102.67, 121102.67, 121102.67, 121147.56, 121147.56, 121147.56, 121147.56, 121147.56, + 120997.55, 120997.55, 120997.55, 120997.55, 120997.55, 121178.45, 121178.45, 121178.45, 121178.45, 121178.45, + 121162.89, 121162.89, 121162.89, 121162.89, 121162.89, 121209.71, 121209.71, 121209.71, 121209.71, 121209.71, + 121284.48, 121284.48, 121284.48, 121284.48, 121284.48, 121315.11, 121315.11, 121315.11, 121315.11, 121315.11, + 121349.2, 121349.2, 121349.2, 121349.2, 121349.2, 121344.6, 121344.6, 121344.6, 121344.6, 121344.6, + 121317.25, 121317.25, 121317.25, 121317.25, 121317.25, 121310.53, 121310.53, 121310.53, 121310.53, 121310.53, + 121410.86, 121410.86, 121410.86, 121410.86, 121410.86, 121512.19, 121512.19, 121512.19, 121512.19, 121512.19, + 121656.58, 121656.58, 121656.58, 121656.58, 121656.58, 121604.67, 121604.67, 121604.67, 121604.67, 121604.67, + 121590.34, 121590.34, 121590.34, 121590.34, 121590.34, 121594.51, 121594.51, 121594.51, 121594.51, 121594.51, + 121585.3, 121585.3, 121585.3, 121585.3, 121585.3, 121637.46, 121637.46, 121637.46, 121637.46, 121637.46, + 121791.06, 121791.06, 121791.06, 121791.06, 121791.06, 121780.3, 121780.3, 121780.3, 121780.3, 121780.3, + 121753.19, 121753.19, 121753.19, 121753.19, 121753.19, 121756.3, 121756.3, 121756.3, 121756.3, 121756.3, + 121548.72, 121548.72, 121548.72, 121548.72, 121548.72, 121445.09, 121445.09, 121445.09, 121445.09, 121445.09, + 121381.48, 121381.48, 121381.48, 121381.48, 121381.48, 121426.28, 121426.28, 121426.28, 121426.28, 121426.28, + 121451.53, 121451.53, 121451.53, 121451.53, 121451.53, 121409.59, 121409.59, 121409.59, 121409.59, 121409.59, + 121506.74, 121506.74, 121506.74, 121506.74, 121506.74, 121427.4, 121427.4, 121427.4, 121427.4, 121427.4, + 121412.49, 121412.49, 121412.49, 121412.49, 121412.49, 121364.94, 121364.94, 121364.94, 121364.94, 121364.94, + 121348.16, 121348.16, 121348.16, 121348.16, 121348.16, 121356.98, 121356.98, 121356.98, 121356.98, 121356.98, + 121407.11, 121407.11, 121407.11, 121407.11, 121407.11, 121523.06, 121523.06, 121523.06, 121523.06, 121523.06, + 121554.8, 121554.8, 121554.8, 121554.8, 121554.8, 121643.79, 121643.79, 121643.79, 121643.79, 121643.79, + 121684.2, 121684.2, 121684.2, 121684.2, 121684.2, 121615.93, 121615.93, 121615.93, 121615.93, 121615.93, + 121608.54, 121608.54, 121608.54, 121608.54, 121608.54, 121510.26, 121510.26, 121510.26, 121510.26, 121510.26, + 121509.38, 121509.38, 121509.38, 121509.38, 121509.38, 121562.51, 121562.51, 121562.51, 121562.51, 121562.51, + 121599.85, 121599.85, 121599.85, 121599.85, 121599.85, 121575.13, 121575.13, 121575.13, 121575.13, 121575.13, + 121540.36, 121540.36, 121540.36, 121540.36, 121540.36, 121659.88, 121659.88, 121659.88, 121659.88, 121659.88, + 121620.96, 121620.96, 121620.96, 121620.96, 121620.96, 121707.33, 121707.33, 121707.33, 121707.33, 121707.33, + 121675.29, 121675.29, 121675.29, 121675.29, 121675.29, 121566.1, 121566.1, 121566.1, 121566.1, 121566.1, + 121572.45, 121572.45, 121572.45, 121572.45, 121572.45, 121586.1, 121586.1, 121586.1, 121586.1, 121586.1, + 121597.98, 121597.98, 121597.98, 121597.98, 121597.98, 121612.89, 121612.89, 121612.89, 121612.89, 121612.89, + 121533.15, 121533.15, 121533.15, 121533.15, 121533.15, 121532.14, 121532.14, 121532.14, 121532.14, 121532.14, + 121542.1, 121542.1, 121542.1, 121542.1, 121542.1, 121605.18, 121605.18, 121605.18, 121605.18, 121605.18, + 121644.31, 121644.31, 121644.31, 121644.31, 121644.31, 121884.3, 121884.3, 121884.3, 121884.3, 121884.3, + 121848.85, 121848.85, 121848.85, 121848.85, 121848.85, 121847.06, 121847.06, 121847.06, 121847.06, 121847.06, + 121883.73, 121883.73, 121883.73, 121883.73, 121883.73, 121953.72, 121953.72, 121953.72, 121953.72, 121953.72, + 122023.4, 122023.4, 122023.4, 122023.4, 122023.4, 121968.21, 121968.21, 121968.21, 121968.21, 121968.21, + 122402.5, 122402.5, 122402.5, 122402.5, 122402.5, 122377.2, 122377.2, 122377.2, 122377.2, 122377.2, + 121964.07, 121964.07, 121964.07, 121964.07, 121964.07, 121570.59, 121570.59, 121570.59, 121570.59, 121570.59, + 121748.77, 121748.77, 121748.77, 121748.77, 121748.77, 121962.04, 121962.04, 121962.04, 121962.04, 121962.04, + 121960.31, 121960.31, 121960.31, 121960.31, 121960.31, 121753.53, 121753.53, 121753.53, 121753.53, 121753.53, + 121340.45, 121340.45, 121340.45, 121340.45, 121340.45, 121303.48, 121303.48, 121303.48, 121303.48, 121303.48, + 121293.04, 121293.04, 121293.04, 121293.04, 121293.04, 121422.7, 121422.7, 121422.7, 121422.7, 121422.7, + 121407.38, 121407.38, 121407.38, 121407.38, 121407.38, 121404.7, 121404.7, 121404.7, 121404.7, 121404.7, + 121413.26, 121413.26, 121413.26, 121413.26, 121413.26, 121371.62, 121371.62, 121371.62, 121371.62, 121371.62, + 120687.83, 120687.83, 120687.83, 120687.83, 120687.83, 120198.16, 120198.16, 120198.16, 120198.16, 120198.16, + 120339.23, 120339.23, 120339.23, 120339.23, 120339.23, 120481.8, 120481.8, 120481.8, 120481.8, 120481.8, + 120018.86, 120018.86, 120018.86, 120018.86, 120018.86, 119508.96, 119508.96, 119508.96, 119508.96, 119508.96, + 119398.97, 119398.97, 119398.97, 119398.97, 119398.97, 118722.93, 118722.93, 118722.93, 118722.93, 118722.93, + 119309.22, 119309.22, 119309.22, 119309.22, 119309.22, 119471.71, 119471.71, 119471.71, 119471.71, 119471.71, + 119300.61, 119300.61, 119300.61, 119300.61, 119300.61, 119234.96, 119234.96, 119234.96, 119234.96, 119234.96, + 119051.26, 119051.26, 119051.26, 119051.26, 119051.26, 118814.97, 118814.97, 118814.97, 118814.97, 118814.97, + 119219.31, 119219.31, 119219.31, 119219.31, 119219.31, 119020.37, 119020.37, 119020.37, 119020.37, 119020.37, + 118804.13, 118804.13, 118804.13, 118804.13, 118804.13, 119179.46, 119179.46, 119179.46, 119179.46, 119179.46, + 119205.5, 119205.5, 119205.5, 119205.5, 119205.5, 119348.72, 119348.72, 119348.72, 119348.72, 119348.72, + 119359.24, 119359.24, 119359.24, 119359.24, 119359.24, 118992.29, 118992.29, 118992.29, 118992.29, 118992.29, + 118759.36, 118759.36, 118759.36, 118759.36, 118759.36, 118636.5, 118636.5, 118636.5, 118636.5, 118636.5, + 118346.69, 118346.69, 118346.69, 118346.69, 118346.69, 118246.12, 118246.12, 118246.12, 118246.12, 118246.12, + 117869.48, 117869.48, 117869.48, 117869.48, 117869.48, 117958.82, 117958.82, 117958.82, 117958.82, 117958.82, + 118141.2, 118141.2, 118141.2, 118141.2, 118141.2, 118197.84, 118197.84, 118197.84, 118197.84, 118197.84, + 118193.43, 118193.43, 118193.43, 118193.43, 118193.43, 118152.44, 118152.44, 118152.44, 118152.44, 118152.44, + 117952.32, 117952.32, 117952.32, 117952.32, 117952.32, 117944.63, 117944.63, 117944.63, 117944.63, 117944.63, + 117864.86, 117864.86, 117864.86, 117864.86, 117864.86, 117709.85, 117709.85, 117709.85, 117709.85, 117709.85, + 117735.96, 117735.96, 117735.96, 117735.96, 117735.96, 117708.36, 117708.36, 117708.36, 117708.36, 117708.36, + 117664.29, 117664.29, 117664.29, 117664.29, 117664.29, 117670.95, 117670.95, 117670.95, 117670.95, 117670.95, + 117619.62, 117619.62, 117619.62, 117619.62, 117619.62, 117783.75, 117783.75, 117783.75, 117783.75, 117783.75, + 117706.37, 117706.37, 117706.37, 117706.37, 117706.37, 117642.41, 117642.41, 117642.41, 117642.41, 117642.41, + 117525.64, 117525.64, 117525.64, 117525.64, 117525.64, 117567.52, 117567.52, 117567.52, 117567.52, 117567.52, + 117436.0, 117436.0, 117436.0, 117436.0, 117436.0, 117349.84, 117349.84, 117349.84, 117349.84, 117349.84, + 117340.02, 117340.02, 117340.02, 117340.02, 117340.02, 117356.33, 117356.33, 117356.33, 117356.33, 117356.33, + 117168.43, 117168.43, 117168.43, 117168.43, 117168.43, 116954.58, 116954.58, 116954.58, 116954.58, 116954.58, + 116717.77, 116717.77, 116717.77, 116717.77, 116717.77, 116425.25, 116425.25, 116425.25, 116425.25, 116425.25, + 116373.27, 116373.27, 116373.27, 116373.27, 116373.27, 116234.68, 116234.68, 116234.68, 116234.68, 116234.68, + 116475.36, 116475.36, 116475.36, 116475.36, 116475.36, 116918.03, 116918.03, 116918.03, 116918.03, 116918.03, + 117088.59, 117088.59, 117088.59, 117088.59, 117088.59, 116951.52, 116951.52, 116951.52, 116951.52, 116951.52, + 116733.74, 116733.74, 116733.74, 116733.74, 116733.74, 116861.35, 116861.35, 116861.35, 116861.35, 116861.35, + 116749.58, 116749.58, 116749.58, 116749.58, 116749.58, 116861.01, 116861.01, 116861.01, 116861.01, 116861.01, + 116830.31, 116830.31, 116830.31, 116830.31, 116830.31, 117026.13, 117026.13, 117026.13, 117026.13, 117026.13, + 117125.74, 117125.74, 117125.74, 117125.74, 117125.74, 116964.78, 116964.78, 116964.78, 116964.78, 116964.78, + 116931.16, 116931.16, 116931.16, 116931.16, 116931.16, 117079.18, 117079.18, 117079.18, 117079.18, 117079.18, + 116980.14, 116980.14, 116980.14, 116980.14, 116980.14, 115514.6, 115514.6, 115514.6, 115514.6, 115514.6, + 114360.84, 114360.84, 114360.84, 114360.84, 114360.84, 115099.4, 115099.4, 115099.4, 115099.4, 115099.4, + 114606.83, 114606.83, 114606.83, 114606.83, 114606.83, 112090.47, 112090.47, 112090.47, 112090.47, 112090.47, + 109332.06, 109332.06, 109332.06, 109332.06, 109332.06, 108930.81, 108930.81, 108930.81, 108930.81, 108930.81, + 110248.96, 110248.96, 110248.96, 110248.96, 110248.96, 112986.13, 112986.13, 112986.13, 112986.13, 112986.13, + 113709.78, 113709.78, 113709.78, 113709.78, 113709.78, 112434.86, 112434.86, 112434.86, 112434.86, 112434.86, + 113844.75, 113844.75, 113844.75, 113844.75, 113844.75, 113738.06, 113738.06, 113738.06, 113738.06, 113738.06, + 114285.84, 114285.84, 114285.84, 114285.84, 114285.84, 113856.09, 113856.09, 113856.09, 113856.09, 113856.09, + 113474.33, 113474.33, 113474.33, 113474.33, 113474.33, 114173.9, 114173.9, 114173.9, 114173.9, 114173.9, + 113828.12, 113828.12, 113828.12, 113828.12, 113828.12, 113414.71, 113414.71, 113414.71, 113414.71, 113414.71, + 113004.53, 113004.53, 113004.53, 113004.53, 113004.53, 113607.96, 113607.96, 113607.96, 113607.96, 113607.96, + 113788.37, 113788.37, 113788.37, 113788.37, 113788.37, 114002.96, 114002.96, 114002.96, 114002.96, 114002.96, + 114434.45, 114434.45, 114434.45, 114434.45, 114434.45, 114124.26, 114124.26, 114124.26, 114124.26, 114124.26, + 114226.79, 114226.79, 114226.79, 114226.79, 114226.79, 114112.45, 114112.45, 114112.45, 114112.45, 114112.45, + 113598.58, 113598.58, 113598.58, 113598.58, 113598.58, 113422.34, 113422.34, 113422.34, 113422.34, 113422.34, + 113639.29, 113639.29, 113639.29, 113639.29, 113639.29, 113694.38, 113694.38, 113694.38, 113694.38, 113694.38, + 114124.6, 114124.6, 114124.6, 114124.6, 114124.6, 114227.85, 114227.85, 114227.85, 114227.85, 114227.85, + 114504.41, 114504.41, 114504.41, 114504.41, 114504.41, 114862.02, 114862.02, 114862.02, 114862.02, 114862.02, + 114098.72, 114098.72, 114098.72, 114098.72, 114098.72, 113427.58, 113427.58, 113427.58, 113427.58, 113427.58 +] + +access(all) let btc_oct_2025_agents: [SimAgent] = [ + SimAgent( + count: 1, + initialHF: 1.1, + rebalancingHF: 1.015, + targetHF: 1.05, + debtPerAgent: 133333.00000000, + totalSystemDebt: 20000000.00000000 + ) +] + +access(all) let btc_oct_2025_pools: {String: SimPool} = { + "moet_yt": SimPool( + size: 500000.00000000, + concentration: 0.95000000, + feeTier: 0.00050000 + ), + "moet_btc": SimPool( + size: 5000000.00000000, + concentration: 0.80000000, + feeTier: 0.00300000 + ), + "pyusd_btc": SimPool( + size: 10000000.00000000, + concentration: 0.80000000, + feeTier: 0.00300000 + ), + "moet_fusdev": SimPool( + size: 500000.00000000, + concentration: 0.95000000, + feeTier: 0.00010000 + ) +} + +access(all) let btc_oct_2025_constants: SimConstants = SimConstants( + btcCollateralFactor: 0.80000000, + btcLiquidationThreshold: 0.85000000, + yieldAPR: 0.10000000, + directMintYT: true +) + +access(all) let btc_oct_2025_expectedLiquidationCount: Int = 0 diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index fe0f1097..34775a3f 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -699,6 +699,16 @@ fun rebalancePosition(signer: Test.TestAccount, pid: UInt64, force: Bool, beFail Test.expect(rebalanceRes, beFailed ? Test.beFailed() : Test.beSucceeded()) } +access(all) +fun setPositionHealthParams(signer: Test.TestAccount, pid: UInt64, targetHealth: UFix64, minHealth: UFix64, maxHealth: UFix64) { + let res = _executeTransaction( + "transactions/set_position_health_params.cdc", + [ pid, targetHealth, minHealth, maxHealth ], + signer + ) + Test.expect(res, Test.beSucceeded()) +} + access(all) fun setupMoetVault(_ signer: Test.TestAccount, beFailed: Bool) { let setupRes = _executeTransaction("../../lib/FlowALP/cadence/transactions/moet/setup_vault.cdc", [], signer) diff --git a/cadence/tests/transactions/set_position_health_params.cdc b/cadence/tests/transactions/set_position_health_params.cdc new file mode 100644 index 00000000..1a0aacec --- /dev/null +++ b/cadence/tests/transactions/set_position_health_params.cdc @@ -0,0 +1,31 @@ +import "FlowALPv0" + +/// Sets target, min, and max health params on a Pool's InternalPosition directly. +/// Must be signed by the Pool owner (flowALPAccount). +transaction(pid: UInt64, targetHealth: UFix64, minHealth: UFix64, maxHealth: UFix64) { + let pool: auth(FlowALPv0.EPosition) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) + ?? panic("Could not borrow reference to Pool from storage") + } + + execute { + let pos = self.pool.borrowPosition(pid: pid) + let newTarget = UFix128(targetHealth) + let newMin = UFix128(minHealth) + let newMax = UFix128(maxHealth) + + // these calls enforce the constraints minHealth < targetHealth < maxHealth + // so we keep the widest constraints first, then tighten later + if newMax > pos.maxHealth { + pos.setMaxHealth(newMax) + } + if newMin < pos.minHealth { + pos.setMinHealth(newMin) + } + pos.setTargetHealth(newTarget) + pos.setMaxHealth(newMax) + pos.setMinHealth(newMin) + } +} \ No newline at end of file