Skip to content

Commit 0fde19c

Browse files
committed
fix: asset per share logic
1 parent da78e01 commit 0fde19c

File tree

2 files changed

+39
-74
lines changed

2 files changed

+39
-74
lines changed

src/contracts/vault/Vault.sol

Lines changed: 33 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity 0.8.25;
3+
import "forge-std/console2.sol";
34

45
import {MigratableEntity} from "../common/MigratableEntity.sol";
56
import {VaultStorage} from "./VaultStorage.sol";
@@ -84,7 +85,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
8485
if (unlockAt <= now_) {
8586
// Calculate assets for this entry based on its bucket's conversion ratio
8687
uint256 bucketIndex = _bucketIndexFromUnlockAt(unlockAt);
87-
uint256 assetPerShare = _bucketAssetPerShare(bucketIndex);
88+
uint256 assetPerShare = _bucketAssetPerShareRate(bucketIndex);
8889

8990
totalAssets += shares.mulDiv(assetPerShare, 1e18, Math.Rounding.Floor);
9091
}
@@ -405,23 +406,18 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
405406
if (bucketIndex != 0) {
406407
revert InvalidTimestamp();
407408
}
408-
_withdrawalPrefixSum.push(PrefixSum({cumulativeShares: mintedShares, cumulativeAssets: 0}));
409+
_withdrawalPrefixSum.push(mintedShares);
409410
return;
410411
}
411412

412413
if (bucketIndex == length_ - 1) {
413-
_withdrawalPrefixSum[bucketIndex].cumulativeShares += mintedShares;
414+
_withdrawalPrefixSum[bucketIndex] += mintedShares;
414415
return;
415416
}
416417

417418
if (bucketIndex == length_) {
418-
PrefixSum memory previous = _withdrawalPrefixSum[length_ - 1];
419-
_withdrawalPrefixSum.push(
420-
PrefixSum({
421-
cumulativeShares: previous.cumulativeShares + mintedShares,
422-
cumulativeAssets: previous.cumulativeAssets
423-
})
424-
);
419+
uint256 previous = _withdrawalPrefixSum[length_ - 1];
420+
_withdrawalPrefixSum.push(previous + mintedShares);
425421
return;
426422
}
427423

@@ -442,53 +438,18 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
442438
revert InvalidTimestamp();
443439
}
444440

445-
uint256 upper = _withdrawalPrefixSum[toIndex].cumulativeShares;
446-
uint256 lower = fromIndex == 0 ? 0 : _withdrawalPrefixSum[fromIndex - 1].cumulativeShares;
441+
uint256 upper = _withdrawalPrefixSum[toIndex];
442+
uint256 lower = fromIndex == 0 ? 0 : _withdrawalPrefixSum[fromIndex - 1];
447443
return upper - lower;
448444
}
449445

