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
2 changes: 1 addition & 1 deletion pkg/bindings/BN254CertificateVerifier/binding.go

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

112 changes: 87 additions & 25 deletions pkg/bindings/IOperatorTableUpdater/binding.go

Large diffs are not rendered by default.

114 changes: 88 additions & 26 deletions pkg/bindings/OperatorTableUpdater/binding.go

Large diffs are not rendered by default.

112 changes: 87 additions & 25 deletions pkg/bindings/OperatorTableUpdaterStorage/binding.go

Large diffs are not rendered by default.

32 changes: 30 additions & 2 deletions src/contracts/interfaces/IOperatorTableUpdater.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ interface IOperatorTableUpdater is
* @param globalTableRootCert certificate of the root
* @param globalTableRoot merkle root of all operatorSet tables
* @param referenceTimestamp timestamp of the root
* @param referenceBlockNumber block number of the root
* @dev Any entity can submit with a valid certificate signed off by the `globalRootConfirmerSet`
* @dev The `msgHash` in the `globalOperatorTableRootCert` is the hash of the `globalOperatorTableRoot`
*/
function confirmGlobalTableRoot(
BN254Certificate calldata globalTableRootCert,
bytes32 globalTableRoot,
uint32 referenceTimestamp
uint32 referenceTimestamp,
uint32 referenceBlockNumber
) external;

/**
Expand Down Expand Up @@ -145,15 +147,41 @@ interface IOperatorTableUpdater is
*/
function getLatestReferenceTimestamp() external view returns (uint32);

/**
* @notice Get the latest reference block number
* @return The latest reference block number
*/
function getLatestReferenceBlockNumber() external view returns (uint32);

/**
* @notice Get the reference block number for a given reference timestamp
* @param referenceTimestamp the reference timestamp
* @return The reference block number for the given reference timestamp
*/
function getReferenceBlockNumberByTimestamp(
uint32 referenceTimestamp
) external view returns (uint32);

/**
* @notice Get the reference timestamp for a given reference block number
* @param referenceBlockNumber the reference block number
* @return The reference timestamp for the given reference block number
*/
function getReferenceTimestampByBlockNumber(
uint32 referenceBlockNumber
) external view returns (uint32);

/**
* @notice Get the message hash for the certificate of a global table root update
* @param globalTableRoot the global table root
* @param referenceTimestamp the reference timestamp
* @param referenceBlockNumber the reference block number
* @return The message hash for a global table root
*/
function getGlobalTableUpdateMessageHash(
bytes32 globalTableRoot,
uint32 referenceTimestamp
uint32 referenceTimestamp,
uint32 referenceBlockNumber
) external view returns (bytes32);

