Skip to content

Commit 435891a

Browse files
authored
Merge pull request #292 from onflow/tim/FLO-30-depositLimit
FLO-30: Update depositLimit calculation
2 parents abe64b3 + 82b1b63 commit 435891a

File tree

5 files changed

+62
-46
lines changed

5 files changed

+62
-46
lines changed

cadence/contracts/FlowALPModels.cdc

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,13 +1037,13 @@ access(all) contract FlowALPModels {
10371037
/// (used when deposits are made)
10381038
access(EImplementation) fun consumeDepositCapacity(_ amount: UFix64, pid: UInt64)
10391039

1040-
/// Returns the per-deposit limit based on depositCapacity * depositLimitFraction
1041-
/// Rationale: cap per-deposit size to a fraction of the time-based
1042-
/// depositCapacity so a single large deposit cannot monopolize capacity.
1040+
/// Returns the per-deposit limit based on user deposit limit and available deposit capacity.
1041+
/// Rationale: cap per-deposit size to a fraction of the total depositCapacityCap
1042+
/// so a single large deposit cannot monopolize capacity.
10431043
/// Excess is queued and drained in chunks (see asyncUpdatePosition),
10441044
/// enabling fair throughput across many deposits in a block. The 5%
10451045
/// fraction is conservative and can be tuned by protocol parameters.
1046-
access(EImplementation) view fun depositLimit(): UFix64
1046+
access(EImplementation) view fun depositLimit(pid: UInt64): UFix64
10471047

10481048
/// Updates interest indices and regenerates deposit capacity for elapsed time
10491049
access(EImplementation) fun updateForTimeChange()
@@ -1376,9 +1376,15 @@ access(all) contract FlowALPModels {
13761376
)
13771377
}
13781378

