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 pkg/bindings/BN254CertificateVerifier/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/BN254CertificateVerifierStorage/binding.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pkg/bindings/ECDSACertificateVerifier/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/ECDSACertificateVerifierStorage/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/IBN254CertificateVerifier/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/IBaseCertificateVerifier/binding.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/bindings/IECDSACertificateVerifier/binding.go

Large diffs are not rendered by default.

264 changes: 263 additions & 1 deletion pkg/bindings/IOperatorTableUpdater/binding.go

Large diffs are not rendered by default.

252 changes: 250 additions & 2 deletions pkg/bindings/OperatorTableUpdater/binding.go

Large diffs are not rendered by default.

264 changes: 263 additions & 1 deletion pkg/bindings/OperatorTableUpdaterStorage/binding.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/contracts/interfaces/IBaseCertificateVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ interface IBaseCertificateVerifierErrors {
error ReferenceTimestampDoesNotExist();
/// @notice Thrown when certificate verification fails
error VerificationFailed();
/// @notice Thrown when the global table root is disabled
error RootDisabled();
}

/// @notice A base interface that verifies certificates for a given operatorSet
Expand Down
50 changes: 50 additions & 0 deletions src/contracts/interfaces/IOperatorTableUpdater.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ interface IOperatorTableUpdaterErrors {
error InvalidConfirmationThreshold();
/// @notice Thrown when the curve type is invalid
error InvalidCurveType();
/// @notice Thrown when a root is invalid
error InvalidRoot();
}

interface IOperatorTableUpdaterEvents {
Expand All @@ -50,6 +52,12 @@ interface IOperatorTableUpdaterEvents {
* @param bps The threshold, in bps, for a global root to be signed off on and updated
*/
event GlobalRootConfirmationThresholdUpdated(uint16 bps);

/**
* @notice Emitted when a global table root is disabled
* @param globalTableRoot the global table root that was disabled
*/
event GlobalRootDisabled(bytes32 indexed globalTableRoot);
}

interface IOperatorTableUpdater is
Expand Down Expand Up @@ -94,6 +102,21 @@ interface IOperatorTableUpdater is
uint16 bps
) external;

/**
* @notice Updates the operator table for the global root confirmer set
* @param referenceTimestamp The reference timestamp of the operator table update
* @param globalRootConfirmerSetInfo The operatorSetInfo for the global root confirmer set
* @param globalRootConfirmerSetConfig The operatorSetConfig for the global root confirmer set
* @dev We have a separate function for updating this operatorSet since it's not transported and updated
* in the same way as the other operatorSets
* @dev Only callable by the owner of the contract
*/
function updateGlobalRootConfirmerSet(
uint32 referenceTimestamp,
BN254OperatorSetInfo calldata globalRootConfirmerSetInfo,
OperatorSetConfig calldata globalRootConfirmerSetConfig
) external;

/**
* @notice Updates an operator table
* @param referenceTimestamp the reference block number of the globalTableRoot
Expand All @@ -111,6 +134,15 @@ interface IOperatorTableUpdater is
bytes calldata operatorTableBytes
) external;

/**
* @notice Disables a global table root
* @param globalTableRoot the global table root to disable
* @dev Only callable by the owner of the contract
*/
function disableRoot(
bytes32 globalTableRoot
) external;

/**
* @notice Get the current global table root
* @return globalTableRoot the current global table root
Expand Down Expand Up @@ -190,4 +222,22 @@ interface IOperatorTableUpdater is
* @dev In V1, we only update the table of the global root confirmer set on initial deployment, and never update it again.
*/
function getGlobalConfirmerSetReferenceTimestamp() external view returns (uint32);

/**
* @notice Get the validity status of a global table root
* @param globalTableRoot the global table root
* @return The validity status of the global table root
*/
function isRootValid(
bytes32 globalTableRoot
) external view returns (bool);