450446
/**
451-
* @notice Get cumulative assets between two bucket indices.
452-
* @param fromIndex starting bucket index (inclusive)
453-
* @param toIndex ending bucket index (inclusive)
454-
* @return cumulative assets across buckets [fromIndex, toIndex]
447+
* @notice Get stored asset-per-share rate for a specific bucket index.
448+
* @param bucketIndex bucket index to get the rate for
449+
* @return assetPerShare asset amount per share (scaled by 1e18), or 0 if the bucket hasn't been processed yet
455450
*/
456-
function _bucketAssetsBetween(uint256 fromIndex, uint256 toIndex) internal view returns (uint256) {
457-
if (fromIndex > toIndex) {
458-
return 0;
459-
}
460-
461-
uint256 length_ = _withdrawalPrefixSum.length;
462-
if (length_ == 0 || fromIndex >= length_) {
463-
return 0;
464-
}
465-
466-
if (toIndex >= length_) {
467-
revert InvalidTimestamp();
468-
}
469-
470-
uint256 upper = _withdrawalPrefixSum[toIndex].cumulativeAssets;
471-
uint256 lower = fromIndex == 0 ? 0 : _withdrawalPrefixSum[fromIndex - 1].cumulativeAssets;
472-
return upper - lower;
473-
}
474-
475-
/**
476-
* @notice Get asset-per-share ratio for a specific bucket.
477-
* @param bucketIndex bucket index to get the ratio for
478-
* @return assetPerShare asset amount per share (scaled by 1e18), or 0 if bucket hasn't matured yet
479-
*/
480-
function _bucketAssetPerShare(uint256 bucketIndex) internal view returns (uint256) {
481-
uint256 bucketShares = _bucketSharesBetween(bucketIndex, bucketIndex);
482-
if (bucketShares == 0) {
483-
return 0;
484-
}
485-
486-
uint256 bucketAssets = _bucketAssetsBetween(bucketIndex, bucketIndex);
487-
if (bucketAssets == 0) {
488-
return 0;
489-
}
490-
491-
return bucketAssets.mulDiv(1e18, bucketShares, Math.Rounding.Floor);
451+
function _bucketAssetPerShareRate(uint256 bucketIndex) internal view returns (uint256) {
452+
return _withdrawalBucketRate.upperLookupRecent(uint48(bucketIndex));
492453
}
493454

494455
function _bucketIndex(uint48 unlockAt) internal returns (uint256 index) {
@@ -516,6 +477,12 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
516477
return (false, 0);
517478
}
518479

480+
uint256 bucketCount = _withdrawalBucketTrace.length();
481+
if (_processedWithdrawalBucket >= bucketCount) {
482+
// All buckets processed; nothing left to mature
483+
return (false, 0);
484+
}
485+
519486
Checkpoints.Checkpoint256 memory checkpoint = _withdrawalBucketTrace.at(uint32(_processedWithdrawalBucket));
520487
if (checkpoint._key > now_) {
521488
return (false, 0);
@@ -525,6 +492,13 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
525492
return (true, matureIndex);
526493
}
527494

495+
function lastMaturedBucket(uint48 now_) public view returns (bool hasMatured, uint256 index) {
496+
console2.log("_withdrawalBucketTrace.length()",_withdrawalBucketTrace.length());
497+
console2.log("_withdrawalPrefixSum.length",_withdrawalPrefixSum.length);
498+
console2.log("_processedWithdrawalBucket",_processedWithdrawalBucket);
499+
return _lastMaturedBucket(now_);
500+
}
501+
528502
function _processMaturedBuckets(uint48 now_)
529503
internal
530504
returns (uint256 pendingWithdrawals_, uint256 pendingWithdrawalShares_)
@@ -551,17 +525,12 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
551525
withdrawals = pendingWithdrawals_;
552526
withdrawalShares = pendingWithdrawalShares_;
553527

554-
// Store cumulative assets for each bucket in this range
555-
// This preserves the asset value at which shares in each bucket were converted
556-
// even if slashing occurs between different buckets maturing
557-
uint256 cumulativeAssetsBefore =
558-
_processedWithdrawalBucket == 0 ? 0 : _withdrawalPrefixSum[_processedWithdrawalBucket - 1].cumulativeAssets;
528+
uint256 assetPerShare = maturedAssets.mulDiv(1e18, maturedShares, Math.Rounding.Floor);
559529

560-
for (uint256 i = _processedWithdrawalBucket; i <= maturedIndex; ++i) {
561-
// Calculate assets for this bucket proportionally
562-
uint256 bucketAssets = maturedAssets.mulDiv(_bucketSharesBetween(i, i), maturedShares, Math.Rounding.Floor);
563-
cumulativeAssetsBefore += bucketAssets;
564-
_withdrawalPrefixSum[i].cumulativeAssets = cumulativeAssetsBefore;
530+
// Store rate only when it changes to reuse checkpoints across buckets with identical conversion rates
531+
(bool exists,, uint256 lastRate) = _withdrawalBucketRate.latestCheckpoint();
532+
if (!exists || lastRate != assetPerShare) {
533+
_withdrawalBucketRate.push(uint48(_processedWithdrawalBucket), assetPerShare);
565534
}
566535

567536
_processedWithdrawalBucket = maturedIndex + 1;
@@ -645,7 +614,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
645614

646615
// Calculate assets for this entry based on its bucket's conversion ratio
647616
uint256 bucketIndex = _bucketIndexFromUnlockAt(unlockAt);
648-
uint256 assetPerShare = _bucketAssetPerShare(bucketIndex);
617+
uint256 assetPerShare = _bucketAssetPerShareRate(bucketIndex);
649618

650619
// Use the stored asset-per-share ratio for this bucket
651620
amount = shares.mulDiv(assetPerShare, 1e18, Math.Rounding.Floor);
@@ -704,7 +673,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
704673

705674
// Calculate assets for this entry based on its bucket's conversion ratio
706675
uint256 bucketIndex = _bucketIndexFromUnlockAt(unlockAt);
707-
uint256 assetPerShare = _bucketAssetPerShare(bucketIndex);
676+
uint256 assetPerShare = _bucketAssetPerShareRate(bucketIndex);
708677

709678
// Use the stored asset-per-share ratio for this bucket
710679
amount += shares.mulDiv(assetPerShare, 1e18, Math.Rounding.Floor);

src/contracts/vault/VaultStorage.sol

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,20 +171,16 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage {
171171
uint256 internal _processedWithdrawalBucket;
172172

173173
/**
174-
* @notice Prefix sum entry containing cumulative shares and assets.
175-
* @dev Stores cumulative values up to and including a bucket index.
174+
* @notice Cumulative withdrawal shares per bucket, stored as prefix sums.
175+
* @dev `_withdrawalPrefixSum[i]` equals cumulative shares across buckets `[0, i]`.
176176
*/
177-
struct PrefixSum {
178-
uint256 cumulativeShares;
179-
uint256 cumulativeAssets;
180-
}
177+
uint256[] internal _withdrawalPrefixSum;
181178

182179
/**
183-
* @notice Cumulative withdrawal shares and assets per bucket, stored as prefix sums.
184-
* @dev `_withdrawalPrefixSum[i]` equals cumulative shares and assets across buckets `[0, i]`.
185-
* Assets are only set when buckets mature; before maturity, cumulativeAssets equals the previous bucket's value.
180+
* @notice Asset-per-share rate checkpoints by bucket index (1e18 scaled).
181+
* @dev Stores the rate only when it changes so consecutive bucket indexes with the same rate reuse a single entry.
186182
*/
187-
PrefixSum[] internal _withdrawalPrefixSum;
183+
Checkpoints.Trace256 internal _withdrawalBucketRate;
188184

189185
constructor(address delegatorFactory, address slasherFactory) {
190186
DELEGATOR_FACTORY = delegatorFactory;

0 commit comments

Comments
 (0)