11// SPDX-License-Identifier: BUSL-1.1
22pragma solidity 0.8.25 ;
3+ import "forge-std/console2.sol " ;
34
45import {MigratableEntity} from "../common/MigratableEntity.sol " ;
56import {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);
0 commit comments