/**
* @notice Get the validity status of a global table root by timestamp
* @param referenceTimestamp the reference timestamp
* @return The validity status of the global table root
*/
function isRootValidByTimestamp(
uint32 referenceTimestamp
) external view returns (bool);
}
3 changes: 3 additions & 0 deletions src/contracts/multichain/BN254CertificateVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ contract BN254CertificateVerifier is Initializable, BN254CertificateVerifierStor
function _validateCertificateTimestamp(bytes32 operatorSetKey, uint32 referenceTimestamp) internal view {
uint32 maxStaleness = _maxStalenessPeriods[operatorSetKey];
require(maxStaleness == 0 || block.timestamp <= referenceTimestamp + maxStaleness, CertificateStale());

// Assert that the root that corresponds to the reference timestamp is not disabled
require(operatorTableUpdater.isRootValidByTimestamp(referenceTimestamp), RootDisabled());
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/contracts/multichain/ECDSACertificateVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ contract ECDSACertificateVerifier is Initializable, ECDSACertificateVerifierStor
// Assert that the reference timestamp exists
require(_latestReferenceTimestamps[operatorSetKey] == cert.referenceTimestamp, ReferenceTimestampDoesNotExist());

// Assert that the root that corresponds to the reference timestamp is not disabled
require(operatorTableUpdater.isRootValidByTimestamp(cert.referenceTimestamp), RootDisabled());

// Get the total stakes
uint256[] memory totalStakes = getTotalStakes(operatorSet, cert.referenceTimestamp);
uint256[] memory signedStakes = new uint256[](totalStakes.length);
Expand Down
60 changes: 55 additions & 5 deletions src/contracts/multichain/OperatorTableUpdater.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
_transferOwnership(owner);
_setGlobalRootConfirmerSet(_globalRootConfirmerSet);
_setGlobalRootConfirmationThreshold(_globalRootConfirmationThreshold);

// Update the operator table for the global root confirmer set
bn254CertificateVerifier.updateOperatorTable(
_globalRootConfirmerSet, referenceTimestamp, globalRootConfirmerSetInfo, globalRootConfirmerSetConfig
);
_updateGlobalRootConfirmerSet(referenceTimestamp, globalRootConfirmerSetInfo, globalRootConfirmerSetConfig);

// Set the latest reference timestamp
_latestReferenceTimestamp = referenceTimestamp;
Expand Down Expand Up @@ -89,6 +85,7 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
_referenceBlockNumbers[referenceTimestamp] = referenceBlockNumber;
_referenceTimestamps[referenceBlockNumber] = referenceTimestamp;
_globalTableRoots[referenceTimestamp] = globalTableRoot;
_isRootValid[globalTableRoot] = true;

emit NewGlobalTableRoot(referenceTimestamp, globalTableRoot);
}
Expand All @@ -108,6 +105,9 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
bytes memory operatorTableInfo
) = _decodeOperatorTableBytes(operatorTableBytes);

// Check that the `globalTableRoot` is not disabled
require(_isRootValid[globalTableRoot], InvalidRoot());

// Check that the `referenceTimestamp` is greater than the latest reference timestamp
require(
referenceTimestamp
Expand Down Expand Up @@ -158,6 +158,26 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
_setGlobalRootConfirmationThreshold(bps);
}

/// @inheritdoc IOperatorTableUpdater
function disableRoot(
bytes32 globalTableRoot
) external onlyOwner {
// Check that the root already exists and is not disabled
require(_isRootValid[globalTableRoot], InvalidRoot());

_isRootValid[globalTableRoot] = false;
emit GlobalRootDisabled(globalTableRoot);
}

/// @inheritdoc IOperatorTableUpdater
function updateGlobalRootConfirmerSet(
uint32 referenceTimestamp,
BN254OperatorSetInfo calldata globalRootConfirmerSetInfo,
OperatorSetConfig calldata globalRootConfirmerSetConfig
) external onlyOwner {
_updateGlobalRootConfirmerSet(referenceTimestamp, globalRootConfirmerSetInfo, globalRootConfirmerSetConfig);
}

/**
*
* GETTERS
Expand Down Expand Up @@ -236,6 +256,20 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
);
}

/// @inheritdoc IOperatorTableUpdater
function isRootValid(
bytes32 globalTableRoot
) public view returns (bool) {
return _isRootValid[globalTableRoot];
}

/// @inheritdoc IOperatorTableUpdater
function isRootValidByTimestamp(
uint32 referenceTimestamp
) external view returns (bool) {
return _isRootValid[_globalTableRoots[referenceTimestamp]];
}

/**
*
* INTERNAL HELPERS
Expand Down Expand Up @@ -296,6 +330,22 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
emit GlobalRootConfirmationThresholdUpdated(bps);
}

/**
* @notice Updates the operator table for the global root confirmer set
* @param referenceTimestamp The reference timestamp of the operator table update
* @param globalRootConfirmerSetInfo The operatorSetInfo for the global root confirmer set
* @param globalRootConfirmerSetConfig The operatorSetConfig for the global root confirmer set
*/
function _updateGlobalRootConfirmerSet(
uint32 referenceTimestamp,
BN254OperatorSetInfo calldata globalRootConfirmerSetInfo,
OperatorSetConfig calldata globalRootConfirmerSetConfig
) internal {
bn254CertificateVerifier.updateOperatorTable(
_globalRootConfirmerSet, referenceTimestamp, globalRootConfirmerSetInfo, globalRootConfirmerSetConfig
);
}

