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
4 changes: 2 additions & 2 deletions docs/B20/Asset.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Each account's stored balance is the **raw** balance. A uniform on-chain **multi

Read the current multiplier with `multiplier()`; the value is in WAD precision (`1e18`, exposed as `WAD_PRECISION()`). `toScaledBalance(rawBalance)` converts a raw amount to its scaled view, `toRawBalance(scaledBalance)` is the reverse converter (integer-floored, so the round-trip can lose up to one ULP), and `scaledBalanceOf(account)` is a convenience over ERC-20's `balanceOf` that returns the same account's raw balance in its scaled form.

Both multiplier setters validate `newMultiplier` is non-zero and at most `type(uint128).max` (reverting `InvalidMultiplier` otherwise). The `uint128` ceiling is the overflow guard: with supply capped at `type(uint128).max`, a `uint128` multiplier keeps `raw * multiplier` inside `uint256`, so scaled reads can never overflow.
Both multiplier setters validate `newMultiplier` is non-zero and at most `type(uint128).max` (reverting `InvalidMultiplier` otherwise). The `uint128` ceiling is the overflow guard: with supply capped at `type(uint128).max`, a `uint128` multiplier keeps `balance * multiplier` inside `uint256`, so balance-derived reads never overflow.

### Scheduling multiplier updates

Expand All @@ -29,7 +29,7 @@ The Asset variant conforms to [ERC-8056](https://eips.ethereum.org/EIPS/eip-8056
- `balanceOfUI(account)` aliases `scaledBalanceOf`, and `totalSupplyUI()` returns `totalSupply() * uiMultiplier() / 1e18` (optional Balances extension `0xd890fd71`).
- `supportsInterface(bytes4)` (ERC-165, `0x01ffc9a7`) returns `true` for those three IDs and for ERC-165 itself. The optional Conversion extension (`0x57854fc3`) is **not** claimed — the native `toScaledBalance` / `toRawBalance` names are kept unaliased for backwards compatibility.

**Events.** Every multiplier change emits `UIMultiplierUpdated(oldMultiplier, newMultiplier, effectiveAtTimestamp)` — from `setUIMultiplier` and from `updateMultiplier`. `MultiplierUpdateCancelled(cancelledMultiplier, cancelledEffectiveAt)` is emitted by `cancelScheduledMultiplier` and by `updateMultiplier` when it clears a live pending.
**Events.** Every multiplier change emits `UIMultiplierUpdated(oldMultiplier, newMultiplier, effectiveAtTimestamp)` — from `setUIMultiplier` and from `updateMultiplier`. `MultiplierUpdateCancelled(cancelledMultiplier, cancelledEffectiveAt)` is emitted by `cancelScheduledMultiplier` and by `updateMultiplier` when it clears a live pending. The optional ERC-8056 `TransferWithUIAmount` event is intentionally omitted — scaled balances are derivable from the raw `Transfer` and the active multiplier.

### Precision & decimals

Expand Down
17 changes: 15 additions & 2 deletions src/interfaces/IB20Asset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ interface IB20Asset is IB20, IERC165, IScaledUIAmount, IScaledUIAmountNewUIMulti
/// in shape to wstETH wrapping stETH.
///
/// @dev Alias of the ERC-8056 `uiMultiplier()`.
///
/// @return Current (effective) multiplier.
function multiplier() external view returns (uint256);

Expand Down Expand Up @@ -180,17 +181,29 @@ interface IB20Asset is IB20, IERC165, IScaledUIAmount, IScaledUIAmountNewUIMulti
/// @notice Schedules a multiplier update to take effect at `effectiveAt` — the standard path
/// for corporate actions (splits, reinvested dividends).
///
/// @param newMultiplier New multiplier scaled to `WAD_PRECISION`
/// @dev Reverts with `AccessControlUnauthorizedAccount` when the caller does not hold `OPERATOR_ROLE`.
/// @dev Reverts with `InvalidMultiplier` when `newMultiplier` is zero or above `type(uint128).max`.
/// @dev Reverts with `EffectiveAtInPast` when `effectiveAt` is not in the future.
/// @dev Reverts with `EffectiveAtTooFar` when `effectiveAt` exceeds `type(uint64).max`.
/// @dev Reverts with `ScheduleOverlap` when a live pending update already exists.
///
/// @param newMultiplier New multiplier scaled to `WAD_PRECISION`.
/// @param effectiveAt Timestamp at which `newMultiplier` becomes effective; must be in the future.
function setUIMultiplier(uint256 newMultiplier, uint256 effectiveAt) external;

/// @notice Cancels the single live pending update, restoring the no-pending state
/// (`effectiveAt` resets to 0).
///
/// @dev Reverts with `AccessControlUnauthorizedAccount` when the caller does not hold `OPERATOR_ROLE`.
/// @dev Reverts with `NoScheduledMultiplier` when there is no live pending update.
function cancelScheduledMultiplier() external;

/// @notice Instant failsafe / emergency override — sets the current multiplier immediately and
/// cancels any live pending update without a scheduling window.
/// Prefer `setUIMultiplier` for routine corporate actions
/// Prefer `setUIMultiplier` for routine corporate actions.
///
/// @dev Reverts with `AccessControlUnauthorizedAccount` when the caller does not hold `OPERATOR_ROLE`.
/// @dev Reverts with `InvalidMultiplier` when `newMultiplier` is zero or above `type(uint128).max`.
///
/// @param newMultiplier New multiplier scaled to `WAD_PRECISION`; must be in `(0, type(uint128).max]`.
function updateMultiplier(uint256 newMultiplier) external;
Expand Down
8 changes: 2 additions & 6 deletions src/interfaces/IScaledUIAmount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ pragma solidity >=0.8.20 <0.9.0;
/// `uiMultiplier` (18-decimal WAD, `1e18 = 1.0`) that rescales the *displayed*
/// balance without minting, transferring, or rewriting any raw balance.
///
/// @dev Interface ID: `0xa60bf13d` — self-checked via `type(IScaledUIAmount).interfaceId`, which
/// is derived solely from `uiMultiplier()`. ERC-8056 also defines an OPTIONAL
/// `TransferWithUIAmount(address,address,uint256,uint256)` event; B20 deliberately omits it
/// (D9: no per-transfer scaled event, since the multiplier is cosmetic and scaled balances
/// are reconstructable off-chain). Events do not contribute to the interface ID, so omitting
/// it leaves `0xa60bf13d` unchanged.
/// @dev Interface ID: `0xa60bf13d`. The optional `TransferWithUIAmount` event is intentionally
/// not implemented; see `docs/B20/Asset.md` for the rationale.
interface IScaledUIAmount {
/// @notice Emitted when the UI multiplier is updated.
event UIMultiplierUpdated(uint256 oldMultiplier, uint256 newMultiplier, uint256 effectiveAtTimestamp);
Expand Down
Loading