You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the contract calls _update(), which then invokes _checkpoint(context), it correctly generates an updated context.currentCheckpoint and stores it in _checkpoints[newId]. However, it never writes this new checkpoint’s ID back to the global state (context.global.current).
As a result, the subsequent settlement logic (_settle())—which relies on iterating through context.global.current and context.global.latest—fails to detect the newly created checkpoint. This ultimately isolates the new checkpoint, making it impossible to be processed properly.
A new checkpoint is created but context.global.current is not updated
function _checkpoint(Context memorycontext) privateview {
// 1) Fetch the "current" context.global.current
context.currentId = context.global.current;
context.currentCheckpoint = _checkpoints[context.currentId].read();
// 2) If now > current checkpoint.timestamp, increment currentId// and modify the new checkpoint in memoryif (context.currentTimestamp > context.currentCheckpoint.timestamp) {
context.currentId++;
context.currentCheckpoint.next(context.currentTimestamp, context.global);
}
}
• Here, context.currentId may be incremented, but this change only happens locally.
• The new context.currentId is never written back to context.global.current.
The new checkpoint is stored, but context.global.current remains unchanged
function _saveContext(Context memorycontext, addressaccount) private {
if (account !=address(0)) _accounts[account].store(context.local);
_accounts[address(0)].store(context.global);
// The new checkpoint is stored in _checkpoints
_checkpoints[context.currentId].store(context.currentCheckpoint);
// However, context.currentId is NOT written back to context.global.current// This means the global "current" ID does not advance.
}
• The contract correctly saves the new checkpoint in _checkpoints[...].
• However, it never updates context.global.current, meaning the global state does not recognize that a new checkpoint was created.
_settle() relies on context.global.current > context.global.latest to process new checkpoints
while (
context.global.current > context.global.latest &&
...
) {
// Only then does it proceed to call nextCheckpoint.complete(...)
}
• _settle() only processes checkpoints that context.global.current acknowledges.
• Since context.global.current was never updated, _settle() never sees the newly created checkpoint.
• As a result, the settlement logic skips the new checkpoint, leaving it orphaned and preventing it from being finalized.
Fixing the Issue
A standard fix is to update _checkpoint() or _saveContext() so that the incremented context.currentId is written back to context.global.current. This ensures that the global state is aware of the newly created checkpoint.
Fixed Version of _checkpoint() and _saveContext()
function _checkpoint(Context memorycontext) privateview {
context.currentId = context.global.current;
context.currentCheckpoint = _checkpoints[context.currentId].read();
if (context.currentTimestamp > context.currentCheckpoint.timestamp) {
context.currentId++;
context.currentCheckpoint.next(context.currentTimestamp, context.global);
}
}
function _saveContext(Context memorycontext, addressaccount) private {
if (account !=address(0)) _accounts[account].store(context.local);
// FIX: Write context.currentId back to context.global.current
context.global.current = context.currentId;
_accounts[address(0)].store(context.global);
_checkpoints[context.currentId].store(context.currentCheckpoint);
}
Why This Fix Works
1. The new checkpoint’s ID is now stored in context.global.current, making it globally recognized.
2. _settle() can now detect the new checkpoint and process it correctly.
3. The contract no longer leaves checkpoints orphaned and ensures proper settlement
The text was updated successfully, but these errors were encountered:
Dazzling Pastel Gibbon
High
the “creating a new checkpoint” process
When the contract calls _update(), which then invokes _checkpoint(context), it correctly generates an updated context.currentCheckpoint and stores it in _checkpoints[newId]. However, it never writes this new checkpoint’s ID back to the global state (context.global.current).
As a result, the subsequent settlement logic (_settle())—which relies on iterating through context.global.current and context.global.latest—fails to detect the newly created checkpoint. This ultimately isolates the new checkpoint, making it impossible to be processed properly.
https://github.com/sherlock-audit/2025-01-perennial-v2-4-update/blob/2381b47e69b17fe892b1d1d0f467c358cf950143/perennial-v2/packages/vault/contracts/Vault.sol#L297
Detailed Explanation of the Bug
Fixing the Issue
A standard fix is to update _checkpoint() or _saveContext() so that the incremented context.currentId is written back to context.global.current. This ensures that the global state is aware of the newly created checkpoint.
Fixed Version of _checkpoint() and _saveContext()
Why This Fix Works
1. The new checkpoint’s ID is now stored in context.global.current, making it globally recognized.
2. _settle() can now detect the new checkpoint and process it correctly.
3. The contract no longer leaves checkpoints orphaned and ensures proper settlement
The text was updated successfully, but these errors were encountered: