CuttingBoardManager
Inherits: Upgradeable
Title: CuttingBoardManager
Manages validator cutting board updates for NFT holders with 2-step proposal/approval process
Acts as a KEEPER_ROLE intermediary between NFT holders and the Infrared contract. NFT holders propose cutting board updates, keepers approve them. This contract must be granted KEEPER_ROLE on the Infrared contract.
Uses ERC-7201 namespaced storage pattern for upgradeability
State Variables
CUTTING_BOARD_MANAGER_STORAGE_LOCATION
bytes32 private constant CUTTING_BOARD_MANAGER_STORAGE_LOCATION =
0x9fd5aaf305ec5d9d4acf2d4a2d8f6d67e82cf32b8c76929329ec5790707b7d00
__gap
uint256[20] private __gap
Functions
_getCuttingBoardManagerStorage
function _getCuttingBoardManagerStorage()
private
pure
returns (CuttingBoardManagerStorage storage $);
infrared
function infrared() public view returns (IInfrared);
controlNFT
function controlNFT() public view returns (CuttingBoardNFT);
chef
function chef() public view returns (IBeraChefVaultCheck);
proposalValidityDuration
function proposalValidityDuration() public view returns (uint256);
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialize the CuttingBoardManager
function initialize(
address _infrared,
address _controlNFT,
address _chef,
address _governance,
address _keeper,
uint256 _proposalValidityDuration
) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
_infrared | address | Address of the Infrared contract |
_controlNFT | address | Address of the CuttingBoardNFT contract |
_chef | address | Address of the BeraChef contract |
_governance | address | Address of governance (receives GOVERNANCE_ROLE and DEFAULT_ADMIN_ROLE) |
_keeper | address | Address of the keeper (receives KEEPER_ROLE) |
_proposalValidityDuration | uint256 | Duration in seconds for which proposals are valid |
proposeCuttingBoard
Propose a cutting board update for a validator controlled by NFT
function proposeCuttingBoard(
uint256 tokenId,
IBeraChef.Weight[] calldata weights
) external virtual whenNotPaused;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | NFT token ID proving control rights |
weights | IBeraChef.Weight[] | New cutting board distribution |
approveCuttingBoard
Approve a cutting board proposal (keeper only)
function approveCuttingBoard(uint256 tokenId)
external
virtual
whenNotPaused
onlyKeeper;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | NFT token ID of the proposal to approve |
cancelProposal
Cancel a pending proposal
function cancelProposal(uint256 tokenId) external virtual;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | NFT token ID of the proposal to cancel |
getLastUpdateBlock
Get the last update block for an NFT
function getLastUpdateBlock(uint256 tokenId)
external
view
virtual
returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The block number of the last update |
getPendingProposalTokenIds
Get all token IDs that have valid pending proposals awaiting keeper approval
Iterates through all minted NFTs to find those with non-expired proposals. Uses a two-pass approach: first counts valid proposals, then fills the array. Useful for keepers to discover which proposals need review.
function getPendingProposalTokenIds()
external
view
virtual
returns (uint256[] memory tokenIds);
Returns
| Name | Type | Description |
|---|---|---|
tokenIds | uint256[] | Array of token IDs with pending proposals (may be empty) |
_isPendingProposal
Check if a token ID has a valid pending proposal
function _isPendingProposal(
CuttingBoardManagerStorage storage $,
uint256 tokenId
) internal view virtual returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
$ | CuttingBoardManagerStorage | Storage reference |
tokenId | uint256 | The token ID to check |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if there is a valid pending proposal for this token |
_checkForDuplicateReceivers
Duplicate vault check in proposed cutting board
function _checkForDuplicateReceivers(IBeraChef.Weight[] calldata weights)
internal
pure;
getProposal
Get proposal details for a token ID
function getProposal(uint256 tokenId)
external
view
virtual
returns (
address proposer,
uint64 startBlock,
uint64 proposedAt,
bool exists,
IBeraChef.Weight[] memory weights
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID |
Returns
| Name | Type | Description |
|---|---|---|
proposer | address | Address of the proposer |
startBlock | uint64 | Block when allocation activates |
proposedAt | uint64 | Timestamp when proposed |
exists | bool | Whether the proposal exists |
weights | IBeraChef.Weight[] | The proposed weights |
isProposalExpired
Check if a proposal has expired
function isProposalExpired(uint256 tokenId)
external
view
virtual
returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if the proposal exists and has expired |
canProposeNow
Check if an NFT can propose now
function canProposeNow(uint256 tokenId)
external
view
virtual
returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if the NFT can propose a cutting board update |
setProposalValidityDuration
Update proposal validity duration
proposal duration should be less than allocation duration, though not enforced
function setProposalValidityDuration(uint256 _proposalValidityDuration)
external
virtual
onlyGovernor;
Parameters
| Name | Type | Description |
|---|---|---|
_proposalValidityDuration | uint256 | New duration in seconds |
revokeControl
Emergency: Invalidate an NFT's control rights
function revokeControl(uint256 tokenId) external virtual onlyGovernor;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID to invalidate |
_validateWeights
Validate cutting board weights
function _validateWeights(IBeraChef.Weight[] calldata weights)
internal
virtual;
Parameters
| Name | Type | Description |
|---|---|---|
weights | IBeraChef.Weight[] | The weights to validate |
Events
CuttingBoardProposed
Emitted when a cutting board proposal is created
event CuttingBoardProposed(
uint256 indexed tokenId,
address indexed proposer,
bytes validatorPubkey,
IBeraChef.Weight[] weights
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID used for authorization |
proposer | address | The address that created the proposal |
validatorPubkey | bytes | The validator whose cutting board will be updated |
weights | IBeraChef.Weight[] | The proposed cutting board weights |
CuttingBoardApproved
Emitted when a cutting board proposal is approved
event CuttingBoardApproved(
uint256 indexed tokenId,
address indexed approver,
bytes validatorPubkey,
uint64 startBlock,
IBeraChef.Weight[] weights
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID used for authorization |
approver | address | The keeper who approved the proposal |
validatorPubkey | bytes | The validator whose cutting board was updated |
startBlock | uint64 | The block when the new allocation activates |
weights | IBeraChef.Weight[] | The new cutting board weights |
ProposalCancelled
Emitted when a cutting board proposal is cancelled
event ProposalCancelled(uint256 indexed tokenId, address indexed canceller);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The NFT token ID |
canceller | address | The address that cancelled the proposal |
ProposalValidityDurationUpdated
Emitted when the proposal validity duration is changed
event ProposalValidityDurationUpdated(
uint256 oldDuration, uint256 newDuration
);
Parameters
| Name | Type | Description |
|---|---|---|
oldDuration | uint256 | The previous duration in seconds |
newDuration | uint256 | The new duration in seconds |
Errors
NotNFTOwner
Thrown when caller is not the NFT owner
error NotNFTOwner();
NFTExpired
Thrown when NFT has expired
error NFTExpired();
NFTInactive
Thrown when NFT is inactive
error NFTInactive();
UpdateTooSoon
Thrown when trying to update too soon after last update
error UpdateTooSoon();
InvalidWeightSum
Thrown when weights don't sum to exactly 10000 (100%)
error InvalidWeightSum();
VaultNotWhitelisted
Thrown when a vault in weights is not whitelisted
error VaultNotWhitelisted();
EmptyWeights
Thrown when weights array is empty
error EmptyWeights();
InvalidAddress
Thrown when invalid address provided in initializer
error InvalidAddress();
InvalidProposer
Thrown when ownership changes and old proposer is no longer valid
error InvalidProposer();
NoProposalExists
Thrown when no proposal exists for the given tokenId
error NoProposalExists();
ProposalAlreadyExists
Thrown when a proposal already exists for the tokenId
error ProposalAlreadyExists();
ProposalExpired
Thrown when a proposal has expired
error ProposalExpired();
NotProposer
Thrown when caller is not the proposer
error NotProposer();
TooManyWeights
Thrown when cutting board allocation has too many vaults
error TooManyWeights();
InvalidWeight
Thrown when cutting board vault has either 0 or a percentage greater than max defined by berachain
error InvalidWeight();
AlreadyQueue
Thrown when outstanding cutting board queue for validator prevents new update
error AlreadyQueue();
DuplicateReceiver
Thrown when duplicate vaults provided in cutting board
error DuplicateReceiver();
InvalidDuration
Thrown when proposalValidityDuration is zero
error InvalidDuration();
Structs
CuttingBoardManagerStorage
Note: storage-location: erc7201:infrared.storage.CuttingBoardManager
struct CuttingBoardManagerStorage {
/// @notice Reference to the Infrared core contract
IInfrared infrared;
/// @notice Reference to the CuttingBoardNFT contract
CuttingBoardNFT controlNFT;
/// @notice Reference to BeraChef for vault whitelist validation
IBeraChefVaultCheck chef;
/// @notice Duration in seconds for which a proposal remains valid
uint256 proposalValidityDuration;
/// @notice Last block number an NFT updated its cutting board
mapping(uint256 => uint256) lastUpdateBlock;
/// @notice Active proposals by tokenId
mapping(uint256 => Proposal) proposals;
}
Proposal
Represents a cutting board update proposal
struct Proposal {
address proposer;
uint64 startBlock;
uint64 proposedAt;
bool exists;
IBeraChef.Weight[] weights;
}