1379-
/// Returns the per-deposit limit based on depositCapacity * depositLimitFraction.
1380-
access(EImplementation) view fun depositLimit(): UFix64 {
1381-
return self.depositCapacity * self.depositLimitFraction
1379+
/// Returns the maximum amount that can be deposited to the given position without being queued.
1380+
access(EImplementation) view fun depositLimit(pid: UInt64): UFix64 {
1381+
let userCap = self.getUserDepositLimitCap()
1382+
let userUsed = self.getDepositUsageForPosition(pid)
1383+
var available = userCap - userUsed
1384+
if self.depositCapacity < available {
1385+
available = self.depositCapacity
1386+
}
1387+
return available
13821388
}
13831389

13841390
/// Updates interest indices and regenerates deposit capacity for elapsed time.

cadence/contracts/FlowALPv0.cdc

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,31 +1113,20 @@ access(all) contract FlowALPv0 {
11131113

11141114
// Time-based state is handled by the tokenState() helper function
11151115

1116-
// Deposit rate limiting: prevent a single large deposit from monopolizing capacity.
1116+
// Deposit rate limiting: prevent a single user or single large deposit from monopolizing capacity.
11171117
// Excess is queued to be processed asynchronously (see asyncUpdatePosition).
11181118
let depositAmount = from.balance
1119-
let depositLimit = tokenState.depositLimit()
1119+
let depositLimit = tokenState.depositLimit(pid: pid)
11201120

1121+
// depositAmount is bounded by the smaller of:
1122+
// User deposit limit, per-deposit limit, and global deposit capacity
1123+
// If the deposit would exceed a limit, queue or reject the excess
11211124
if depositAmount > depositLimit {
1122-
// The deposit is too big, so we need to queue the excess
1123-
let queuedDeposit <- from.withdraw(amount: depositAmount - depositLimit)
1124-
1125+
let excessAmount = depositAmount - depositLimit
1126+
let queuedDeposit <- from.withdraw(amount: excessAmount)
11251127
position.depositToQueue(type, vault: <-queuedDeposit)
11261128
}
11271129

1128-
// Per-user deposit limit: check if user has exceeded their per-user limit
1129-
let userDepositLimitCap = tokenState.getUserDepositLimitCap()
1130-
let currentUsage = tokenState.getDepositUsageForPosition(pid)
1131-
let remainingUserLimit = userDepositLimitCap - currentUsage
1132-
1133-
// If the deposit would exceed the user's limit, queue or reject the excess
1134-
if from.balance > remainingUserLimit {
1135-
let excessAmount = from.balance - remainingUserLimit
1136-
let queuedForUserLimit <- from.withdraw(amount: excessAmount)
1137-
1138-
position.depositToQueue(type, vault: <-queuedForUserLimit)
1139-
}
1140-
11411130
// If this position doesn't currently have an entry for this token, create one.
11421131
if position.getBalance(type) == nil {
11431132
position.setBalance(type, FlowALPModels.InternalBalance(
@@ -1176,7 +1165,7 @@ access(all) contract FlowALPv0 {
11761165
pid: pid,
11771166
poolUUID: self.uuid,
11781167
vaultType: type,
1179-
amount: amount,
1168+
amount: acceptedAmount,
11801169
depositedUUID: depositedUUID
11811170
)
11821171

@@ -1861,7 +1850,7 @@ access(all) contract FlowALPv0 {
18611850
let queuedVault <- position.removeQueuedDeposit(depositType)!
18621851
let queuedAmount = queuedVault.balance
18631852
let depositTokenState = self._borrowUpdatedTokenState(type: depositType)
1864-
let maxDeposit = depositTokenState.depositLimit()
1853+
let maxDeposit = depositTokenState.depositLimit(pid: pid)
18651854

18661855
if maxDeposit >= queuedAmount {
18671856
// We can deposit all of the queued deposit, so just do it and remove it from the queue

cadence/tests/deposit_capacity_test.cdc

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ fun test_per_user_deposit_limits() {
112112
// Calculate expected per-user limit
113113
let expectedUserLimit = initialCap * depositLimitFraction // 10000 * 0.05 = 500
114114

115+
var capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
116+
Test.assertEqual(initialCap, capacityInfo["depositCapacity"]!)
117+
115118
// Setup user 1
116119
let user1 = Test.createAccount()
117120
setupMoetVault(user1, beFailed: false)
@@ -124,15 +127,23 @@ fun test_per_user_deposit_limits() {
124127
// User 1 deposits more (should be accepted up to limit)
125128
let user1Deposit1 = 300.0 // After this: usage = 400 (out of 500 limit)
126129
depositToPosition(signer: user1, positionID: 0, amount: user1Deposit1, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false)
127-
130+
131+
capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
132+
Test.assertEqual(initialCap - 400.0, capacityInfo["depositCapacity"]!)
133+
128134
// User 1 deposits more (should be partially accepted, partially queued)
129135
let user1Deposit2 = 200.0 // Only 100 more can be accepted to reach limit of 500, 100 will be queued
130136
depositToPosition(signer: user1, positionID: 0, amount: user1Deposit2, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false)
131137
// After this: usage = 500 (at limit), 100 queued
138+
capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
139+
Test.assertEqual(initialCap - 500.0, capacityInfo["depositCapacity"]!)
132140

133141
// User 1 tries to deposit more (should be queued due to per-user limit)
134142
let user1Deposit3 = 100.0 // This should be queued (user already at limit)
135-
depositToPosition(signer: user1, positionID: 0, amount: user1Deposit3, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false) // Transaction succeeds but deposit is queued
143+
depositToPosition(signer: user1, positionID: 0, amount: user1Deposit3, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false)
144+
// Transaction succeeds but deposit is queued
145+
capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
146+
Test.assertEqual(initialCap - 500.0, capacityInfo["depositCapacity"]!)
136147

137148
// Setup user 2 - they should have their own independent limit
138149
let user2 = Test.createAccount()
@@ -141,25 +152,35 @@ fun test_per_user_deposit_limits() {
141152

142153
let initialDeposit2 = 100.0
143154
createPosition(admin: PROTOCOL_ACCOUNT, signer: user2, amount: initialDeposit2, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false)
144-
// After position creation: usage = 100 (out of 500 limit)
155+
// After position creation: user usage = 100 (out of 500 limit); total usage is 600
156+
capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
157+
Test.assertEqual(initialCap - 600.0, capacityInfo["depositCapacity"]!)
145158

146159
// User 2 should be able to deposit up to their own limit (500 total, so 400 more)
147160
let user2Deposit = 400.0
148161
depositToPosition(signer: user2, positionID: 1, amount: user2Deposit, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false)
149-
// After this: usage = 500 (at limit)
150-
151-
// Verify that both users have independent limits by checking capacity
152-
// Get capacity after all deposits
153-
var capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
154-
let finalCapacity = capacityInfo["depositCapacity"]!
162+
// After this: user usage = 500 (at limit); total usage is 1000
163+
capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
164+
Test.assertEqual(initialCap - 1000.0, capacityInfo["depositCapacity"]!)
155165

166+
// Setup user 3 - they should be able to deposit up to the user limit in a single deposit
167+
let user3 = Test.createAccount()
168+
setupMoetVault(user3, beFailed: false)
169+
mintMoet(signer: PROTOCOL_ACCOUNT, to: user3.address, amount: 10000.0, beFailed: false)
170+
171+
let initialDeposit3 = 500.0
172+
createPosition(admin: PROTOCOL_ACCOUNT, signer: user3, amount: initialDeposit3, vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false)
173+
// After position creation: user usage = 500 (out of 500 limit); total usage is 1500
174+
capacityInfo = getDepositCapacityInfo(vaultIdentifier: MOET_TOKEN_IDENTIFIER)
175+
Test.assertEqual(initialCap - 1500.0, capacityInfo["depositCapacity"]!)
176+
156177
// Total accepted deposits:
157178
// user1 = 500 (100 initial + 300 + 100 from deposit2, 100 from deposit2 queued, 100 from deposit3 queued)
158179
// user2 = 500 (100 initial + 400)
159-
// total = 1000
160-
// We need to check that capacity decreased by at least 1000 from some initial value
161-
Test.assert(finalCapacity <= initialCap - 1000.0,
162-
message: "Final capacity \(finalCapacity) should be <= initial cap \(initialCap) - 1000")
180+
// user3 = 500 (500 initial)
181+
// total = 1500
182+
// since queued deposits do not consume any deposit capacity, and none of the deposits should have been
183+
// affected by the per-deposit limit, the total capacity should have decreased by exactly 1500.0
163184
}
164185

165186
// -----------------------------------------------------------------------------

cadence/tests/fork_liquidation_edge_cases.cdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ fun testStabilityAndInsuranceFees_notCollectedForLiquidatedFunds() {
530530
// hardcodes MOET_TOKEN_ID = "A.0000000000000007.MOET.Vault" (local test address),
531531
// whereas in fork mode MOET lives at 0x6b00ff876c299c61 (MAINNET_MOET_TOKEN_ID).
532532
let swapRes = _executeTransaction(
533-
"./transactions/flow-alp/pool-governance/set_insurance_swapper_mock.cdc",
533+
"./transactions/flow-alp/egovernance/set_insurance_swapper_mock.cdc",
534534
[MAINNET_USDF_TOKEN_ID, 1.0, MAINNET_USDF_TOKEN_ID, MAINNET_MOET_TOKEN_ID],
535535
MAINNET_PROTOCOL_ACCOUNT
536536
)

docs/deposit_capacity_mechanism.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ After 1 hour regeneration (cap increases to 11000.0):
113113
When a user attempts to deposit:
114114

115115
1. **Per-Deposit Limit Check** (prevents single large deposits):
116-
- Calculate: `depositLimit = depositCapacity * depositLimitFraction`
116+
- Calculate: `depositLimit = min(depositCapacity, depositCapacityCap * depositLimitFraction)`
117117
- If deposit amount > `depositLimit`, queue the excess
118118
- This ensures no single deposit can monopolize available capacity
119119

@@ -161,10 +161,10 @@ When a user attempts to deposit:
161161

162162
### Per-Deposit Limit vs Per-User Limit
163163

164-
- **Per-Deposit Limit**: `depositCapacity * depositLimitFraction`
165-
- Based on current available capacity
166-
- Prevents single large deposits
167-
- Changes as capacity is consumed
164+
- **Per-Deposit Limit**: `min(depositCapacity, depositCapacityCap * depositLimitFraction)`
165+
- Based on maximum capacity cap
166+
- Prevents single large deposits and enforces the total capacity
167+
- Limited by available capacity
168168

169169
- **Per-User Limit**: `depositCapacityCap * depositLimitFraction`
170170
- Based on maximum capacity cap

0 commit comments

Comments
 (0)