Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions src/lib/LibRebase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,30 @@ library LibRebase {
/// tests in `test/src/concrete/StoxReceiptVault.t.sol`.
///
/// @param storedBalance The account's raw stored balance.
/// @param cursor The index of the last node this account was migrated
/// through. The default 0 is the bootstrap node — fresh holders start
/// at the bootstrap, and the walk advances them through every
/// subsequent completed split.
/// @param fromActionId The action id of the last node this account was
/// migrated through. The default 0 is the bootstrap node — fresh
/// holders start at the bootstrap, and the walk advances them through
/// every subsequent completed split.
/// @return migratedBalance The balance after sequential multiplier
/// application. Always 0 when `storedBalance == 0`.
/// @return newCursor The index of the last completed split node visited.
/// Equals the input cursor if there were no further completed splits.
function migratedBalance(uint256 storedBalance, uint256 cursor) internal view returns (uint256, uint256 newCursor) {
newCursor = cursor;
/// @return toActionId The action id of the last completed split node
/// visited. Equals `fromActionId` if there were no further completed
/// splits.
function migratedBalance(uint256 storedBalance, uint256 fromActionId)
internal
view
returns (uint256, uint256 toActionId)
{
toActionId = fromActionId;

LibCorporateAction.CorporateActionStorage storage s = LibCorporateAction.getStorage();

uint256 balance = storedBalance;
uint256 nodeIndex =
LibCorporateActionNode.nextOfType(cursor, BALANCE_MIGRATION_TYPES_MASK, CompletionFilter.COMPLETED);
LibCorporateActionNode.nextOfType(fromActionId, BALANCE_MIGRATION_TYPES_MASK, CompletionFilter.COMPLETED);

while (nodeIndex != NODE_NONE) {
newCursor = nodeIndex;
toActionId = nodeIndex;
// Skip the multiplier read and float math whenever the balance
// is already zero. This covers both dormant zero-balance accounts
// (never held / fully burned) and mid-iteration truncation to
Expand Down Expand Up @@ -121,6 +126,6 @@ library LibRebase {
LibCorporateActionNode.nextOfType(nodeIndex, BALANCE_MIGRATION_TYPES_MASK, CompletionFilter.COMPLETED);
}

return (balance, newCursor);
return (balance, toActionId);
}
}
39 changes: 20 additions & 19 deletions src/lib/LibReceiptRebase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,37 +46,38 @@ import {LibRebaseMath} from "./LibRebaseMath.sol";
/// expected to be rare (O(10) over a contract's lifetime) so the
/// per-receipt-holder migration cost is bounded and acceptable.
library LibReceiptRebase {
/// @notice Walk the vault's completed stock split list from `cursor`
/// forward, returning the rebased balance and the advanced cursor.
/// @notice Walk the vault's completed stock split list from
/// `fromActionId` forward, returning the rebased balance and the
/// advanced action id.
///
/// @param storedBalance The raw stored receipt balance for
/// `(holder, id)`, read directly from OZ's ERC1155 storage.
/// @param cursor The index of the last vault corporate-action node this
/// `(holder, id)` pair was migrated through. The default 0 is the
/// vault's bootstrap node — fresh `(holder, id)` pairs start there
/// and the walk advances them through every subsequent completed
/// stock split.
/// @param fromActionId The action id of the last vault corporate-action
/// node this `(holder, id)` pair was migrated through. The default 0
/// is the vault's bootstrap node — fresh `(holder, id)` pairs start
/// there and the walk advances them through every subsequent
/// completed stock split.
/// @param vault The vault contract implementing `ICorporateActionsV1`.
/// @return migratedBalance The balance after sequential multiplier
/// application. Always 0 when `storedBalance == 0`.
/// @return newCursor The index of the last completed stock split
/// visited. Equals the input cursor if no further completed splits
/// were found.
function migratedBalance(uint256 storedBalance, uint256 cursor, ICorporateActionsV1 vault)
/// @return toActionId The action id of the last completed stock split
/// visited. Equals `fromActionId` if no further completed splits were
/// found.
function migratedBalance(uint256 storedBalance, uint256 fromActionId, ICorporateActionsV1 vault)
internal
view
returns (uint256, uint256 newCursor)
returns (uint256, uint256 toActionId)
{
newCursor = cursor;
toActionId = fromActionId;

// Discard effectiveTime — only nextCursor and actionType are used
// Discard effectiveTime — only nextActionId and actionType are used
// for the walk. The mask covers init and stock-split nodes;
// effectiveTime is irrelevant here (the COMPLETED filter already
// handled it on the vault side). actionType lets us skip the
// float multiplier read for the identity init node.
// slither-disable-next-line unused-return
(uint256 nodeIndex, uint256 actionType,) =
vault.nextOfType(cursor, BALANCE_MIGRATION_TYPES_MASK, CompletionFilter.COMPLETED);
vault.nextOfType(fromActionId, BALANCE_MIGRATION_TYPES_MASK, CompletionFilter.COMPLETED);

// Fast path: zero balance still advances the cursor through every
// completed migration node without any multiplier math. Required for
Expand All @@ -87,18 +88,18 @@ library LibReceiptRebase {
// mechanism on the share side.
if (storedBalance == 0) {
while (nodeIndex != NODE_NONE) {
newCursor = nodeIndex;
toActionId = nodeIndex;
// slither-disable-next-line unused-return
(nodeIndex, actionType,) =
vault.nextOfType(nodeIndex, BALANCE_MIGRATION_TYPES_MASK, CompletionFilter.COMPLETED);
}
return (0, newCursor);
return (0, toActionId);
}

uint256 balance = storedBalance;

while (nodeIndex != NODE_NONE) {
newCursor = nodeIndex;
toActionId = nodeIndex;
// Init is identity — no multiplier, no balance change. Skip the
// cross-contract `getActionParameters` call entirely; the
// bootstrap node has empty parameters that would not decode as
Expand All @@ -113,6 +114,6 @@ library LibReceiptRebase {
vault.nextOfType(nodeIndex, BALANCE_MIGRATION_TYPES_MASK, CompletionFilter.COMPLETED);
}

return (balance, newCursor);
return (balance, toActionId);
}
}
14 changes: 7 additions & 7 deletions src/lib/LibTotalSupply.sol
Original file line number Diff line number Diff line change
Expand Up @@ -240,19 +240,19 @@ library LibTotalSupply {
}

/// @notice Update tracking when an account is migrated.
/// @param fromCursor The account's cursor before migration.
/// @param fromActionId The action id the account's cursor was at before migration.
/// @param storedBalance The account's stored balance before migration.
/// @param toCursor The account's cursor after migration.
/// @param toActionId The action id the account's cursor is at after migration.
/// @param newBalance The account's rasterized balance after migration.
function onAccountMigrated(uint256 fromCursor, uint256 storedBalance, uint256 toCursor, uint256 newBalance)
function onAccountMigrated(uint256 fromActionId, uint256 storedBalance, uint256 toActionId, uint256 newBalance)
internal
{
LibCorporateAction.CorporateActionStorage storage s = LibCorporateAction.getStorage();
// Checked subtraction: safe by the pot invariant I(fromCursor) stated
// Checked subtraction: safe by the pot invariant I(fromActionId) stated
// at the top of this library. `storedBalance` is one of the summands
// of `unmigrated[fromCursor]`, so the subtraction cannot underflow.
s.unmigrated[fromCursor] -= storedBalance;
s.unmigrated[toCursor] += newBalance;
// of `unmigrated[fromActionId]`, so the subtraction cannot underflow.
s.unmigrated[fromActionId] -= storedBalance;
s.unmigrated[toActionId] += newBalance;
}

/// @notice Update tracking for a mint (adds to the latest cursor pot).
Expand Down
Loading