Skip to content
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ forge install base/base-std
src
├── <a href="./src/StdPrecompiles.sol">StdPrecompiles.sol</a>: Collection of precompiles and their interfaces
└── interfaces
├── <a href="./src/interfaces/IActivationRegistry.sol">IActivationRegistry.sol</a>: Runtime feature activation registry (Beryl+)
├── <a href="./src/interfaces/IB20.sol">IB20.sol</a>: Core Token Standard
├── <a href="./src/interfaces/IB20Stablecoin.sol">IB20Stablecoin.sol</a>: Stablecoin variant of B20
├── <a href="./src/interfaces/IB20Security.sol">IB20Security.sol</a>: Security variant of B20
Expand Down
112 changes: 59 additions & 53 deletions src/interfaces/IActivationRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,94 @@
pragma solidity >=0.8.20 <0.9.0;

/// @title IActivationRegistry
/// @notice Singleton precompile that gates Base-native features behind
/// an activation admin. Each feature is identified by an opaque
/// `bytes32` feature id and is either activated or not;
/// consumers (other precompiles, system configuration, or
/// downstream contracts) consult `isActivated` to gate behavior.
/// @notice Singleton precompile that gates Base-native features behind an
/// activation admin. Each feature is identified by an opaque `bytes32`
/// id and is either enabled or disabled; all features default to
/// disabled. The admin can call `enable` or `disable`; any other
/// caller reverts with `Unauthorized`. No-op transitions (enabling an
/// already-enabled feature or disabling an already-disabled feature)
/// also revert.
///
/// The activation admin is the only address authorized to call
/// `activate`; all other callers revert with `Unauthorized`.
/// Activation is one-way: once a feature is activated it cannot
/// be deactivated.
/// @dev The precompile enforces two call-context invariants surfaced as ABI
/// reverts:
/// - `DelegateCallNotAllowed`: entry points require a direct `CALL`.
/// `DELEGATECALL` / `CALLCODE` are rejected so the caller identity is
/// bound to `msg.sender`, not the calling contract's storage context.
/// - `StaticCallNotAllowed`: `enable` and `disable` mutate state and
/// cannot be invoked from a `STATICCALL` frame.
///
/// @dev The precompile enforces two call-context invariants that are
/// surfaced as reverts but cannot originate from normal Solidity
/// consumers:
/// - `DelegateCallNotAllowed`: the precompile MUST be invoked
/// via `CALL` (not `DELEGATECALL` or `CALLCODE`), so the admin
/// identity is bound to `msg.sender` rather than the calling
/// contract's storage context.
/// - `StaticCallNotAllowed`: `activate` mutates state and cannot
/// be invoked from a `STATICCALL` frame.
/// Feature ids are opaque to the registry: any `bytes32` is a valid id.
/// By convention the producing component picks a stable id derived from
/// a human-readable feature name (the chain-node source uses 32-byte
/// digests for this purpose).
///
/// Feature ids are opaque to the registry: it does not interpret
/// them, and any `bytes32` is a valid id. By convention the
/// producing component picks a stable id derived from a
/// human-readable feature name (the chain-node source uses
/// 32-byte digests for this purpose).
/// `FeatureNotEnabled` is raised by consumers that call an
/// `assertEnabled`-style gate on a disabled feature, not by `isEnabled`
/// itself (which returns `false` instead).
interface IActivationRegistry {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/

/// @notice `caller` is not the activation admin and is therefore
/// not authorized to call `activate`.
/// @notice `caller` is not the activation admin and is therefore not
/// authorized to call `enable` or `disable`.
error Unauthorized(address caller);

/// @notice `activate` was called on a feature that is already
/// activated.
error AlreadyActivated(bytes32 feature);
/// @notice `enable` was called on a feature that is already enabled.
error AlreadyEnabled(bytes32 feature);

/// @notice `feature` is not activated. Returned by precompiles that
/// consult the registry as a hard gate (the chain node
/// uses an `assertActivated`-style flow for this); not
/// raised by `isActivated`, which returns `false` instead.
error FeatureNotActivated(bytes32 feature);
/// @notice `disable` was called on a feature that is already disabled.
error AlreadyDisabled(bytes32 feature);

/// @notice The precompile was invoked via `DELEGATECALL` or
/// `CALLCODE`. All entry points require a direct `CALL`.
/// @notice `feature` is not enabled. Raised by consumers that use the
/// registry as a hard gate (e.g. an `assertEnabled` pattern);
/// not raised by `isEnabled`, which returns `false` instead.
error FeatureNotEnabled(bytes32 feature);

/// @notice The precompile was invoked via `DELEGATECALL` or `CALLCODE`.
/// All entry points require a direct `CALL`.
error DelegateCallNotAllowed();

/// @notice A state-mutating entry point (`activate`) was invoked
/// from a `STATICCALL` frame.
/// @notice A state-mutating entry point (`enable` or `disable`) was
/// invoked from a `STATICCALL` frame.
error StaticCallNotAllowed();

/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/

/// @notice Emitted when `feature` transitions from inactive to
/// activated. `caller` is the activation admin.
event FeatureActivated(bytes32 indexed feature, address indexed caller);
/// @notice Emitted when `feature` transitions from disabled to enabled.
/// `caller` is the activation admin.
event FeatureEnabled(bytes32 indexed feature, address indexed caller);

/// @notice Emitted when `feature` transitions from enabled to disabled.
/// `caller` is the activation admin.
event FeatureDisabled(bytes32 indexed feature, address indexed caller);

/*//////////////////////////////////////////////////////////////
ACTIVATION QUERIES
//////////////////////////////////////////////////////////////*/

/// @notice Whether `feature` is currently activated. Returns
/// `false` for any feature id that has never been
/// activated (the default state).
function isActivated(bytes32 feature) external view returns (bool);
/// @notice Whether `feature` is currently enabled. Returns `false` for
/// any feature id that has never been enabled (the default state).
function isEnabled(bytes32 feature) external view returns (bool);

/// @notice The address authorized to call `activate`.
function admin() external view returns (address);
/// @notice The address authorized to call `enable` and `disable`.
function activationAdmin() external view returns (address);

/*//////////////////////////////////////////////////////////////
ACTIVATION CONTROL
//////////////////////////////////////////////////////////////*/

/// @notice Activates `feature`. Caller MUST equal `admin()` (else
/// `Unauthorized`). Reverts with `AlreadyActivated` if the
/// feature is already activated; reverts with
/// `StaticCallNotAllowed` if invoked under `STATICCALL`.
/// Emits `FeatureActivated` on success. Activation is
/// one-way: there is no `deactivate` counterpart.
function activate(bytes32 feature) external;
/// @notice Enables `feature`. Caller MUST equal `activationAdmin()` (else
/// `Unauthorized`). Reverts with `AlreadyEnabled` if the feature
/// is already enabled; reverts with `StaticCallNotAllowed` if
/// invoked under `STATICCALL`. Emits `FeatureEnabled` on success.
function enable(bytes32 feature) external;

/// @notice Disables `feature`. Caller MUST equal `activationAdmin()` (else
/// `Unauthorized`). Reverts with `AlreadyDisabled` if the feature
/// is already disabled; reverts with `StaticCallNotAllowed` if
/// invoked under `STATICCALL`. Emits `FeatureDisabled` on success.
function disable(bytes32 feature) external;
}