From cabe09ce585dcfbe0b626aa4c12ad8860086261e Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Tue, 24 Mar 2026 14:45:58 -0700 Subject: [PATCH 1/8] Update depositLimit calculation --- cadence/contracts/FlowALPModels.cdc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/FlowALPModels.cdc b/cadence/contracts/FlowALPModels.cdc index 42f60eb5..49905f42 100644 --- a/cadence/contracts/FlowALPModels.cdc +++ b/cadence/contracts/FlowALPModels.cdc @@ -1376,9 +1376,13 @@ access(all) contract FlowALPModels { ) } - /// Returns the per-deposit limit based on depositCapacity * depositLimitFraction. + /// Returns the per-deposit limit based on user deposit limit cap and available deposit capacity. access(EImplementation) view fun depositLimit(): UFix64 { - return self.depositCapacity * self.depositLimitFraction + let cap = self.getUserDepositLimitCap() + if self.depositCapacity < cap { + return self.depositCapacity + } + return cap } /// Updates interest indices and regenerates deposit capacity for elapsed time. From 6900012a96c9b92c2fc87b18f47436d1da704185 Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Tue, 24 Mar 2026 16:28:41 -0700 Subject: [PATCH 2/8] update docs for depositLimit --- docs/deposit_capacity_mechanism.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/deposit_capacity_mechanism.md b/docs/deposit_capacity_mechanism.md index 9cf4ef5b..9873eaea 100644 --- a/docs/deposit_capacity_mechanism.md +++ b/docs/deposit_capacity_mechanism.md @@ -113,7 +113,7 @@ After 1 hour regeneration (cap increases to 11000.0): When a user attempts to deposit: 1. **Per-Deposit Limit Check** (prevents single large deposits): - - Calculate: `depositLimit = depositCapacity * depositLimitFraction` + - Calculate: `depositLimit = min(depositCapacity, depositCapacityCap * depositLimitFraction)` - If deposit amount > `depositLimit`, queue the excess - This ensures no single deposit can monopolize available capacity @@ -161,10 +161,10 @@ When a user attempts to deposit: ### Per-Deposit Limit vs Per-User Limit -- **Per-Deposit Limit**: `depositCapacity * depositLimitFraction` - - Based on current available capacity - - Prevents single large deposits - - Changes as capacity is consumed +- **Per-Deposit Limit**: `min(depositCapacity, depositCapacityCap * depositLimitFraction)` + - Based on maximum capacity cap + - Prevents single large deposits and enforces the total capacity + - Limited by available capacity - **Per-User Limit**: `depositCapacityCap * depositLimitFraction` - Based on maximum capacity cap From 33cdcc9522dcaf5d5a834b35e8ab85bd52228d60 Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Tue, 24 Mar 2026 17:11:03 -0700 Subject: [PATCH 3/8] update deposit limit test --- cadence/tests/deposit_capacity_test.cdc | 47 ++++++++++++++++++------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/cadence/tests/deposit_capacity_test.cdc b/cadence/tests/deposit_capacity_test.cdc index 0508d0b0..71e3f93d 100644 --- a/cadence/tests/deposit_capacity_test.cdc +++ b/cadence/tests/deposit_capacity_test.cdc @@ -112,6 +112,9 @@ fun test_per_user_deposit_limits() { // Calculate expected per-user limit let expectedUserLimit = initialCap * depositLimitFraction // 10000 * 0.05 = 500 + var capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) + Test.assertEqual(initialCap, capacityInfo["depositCapacity"]!) + // Setup user 1 let user1 = Test.createAccount() setupMoetVault(user1, beFailed: false) @@ -124,15 +127,23 @@ fun test_per_user_deposit_limits() { // User 1 deposits more (should be accepted up to limit) let user1Deposit1 = 300.0 // After this: usage = 400 (out of 500 limit) depositToPosition(signer: user1, positionID: 0, amount: user1Deposit1, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) - + + capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) + Test.assertEqual(initialCap - 400.0, capacityInfo["depositCapacity"]!) + // User 1 deposits more (should be partially accepted, partially queued) let user1Deposit2 = 200.0 // Only 100 more can be accepted to reach limit of 500, 100 will be queued depositToPosition(signer: user1, positionID: 0, amount: user1Deposit2, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) // After this: usage = 500 (at limit), 100 queued + capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) + Test.assertEqual(initialCap - 500.0, capacityInfo["depositCapacity"]!) // User 1 tries to deposit more (should be queued due to per-user limit) let user1Deposit3 = 100.0 // This should be queued (user already at limit) - depositToPosition(signer: user1, positionID: 0, amount: user1Deposit3, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) // Transaction succeeds but deposit is queued + depositToPosition(signer: user1, positionID: 0, amount: user1Deposit3, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) + // Transaction succeeds but deposit is queued + capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) + Test.assertEqual(initialCap - 500.0, capacityInfo["depositCapacity"]!) // Setup user 2 - they should have their own independent limit let user2 = Test.createAccount() @@ -141,25 +152,35 @@ fun test_per_user_deposit_limits() { let initialDeposit2 = 100.0 createPosition(admin: PROTOCOL_ACCOUNT, signer: user2, amount: initialDeposit2, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) - // After position creation: usage = 100 (out of 500 limit) + // After position creation: user usage = 100 (out of 500 limit); total usage is 600 + capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) + Test.assertEqual(initialCap - 600.0, capacityInfo["depositCapacity"]!) // User 2 should be able to deposit up to their own limit (500 total, so 400 more) let user2Deposit = 400.0 depositToPosition(signer: user2, positionID: 1, amount: user2Deposit, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) - // After this: usage = 500 (at limit) - - // Verify that both users have independent limits by checking capacity - // Get capacity after all deposits - var capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) - let finalCapacity = capacityInfo["depositCapacity"]! + // After this: user usage = 500 (at limit); total usage is 1000 + capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) + Test.assertEqual(initialCap - 1000.0, capacityInfo["depositCapacity"]!) + // Setup user 3 - they should be able to deposit up to the user limit in a single deposit + let user3 = Test.createAccount() + setupMoetVault(user3, beFailed: false) + mintMoet(signer: PROTOCOL_ACCOUNT, to: user3.address, amount: 10000.0, beFailed: false) + + let initialDeposit3 = 500.0 + createPosition(admin: PROTOCOL_ACCOUNT, signer: user3, amount: initialDeposit3, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) + // After position creation: user usage = 500 (out of 500 limit); total usage is 1500 + capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER) + Test.assertEqual(initialCap - 1500.0, capacityInfo["depositCapacity"]!) + // Total accepted deposits: // user1 = 500 (100 initial + 300 + 100 from deposit2, 100 from deposit2 queued, 100 from deposit3 queued) // user2 = 500 (100 initial + 400) - // total = 1000 - // We need to check that capacity decreased by at least 1000 from some initial value - Test.assert(finalCapacity <= initialCap - 1000.0, - message: "Final capacity \(finalCapacity) should be <= initial cap \(initialCap) - 1000") + // user3 = 500 (500 initial) + // total = 1500 + // since queued deposits do not consume any deposit capacity, and none of the deposits should have been + // affected by the per-deposit limit, the total capacity should have decreased by exactly 1500.0 } // ----------------------------------------------------------------------------- From 315f0b1e22983584628ebed2795aa848d927803b Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Thu, 26 Mar 2026 15:20:53 -0700 Subject: [PATCH 4/8] update interface doc for depositLimit --- cadence/contracts/FlowALPModels.cdc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/FlowALPModels.cdc b/cadence/contracts/FlowALPModels.cdc index 49905f42..ca283036 100644 --- a/cadence/contracts/FlowALPModels.cdc +++ b/cadence/contracts/FlowALPModels.cdc @@ -1037,9 +1037,9 @@ access(all) contract FlowALPModels { /// (used when deposits are made) access(EImplementation) fun consumeDepositCapacity(_ amount: UFix64, pid: UInt64) - /// Returns the per-deposit limit based on depositCapacity * depositLimitFraction - /// Rationale: cap per-deposit size to a fraction of the time-based - /// depositCapacity so a single large deposit cannot monopolize capacity. + /// Returns the per-deposit limit based on user deposit limit cap and available deposit capacity. + /// Rationale: cap per-deposit size to a fraction of the total depositCapacityCap + /// so a single large deposit cannot monopolize capacity. /// Excess is queued and drained in chunks (see asyncUpdatePosition), /// enabling fair throughput across many deposits in a block. The 5% /// fraction is conservative and can be tuned by protocol parameters. From 524b5be2093e907962099b9f3d78ae669704a68d Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Thu, 26 Mar 2026 15:22:11 -0700 Subject: [PATCH 5/8] clean up excess deposit queueing --- cadence/contracts/FlowALPv0.cdc | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 224bf8e1..07abe488 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -1116,26 +1116,23 @@ access(all) contract FlowALPv0 { // Deposit rate limiting: prevent a single large deposit from monopolizing capacity. // Excess is queued to be processed asynchronously (see asyncUpdatePosition). let depositAmount = from.balance - let depositLimit = tokenState.depositLimit() - - if depositAmount > depositLimit { - // The deposit is too big, so we need to queue the excess - let queuedDeposit <- from.withdraw(amount: depositAmount - depositLimit) - - position.depositToQueue(type, vault: <-queuedDeposit) - } + var depositLimit = tokenState.depositLimit() // Per-user deposit limit: check if user has exceeded their per-user limit let userDepositLimitCap = tokenState.getUserDepositLimitCap() let currentUsage = tokenState.getDepositUsageForPosition(pid) let remainingUserLimit = userDepositLimitCap - currentUsage + if remainingUserLimit < depositLimit { + depositLimit = remainingUserLimit + } - // If the deposit would exceed the user's limit, queue or reject the excess - if from.balance > remainingUserLimit { - let excessAmount = from.balance - remainingUserLimit - let queuedForUserLimit <- from.withdraw(amount: excessAmount) - - position.depositToQueue(type, vault: <-queuedForUserLimit) + // depositAmount is bounded by the smaller of: + // User deposit limit, per-deposit limit, and global deposit capacity + // If the deposit would exceed a limit, queue or reject the excess + if depositAmount > depositLimit { + let excessAmount = depositAmount - depositLimit + let queuedDeposit <- from.withdraw(amount: excessAmount) + position.depositToQueue(type, vault: <-queuedDeposit) } // If this position doesn't currently have an entry for this token, create one. From 9562629d95bbe564ebfa5ace9f587e26e43d546d Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Thu, 26 Mar 2026 16:18:14 -0700 Subject: [PATCH 6/8] move per-user deposit limit to depositLimit() --- cadence/contracts/FlowALPModels.cdc | 18 ++++++++++-------- cadence/contracts/FlowALPv0.cdc | 14 +++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/cadence/contracts/FlowALPModels.cdc b/cadence/contracts/FlowALPModels.cdc index ca283036..3cbab929 100644 --- a/cadence/contracts/FlowALPModels.cdc +++ b/cadence/contracts/FlowALPModels.cdc @@ -1037,13 +1037,13 @@ access(all) contract FlowALPModels { /// (used when deposits are made) access(EImplementation) fun consumeDepositCapacity(_ amount: UFix64, pid: UInt64) - /// Returns the per-deposit limit based on user deposit limit cap and available deposit capacity. + /// Returns the per-deposit limit based on user deposit limit and available deposit capacity. /// Rationale: cap per-deposit size to a fraction of the total depositCapacityCap /// so a single large deposit cannot monopolize capacity. /// Excess is queued and drained in chunks (see asyncUpdatePosition), /// enabling fair throughput across many deposits in a block. The 5% /// fraction is conservative and can be tuned by protocol parameters. - access(EImplementation) view fun depositLimit(): UFix64 + access(EImplementation) view fun depositLimit(pid: UInt64): UFix64 /// Updates interest indices and regenerates deposit capacity for elapsed time access(EImplementation) fun updateForTimeChange() @@ -1376,13 +1376,15 @@ access(all) contract FlowALPModels { ) } - /// Returns the per-deposit limit based on user deposit limit cap and available deposit capacity. - access(EImplementation) view fun depositLimit(): UFix64 { - let cap = self.getUserDepositLimitCap() - if self.depositCapacity < cap { - return self.depositCapacity + /// Returns the maximum amount that can be deposited to the given position without being queued. + access(EImplementation) view fun depositLimit(pid: UInt64): UFix64 { + let userCap = self.getUserDepositLimitCap() + let userUsed = self.getDepositUsageForPosition(pid) + var available = userCap - userUsed + if self.depositCapacity < available { + available = self.depositCapacity } - return cap + return available } /// Updates interest indices and regenerates deposit capacity for elapsed time. diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 07abe488..9a97d83b 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -1113,18 +1113,10 @@ access(all) contract FlowALPv0 { // Time-based state is handled by the tokenState() helper function - // Deposit rate limiting: prevent a single large deposit from monopolizing capacity. + // Deposit rate limiting: prevent a single user or single large deposit from monopolizing capacity. // Excess is queued to be processed asynchronously (see asyncUpdatePosition). let depositAmount = from.balance - var depositLimit = tokenState.depositLimit() - - // Per-user deposit limit: check if user has exceeded their per-user limit - let userDepositLimitCap = tokenState.getUserDepositLimitCap() - let currentUsage = tokenState.getDepositUsageForPosition(pid) - let remainingUserLimit = userDepositLimitCap - currentUsage - if remainingUserLimit < depositLimit { - depositLimit = remainingUserLimit - } + let depositLimit = tokenState.depositLimit(pid: pid) // depositAmount is bounded by the smaller of: // User deposit limit, per-deposit limit, and global deposit capacity @@ -1858,7 +1850,7 @@ access(all) contract FlowALPv0 { let queuedVault <- position.removeQueuedDeposit(depositType)! let queuedAmount = queuedVault.balance let depositTokenState = self._borrowUpdatedTokenState(type: depositType) - let maxDeposit = depositTokenState.depositLimit() + let maxDeposit = depositTokenState.depositLimit(pid: pid) if maxDeposit >= queuedAmount { // We can deposit all of the queued deposit, so just do it and remove it from the queue From 624d307aec0c03f0908294c9dea791b06d73814f Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Fri, 27 Mar 2026 11:07:34 -0700 Subject: [PATCH 7/8] fix double-counting of queued deposits in emitted events --- cadence/contracts/FlowALPv0.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 9a97d83b..e5b45c8f 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -1165,7 +1165,7 @@ access(all) contract FlowALPv0 { pid: pid, poolUUID: self.uuid, vaultType: type, - amount: amount, + amount: acceptedAmount, depositedUUID: depositedUUID ) From 82b1b637b6690e0ec84e28725f8012f46e00f1ac Mon Sep 17 00:00:00 2001 From: Tim Barry Date: Fri, 27 Mar 2026 11:23:57 -0700 Subject: [PATCH 8/8] fix path to test helper transaction --- cadence/tests/fork_liquidation_edge_cases.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/tests/fork_liquidation_edge_cases.cdc b/cadence/tests/fork_liquidation_edge_cases.cdc index 9aaef9ba..2c93cc68 100644 --- a/cadence/tests/fork_liquidation_edge_cases.cdc +++ b/cadence/tests/fork_liquidation_edge_cases.cdc @@ -530,7 +530,7 @@ fun testStabilityAndInsuranceFees_notCollectedForLiquidatedFunds() { // hardcodes MOET_TOKEN_ID = "A.0000000000000007.MOET.Vault" (local test address), // whereas in fork mode MOET lives at 0x6b00ff876c299c61 (MAINNET_MOET_TOKEN_ID). let swapRes = _executeTransaction( - "./transactions/flow-alp/pool-governance/set_insurance_swapper_mock.cdc", + "./transactions/flow-alp/egovernance/set_insurance_swapper_mock.cdc", [MAINNET_USDF_TOKEN_ID, 1.0, MAINNET_USDF_TOKEN_ID, MAINNET_MOET_TOKEN_ID], MAINNET_PROTOCOL_ACCOUNT )