/**
* @notice Gets the operator table info from a bytes array
* @param operatorTable The bytes containing the operator table
Expand Down
5 changes: 4 additions & 1 deletion src/contracts/multichain/OperatorTableUpdaterStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ abstract contract OperatorTableUpdaterStorage is IOperatorTableUpdater {
// Constants

bytes32 public constant GLOBAL_TABLE_ROOT_CERT_TYPEHASH =
keccak256("GlobalTableRootCert(bytes32 globalTableRoot,uint32 referenceTimestamp)");
keccak256("GlobalTableRootCert(bytes32 globalTableRoot,uint32 referenceTimestamp,uint32 referenceBlockNumber)");

/// @notice The maximum BPS value
uint16 public constant MAX_BPS = 10_000;
Expand Down Expand Up @@ -42,6 +42,9 @@ abstract contract OperatorTableUpdaterStorage is IOperatorTableUpdater {
/// @notice Mapping from reference block number to reference timestamp
mapping(uint32 referenceBlockNumber => uint32 referenceTimestamp) internal _referenceTimestamps;

/// @notice Mapping from global table root to validity status
mapping(bytes32 globalTableRoot => bool valid) internal _isRootValid;

// Constructor
constructor(
IBN254CertificateVerifier _bn254CertificateVerifier,
Expand Down
11 changes: 11 additions & 0 deletions src/test/mocks/OperatorTableUpdaterMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@ import "src/contracts/interfaces/IOperatorTableUpdater.sol";
contract OperatorTableUpdaterMock is Test {
receive() external payable {}
fallback() external payable {}

mapping(uint32 referenceTimestamp => bool valid) internal _invalidRoots;

function isRootValidByTimestamp(uint32 referenceTimestamp) external view returns (bool) {
// If the root is invalid, return false
return !_invalidRoots[referenceTimestamp];
}

function invalidateRoot(uint32 referenceTimestamp) external {
_invalidRoots[referenceTimestamp] = true;
}
}
20 changes: 19 additions & 1 deletion src/test/tree/OperatorTableUpdaterUnit.tree
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
├── when updateOperatorTable is called
│ ├── given that the reference timestamp is not greater than operator set's latest
│ │ └── it should revert with TableUpdateForPastTimestamp
│ ├── given that the global table root is not valid
│ │ └── it should revert with InvalidRoot
│ ├── given that the global table root does not match the reference timestamp
│ │ └── it should revert with InvalidGlobalTableRoot
│ ├── given that the merkle proof is invalid
Expand All @@ -41,6 +43,22 @@
│ │ └── it should revert with InvalidConfirmationThreshold
│ └── given that the caller is owner and threshold is valid
│ └── it should update the threshold & emit GlobalRootConfirmationThresholdUpdated event
├── when disableRoot is called
│ ├── given that the caller is not the owner
│ │ └── it should revert
│ ├── given that the root is invalid or doesn't exist
│ │ └── it should revert with InvalidRoot
│ └── given that the caller is the owner and root is valid
│ └── it should disable the root & emit GlobalRootDisabled event
├── when updateGlobalRootConfirmerSet is called
│ ├── given that the caller is not the owner
│ │ └── it should revert
│ └── given that the caller is the owner
│ └── it should update the global root confirmer set table
├── when isRootValid(bytes32) is called
│ └── it should return the validity status of the given global table root
├── when isRootValidByTimestamp(uint32) is called
│ └── it should return the validity status of the global table root at the given reference timestamp
├── when getGlobalTableRootByTimestamp is called
│ └── it should return the global table root for the given timestamp
├── when getCurrentGlobalTableRoot is called
Expand All @@ -65,4 +83,4 @@
├── when getGlobalTableUpdateMessageHash is called
│ └── it should return the keccak256 hash of encoded typehash, root, timestamp, and block number
└── when getGlobalConfirmerSetReferenceTimestamp is called
└── it should return the latest reference timestamp for the global root confirmer set
└── it should return the latest reference timestamp for the global root confirmer set
14 changes: 14 additions & 0 deletions src/test/unit/BN254CertificateVerifierUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,20 @@ contract BN254CertificateVerifierUnitTests_verifyCertificate is BN254Certificate
verifier.verifyCertificate(defaultOperatorSet, cert);
}

function test_revert_rootDisabled() public {
// Initialize operator table with a valid root
uint32 referenceTimestamp = _initializeOperatorTableBase();

// Mock the operatorTableUpdater to return false for isRootValidByTimestamp
operatorTableUpdaterMock.invalidateRoot(referenceTimestamp);

BN254Certificate memory cert;
cert.referenceTimestamp = referenceTimestamp;

vm.expectRevert(RootDisabled.selector);
verifier.verifyCertificate(defaultOperatorSet, cert);
}

function test_revert_invalidOperatorIndex() public {
// Update operator table
(BN254OperatorInfo[] memory operatorInfos, uint32[] memory nonSignerIndices, BN254.G1Point memory signature) =
Expand Down
Loading