/**
Expand Down
36 changes: 31 additions & 5 deletions src/contracts/multichain/OperatorTableUpdater.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
function confirmGlobalTableRoot(
BN254Certificate calldata globalTableRootCert,
bytes32 globalTableRoot,
uint32 referenceTimestamp
uint32 referenceTimestamp,
uint32 referenceBlockNumber
) external {
// Table roots can only be updated for current or past timestamps and after the latest reference timestamp
require(referenceTimestamp <= block.timestamp, GlobalTableRootInFuture());
require(referenceTimestamp > _latestReferenceTimestamp, GlobalTableRootStale());
require(
globalTableRootCert.messageHash == getGlobalTableUpdateMessageHash(globalTableRoot, referenceTimestamp),
globalTableRootCert.messageHash
== getGlobalTableUpdateMessageHash(globalTableRoot, referenceTimestamp, referenceBlockNumber),
InvalidMessageHash()
);

Expand All @@ -82,8 +84,10 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl

require(isValid, CertificateInvalid());

// Update the global table root
// Update the global table root & reference timestamps
_latestReferenceTimestamp = referenceTimestamp;
_referenceBlockNumbers[referenceTimestamp] = referenceBlockNumber;
_referenceTimestamps[referenceBlockNumber] = referenceTimestamp;
_globalTableRoots[referenceTimestamp] = globalTableRoot;

emit NewGlobalTableRoot(referenceTimestamp, globalTableRoot);
Expand Down Expand Up @@ -195,12 +199,34 @@ contract OperatorTableUpdater is Initializable, OwnableUpgradeable, OperatorTabl
return _latestReferenceTimestamp;
}

/// @inheritdoc IOperatorTableUpdater
function getLatestReferenceBlockNumber() external view returns (uint32) {
return _referenceBlockNumbers[_latestReferenceTimestamp];
}

/// @inheritdoc IOperatorTableUpdater
function getReferenceBlockNumberByTimestamp(
uint32 referenceTimestamp
) external view returns (uint32) {
return _referenceBlockNumbers[referenceTimestamp];
}

/// @inheritdoc IOperatorTableUpdater
function getReferenceTimestampByBlockNumber(
uint32 referenceBlockNumber
) external view returns (uint32) {
return _referenceTimestamps[referenceBlockNumber];
}

/// @inheritdoc IOperatorTableUpdater
function getGlobalTableUpdateMessageHash(
bytes32 globalTableRoot,
uint32 referenceTimestamp
uint32 referenceTimestamp,
uint32 referenceBlockNumber
) public pure returns (bytes32) {
return keccak256(abi.encode(GLOBAL_TABLE_ROOT_CERT_TYPEHASH, globalTableRoot, referenceTimestamp));
return keccak256(
abi.encode(GLOBAL_TABLE_ROOT_CERT_TYPEHASH, globalTableRoot, referenceTimestamp, referenceBlockNumber)
);
}

/// @inheritdoc IOperatorTableUpdater
Expand Down
8 changes: 7 additions & 1 deletion src/contracts/multichain/OperatorTableUpdaterStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ abstract contract OperatorTableUpdaterStorage is IOperatorTableUpdater {
/// @notice The global table roots by timestamp
mapping(uint32 timestamp => bytes32 globalTableRoot) internal _globalTableRoots;

/// @notice Mapping from latest reference timestamp to reference block number
mapping(uint32 referenceTimestamp => uint32 referenceBlockNumber) internal _referenceBlockNumbers;

/// @notice Mapping from reference block number to reference timestamp
mapping(uint32 referenceBlockNumber => uint32 referenceTimestamp) internal _referenceTimestamps;

// Constructor
constructor(
IBN254CertificateVerifier _bn254CertificateVerifier,
Expand All @@ -50,5 +56,5 @@ abstract contract OperatorTableUpdaterStorage is IOperatorTableUpdater {
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[45] private __gap;
uint256[44] private __gap;
}
68 changes: 68 additions & 0 deletions src/test/tree/OperatorTableUpdaterUnit.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
.
└── OperatorTableUpdater (**** denotes that integration tests are needed to fully validate path)
├── when initialize is called
│ ├── given that the contract is already initialized
│ │ └── it should revert
│ └── given that the contract is not initialized
│ └── it should set the owner, global root confirmer set, threshold, and update operator table & emit events
├── when confirmGlobalTableRoot is called
│ ├── given that the reference timestamp is in the future
│ │ └── it should revert with GlobalTableRootInFuture
│ ├── given that the reference timestamp is not greater than latest
│ │ └── it should revert with GlobalTableRootStale
│ ├── given that the message hash is invalid
│ │ └── it should revert with InvalidMessageHash
│ ├── given that the certificate is invalid
│ │ └── it should revert with CertificateInvalid
│ └── given that all parameters are valid
│ └── it should update global table root, reference timestamp, block number mappings (both directions) & emit NewGlobalTableRoot event
├── 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 does not match the reference timestamp
│ │ └── it should revert with InvalidGlobalTableRoot
│ ├── given that the merkle proof is invalid
│ │ └── it should revert with InvalidOperatorSetProof
│ ├── given that the curve type is invalid
│ │ └── it should revert with InvalidCurveType
│ ├── given that the curve type is BN254
│ │ └── it should call bn254CertificateVerifier.updateOperatorTable with decoded info
│ └── given that the curve type is ECDSA
│ └── it should call ecdsaCertificateVerifier.updateOperatorTable with decoded info
├── when setGlobalRootConfirmerSet 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 & emit GlobalRootConfirmerSetUpdated event
├── when setGlobalRootConfirmationThreshold is called
│ ├── given that the caller is not the owner
│ │ └── it should revert
│ ├── given that the threshold exceeds MAX_BPS
│ │ └── it should revert with InvalidConfirmationThreshold
│ └── given that the caller is owner and threshold is valid
│ └── it should update the threshold & emit GlobalRootConfirmationThresholdUpdated event
├── when getGlobalTableRootByTimestamp is called
│ └── it should return the global table root for the given timestamp
├── when getCurrentGlobalTableRoot is called
│ └── it should return the global table root for the latest reference timestamp
├── when getGlobalRootConfirmerSet is called
│ └── it should return the current global root confirmer set
├── when getCertificateVerifier is called
│ ├── given that the curve type is BN254
│ │ └── it should return the bn254CertificateVerifier address
│ ├── given that the curve type is ECDSA
│ │ └── it should return the ecdsaCertificateVerifier address
│ └── given that the curve type is invalid
│ └── it should revert with InvalidCurveType
├── when getLatestReferenceTimestamp is called
│ └── it should return the latest reference timestamp
├── when getLatestReferenceBlockNumber is called
│ └── it should return the reference block number for the latest reference timestamp
├── when getReferenceBlockNumberByTimestamp is called
│ └── it should return the reference block number for the given timestamp
├── when getReferenceTimestampByBlockNumber is called
│ └── it should return the reference timestamp for the given block number
├── 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
Loading