// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
import {ICommitStore} from "./interfaces/ICommitStore.sol";
import {IARM} from "./interfaces/IARM.sol";
import {IPriceRegistry} from "./interfaces/IPriceRegistry.sol";
import {OCR2Base} from "./ocr/OCR2Base.sol";
import {Internal} from "./libraries/Internal.sol";
import {MerkleMultiProof} from "./libraries/MerkleMultiProof.sol";
contract CommitStore is ICommitStore, ITypeAndVersion, OCR2Base {
error StaleReport();
error PausedError();
error InvalidInterval(Interval interval);
error InvalidRoot();
error InvalidCommitStoreConfig();
error BadARMSignal();
error RootAlreadyCommitted();
event Paused(address account);
event Unpaused(address account);
/// @dev RMN depends on this event, if changing, please notify the RMN maintainers.
event ReportAccepted(CommitReport report);
event ConfigSet(StaticConfig staticConfig, DynamicConfig dynamicConfig);
event RootRemoved(bytes32 root);
/// @notice Static commit store config
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct StaticConfig {
uint64 chainSelector; // ───────╮ Destination chainSelector
uint64 sourceChainSelector; // ─╯ Source chainSelector
address onRamp; // OnRamp address on the source chain
address armProxy; // ARM proxy address
}
/// @notice Dynamic commit store config
struct DynamicConfig {
address priceRegistry; // Price registry address on the destination chain
}
/// @notice a sequenceNumber interval
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct Interval {
uint64 min; // ───╮ Minimum sequence number, inclusive
uint64 max; // ───╯ Maximum sequence number, inclusive
}
/// @notice Report that is committed by the observing DON at the committing phase
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct CommitReport {
Internal.PriceUpdates priceUpdates;
Interval interval;
bytes32 merkleRoot;
}
// STATIC CONFIG
// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
string public constant override typeAndVersion = "CommitStore 1.2.0";
// Chain ID of this chain
uint64 internal immutable i_chainSelector;
// Chain ID of the source chain
uint64 internal immutable i_sourceChainSelector;
// The onRamp address on the source chain
address internal immutable i_onRamp;
// The address of the arm proxy
address internal immutable i_armProxy;
// DYNAMIC CONFIG
// The dynamic commitStore config
DynamicConfig internal s_dynamicConfig;
// STATE
// The min sequence number expected for future messages
uint64 private s_minSeqNr = 1;
/// @dev The epoch and round of the last report
uint40 private s_latestPriceEpochAndRound;
/// @dev Whether this OnRamp is paused or not
bool private s_paused = false;
// merkleRoot => timestamp when received
mapping(bytes32 merkleRoot => uint256 timestamp) private s_roots;
/// @param staticConfig Containing the static part of the commitStore config
/// @dev When instantiating OCR2Base we set UNIQUE_REPORTS to false, which means
/// that we do not require 2f+1 signatures on a report, only f+1 to save gas. 2f+1 is required
/// only if one must strictly ensure that for a given round there is only one valid report ever generated by
/// the DON. In our case additional valid reports (i.e. approved by >= f+1 oracles) are not a problem, as they will
/// will either be ignored (reverted as an invalid interval) or will be accepted as an additional valid price update.
constructor(StaticConfig memory staticConfig) OCR2Base(false) {
if (
staticConfig.onRamp == address(0) ||
staticConfig.chainSelector == 0 ||
staticConfig.sourceChainSelector == 0 ||
staticConfig.armProxy == address(0)
) revert InvalidCommitStoreConfig();
i_chainSelector = staticConfig.chainSelector;
i_sourceChainSelector = staticConfig.sourceChainSelector;
i_onRamp = staticConfig.onRamp;
i_armProxy = staticConfig.armProxy;
}
// ================================================================
// │ Verification │
// ================================================================
/// @notice Returns the next expected sequence number.
/// @return the next expected sequenceNumber.
function getExpectedNextSequenceNumber() external view returns (uint64) {
return s_minSeqNr;
}
/// @notice Sets the minimum sequence number.
/// @param minSeqNr The new minimum sequence number.
function setMinSeqNr(uint64 minSeqNr) external onlyOwner {
s_minSeqNr = minSeqNr;
}
/// @notice Returns the epoch and round of the last price update.
/// @return the latest price epoch and round.
function getLatestPriceEpochAndRound() public view returns (uint64) {
return s_latestPriceEpochAndRound;
}
/// @notice Sets the latest epoch and round for price update.
/// @param latestPriceEpochAndRound The new epoch and round for prices.
function setLatestPriceEpochAndRound(uint40 latestPriceEpochAndRound) external onlyOwner {
s_latestPriceEpochAndRound = latestPriceEpochAndRound;
}
/// @notice Returns the timestamp of a potentially previously committed merkle root.
/// If the root was never committed 0 will be returned.
/// @param root The merkle root to check the commit status for.
/// @return the timestamp of the committed root or zero in the case that it was never
/// committed.
function getMerkleRoot(bytes32 root) external view returns (uint256) {
return s_roots[root];
}
/// @notice Returns if a root is blessed or not.
/// @param root The merkle root to check the blessing status for.
/// @return whether the root is blessed or not.
function isBlessed(bytes32 root) public view returns (bool) {
return IARM(i_armProxy).isBlessed(IARM.TaggedRoot({commitStore: address(this), root: root}));
}
/// @notice Used by the owner in case an invalid sequence of roots has been
/// posted and needs to be removed. The interval in the report is trusted.
/// @param rootToReset The roots that will be reset. This function will only
/// reset roots that are not blessed.
function resetUnblessedRoots(bytes32[] calldata rootToReset) external onlyOwner {
for (uint256 i = 0; i < rootToReset.length; ++i) {
bytes32 root = rootToReset[i];
if (!isBlessed(root)) {
delete s_roots[root];
emit RootRemoved(root);
}
}
}
/// @inheritdoc ICommitStore
function verify(
bytes32[] calldata hashedLeaves,
bytes32[] calldata proofs,
uint256 proofFlagBits
) external view override whenNotPaused returns (uint256 timestamp) {
bytes32 root = MerkleMultiProof.merkleRoot(hashedLeaves, proofs, proofFlagBits);
// Only return non-zero if present and blessed.
if (!isBlessed(root)) {
return 0;
}
return s_roots[root];
}
/// @inheritdoc OCR2Base
/// @dev A commitReport can have two distinct parts (batched together to amortize the cost of checking sigs):
/// 1. Price updates
/// 2. A merkle root and sequence number interval
/// Both have their own, separate, staleness checks, with price updates using the epoch and round
/// number of the latest price update. The merkle root checks for staleness based on the seqNums.
/// They need to be separate because a price report for round t+2 might be included before a report
/// containing a merkle root for round t+1. This merkle root report for round t+1 is still valid
/// and should not be rejected. When a report with a stale root but valid price updates is submitted,
/// we are OK to revert to preserve the invariant that we always revert on invalid sequence number ranges.
/// If that happens, prices will be updates in later rounds.
function _report(bytes calldata encodedReport, uint40 epochAndRound) internal override whenNotPaused whenHealthy {
CommitReport memory report = abi.decode(encodedReport, (CommitReport));
// Check if the report contains price updates
if (report.priceUpdates.tokenPriceUpdates.length > 0 || report.priceUpdates.gasPriceUpdates.length > 0) {
// Check for price staleness based on the epoch and round
if (s_latestPriceEpochAndRound < epochAndRound) {
// If prices are not stale, update the latest epoch and round
s_latestPriceEpochAndRound = epochAndRound;
// And update the prices in the price registry
IPriceRegistry(s_dynamicConfig.priceRegistry).updatePrices(report.priceUpdates);
// If there is no root, the report only contained fee updated and
// we return to not revert on the empty root check below.
if (report.merkleRoot == bytes32(0)) return;
} else {
// If prices are stale and the report doesn't contain a root, this report
// does not have any valid information and we revert.
// If it does contain a merkle root, continue to the root checking section.
if (report.merkleRoot == bytes32(0)) revert StaleReport();
}
}
// If we reached this section, the report should contain a valid root
if (s_minSeqNr != report.interval.min || report.interval.min > report.interval.max)
revert InvalidInterval(report.interval);
if (report.merkleRoot == bytes32(0)) revert InvalidRoot();
// Disallow duplicate roots as that would reset the timestamp and
// delay potential manual execution.
if (s_roots[report.merkleRoot] != 0) revert RootAlreadyCommitted();
s_minSeqNr = report.interval.max + 1;
s_roots[report.merkleRoot] = block.timestamp;
emit ReportAccepted(report);
}
// ================================================================
// │ Config │
// ================================================================
/// @notice Returns the static commit store config.
/// @dev RMN depends on this function, if changing, please notify the RMN maintainers.
/// @return the configuration.
function getStaticConfig() external view returns (StaticConfig memory) {
return
StaticConfig({
chainSelector: i_chainSelector,
sourceChainSelector: i_sourceChainSelector,
onRamp: i_onRamp,
armProxy: i_armProxy
});
}
/// @notice Returns the dynamic commit store config.
/// @return the configuration.
function getDynamicConfig() external view returns (DynamicConfig memory) {
return s_dynamicConfig;
}
/// @notice Sets the dynamic config. This function is called during `setOCR2Config` flow
function _beforeSetConfig(bytes memory onchainConfig) internal override {
DynamicConfig memory dynamicConfig = abi.decode(onchainConfig, (DynamicConfig));
if (dynamicConfig.priceRegistry == address(0)) revert InvalidCommitStoreConfig();
s_dynamicConfig = dynamicConfig;
// When the OCR config changes, we reset the price epoch and round
// since epoch and rounds are scoped per config digest.
// Note that s_minSeqNr/roots do not need to be reset as the roots persist
// across reconfigurations and are de-duplicated separately.
s_latestPriceEpochAndRound = 0;
emit ConfigSet(
StaticConfig({
chainSelector: i_chainSelector,
sourceChainSelector: i_sourceChainSelector,
onRamp: i_onRamp,
armProxy: i_armProxy
}),
dynamicConfig
);
}
// ================================================================
// │ Access and ARM │
// ================================================================
/// @notice Single function to check the status of the commitStore.
function isUnpausedAndARMHealthy() external view returns (bool) {
return !IARM(i_armProxy).isCursed() && !s_paused;
}
/// @notice Support querying whether health checker is healthy.
function isARMHealthy() external view returns (bool) {
return !IARM(i_armProxy).isCursed();
}
/// @notice Ensure that the ARM has not emitted a bad signal, and that the latest heartbeat is not stale.
modifier whenHealthy() {
if (IARM(i_armProxy).isCursed()) revert BadARMSignal();
_;
}
/// @notice Modifier to make a function callable only when the contract is not paused.
modifier whenNotPaused() {
if (paused()) revert PausedError();
_;
}
/// @notice Returns true if the contract is paused, and false otherwise.
function paused() public view returns (bool) {
return s_paused;
}
/// @notice Pause the contract
/// @dev only callable by the owner
function pause() external onlyOwner {
s_paused = true;
emit Paused(msg.sender);
}
/// @notice Unpause the contract
/// @dev only callable by the owner
function unpause() external onlyOwner {
s_paused = false;
emit Unpaused(msg.sender);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ICommitStore {
/// @notice Returns timestamp of when root was accepted or 0 if verification fails.
/// @dev This method uses a merkle tree within a merkle tree, with the hashedLeaves,
/// proofs and proofFlagBits being used to get the root of the inner tree.
/// This root is then used as the singular leaf of the outer tree.
function verify(
bytes32[] calldata hashedLeaves,
bytes32[] calldata proofs,
uint256 proofFlagBits
) external view returns (uint256 timestamp);
/// @notice Returns the expected next sequence number
function getExpectedNextSequenceNumber() external view returns (uint64 sequenceNumber);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ITypeAndVersion {
function typeAndVersion() external pure returns (string memory);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import {OwnerIsCreator} from "../../shared/access/OwnerIsCreator.sol";
import {OCR2Abstract} from "./OCR2Abstract.sol";
/// @notice Onchain verification of reports from the offchain reporting protocol
/// @dev For details on its operation, see the offchain reporting protocol design
/// doc, which refers to this contract as simply the "contract".
abstract contract OCR2Base is OwnerIsCreator, OCR2Abstract {
error InvalidConfig(string message);
error WrongMessageLength(uint256 expected, uint256 actual);
error ConfigDigestMismatch(bytes32 expected, bytes32 actual);
error ForkedChain(uint256 expected, uint256 actual);
error WrongNumberOfSignatures();
error SignaturesOutOfRegistration();
error UnauthorizedTransmitter();
error UnauthorizedSigner();
error NonUniqueSignatures();
error OracleCannotBeZeroAddress();
// Packing these fields used on the hot path in a ConfigInfo variable reduces the
// retrieval of all of them to a minimum number of SLOADs.
struct ConfigInfo {
bytes32 latestConfigDigest;
uint8 f;
uint8 n;
}
// Used for s_oracles[a].role, where a is an address, to track the purpose
// of the address, or to indicate that the address is unset.
enum Role {
// No oracle role has been set for address a
Unset,
// Signing address for the s_oracles[a].index'th oracle. I.e., report
// signatures from this oracle should ecrecover back to address a.
Signer,
// Transmission address for the s_oracles[a].index'th oracle. I.e., if a
// report is received by OCR2Aggregator.transmit in which msg.sender is
// a, it is attributed to the s_oracles[a].index'th oracle.
Transmitter
}
struct Oracle {
uint8 index; // Index of oracle in s_signers/s_transmitters
Role role; // Role of the address which mapped to this struct
}
// The current config
ConfigInfo internal s_configInfo;
// incremented each time a new config is posted. This count is incorporated
// into the config digest, to prevent replay attacks.
uint32 internal s_configCount;
// makes it easier for offchain systems to extract config from logs.
uint32 internal s_latestConfigBlockNumber;
// signer OR transmitter address
mapping(address signerOrTransmitter => Oracle oracle) internal s_oracles;
// s_signers contains the signing address of each oracle
address[] internal s_signers;
// s_transmitters contains the transmission address of each oracle,
// i.e. the address the oracle actually sends transactions to the contract from
address[] internal s_transmitters;
// The constant-length components of the msg.data sent to transmit.
// See the "If we wanted to call sam" example on for example reasoning
// https://solidity.readthedocs.io/en/v0.7.2/abi-spec.html
uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT =
4 + // function selector
32 *
3 + // 3 words containing reportContext
32 + // word containing start location of abiencoded report value
32 + // word containing location start of abiencoded rs value
32 + // word containing start location of abiencoded ss value
32 + // rawVs value
32 + // word containing length of report
32 + // word containing length rs
32; // word containing length of ss
bool internal immutable i_uniqueReports;
uint256 internal immutable i_chainID;
constructor(bool uniqueReports) {
i_uniqueReports = uniqueReports;
i_chainID = block.chainid;
}
// Reverts transaction if config args are invalid
modifier checkConfigValid(
uint256 numSigners,
uint256 numTransmitters,
uint256 f
) {
if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers");
if (f == 0) revert InvalidConfig("f must be positive");
if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration");
if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high");
_;
}
/// @notice sets offchain reporting protocol configuration incl. participating oracles
/// @param signers addresses with which oracles sign the reports
/// @param transmitters addresses oracles use to transmit the reports
/// @param f number of faulty oracles the system can tolerate
/// @param onchainConfig encoded on-chain contract configuration
/// @param offchainConfigVersion version number for offchainEncoding schema
/// @param offchainConfig encoded off-chain oracle configuration
function setOCR2Config(
address[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) external override checkConfigValid(signers.length, transmitters.length, f) onlyOwner {
_beforeSetConfig(onchainConfig);
uint256 oldSignerLength = s_signers.length;
for (uint256 i = 0; i < oldSignerLength; ++i) {
delete s_oracles[s_signers[i]];
delete s_oracles[s_transmitters[i]];
}
uint256 newSignersLength = signers.length;
for (uint256 i = 0; i < newSignersLength; ++i) {
// add new signer/transmitter addresses
address signer = signers[i];
if (s_oracles[signer].role != Role.Unset) revert InvalidConfig("repeated signer address");
if (signer == address(0)) revert OracleCannotBeZeroAddress();
s_oracles[signer] = Oracle(uint8(i), Role.Signer);
address transmitter = transmitters[i];
if (s_oracles[transmitter].role != Role.Unset) revert InvalidConfig("repeated transmitter address");
if (transmitter == address(0)) revert OracleCannotBeZeroAddress();
s_oracles[transmitter] = Oracle(uint8(i), Role.Transmitter);
}
s_signers = signers;
s_transmitters = transmitters;
s_configInfo.f = f;
s_configInfo.n = uint8(newSignersLength);
s_configInfo.latestConfigDigest = _configDigestFromConfigData(
block.chainid,
address(this),
++s_configCount,
signers,
transmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig
);
uint32 previousConfigBlockNumber = s_latestConfigBlockNumber;
s_latestConfigBlockNumber = uint32(block.number);
emit ConfigSet(
previousConfigBlockNumber,
s_configInfo.latestConfigDigest,
s_configCount,
signers,
transmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig
);
}
/// @dev Hook that is run from setOCR2Config() right after validating configuration.
/// Empty by default, please provide an implementation in a child contract if you need additional configuration processing
function _beforeSetConfig(bytes memory _onchainConfig) internal virtual {}
/// @return list of addresses permitted to transmit reports to this contract
/// @dev The list will match the order used to specify the transmitter during setConfig
function getTransmitters() external view returns (address[] memory) {
return s_transmitters;
}
/// @notice transmit is called to post a new report to the contract
/// @param report serialized report, which the signatures are signing.
/// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
/// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
/// @param rawVs ith element is the the V component of the ith signature
function transmit(
// NOTE: If these parameters are changed, expectedMsgDataLength and/or
// TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
bytes32[3] calldata reportContext,
bytes calldata report,
bytes32[] calldata rs,
bytes32[] calldata ss,
bytes32 rawVs // signatures
) external override {
// Scoping this reduces stack pressure and gas usage
{
// report and epochAndRound
_report(report, uint40(uint256(reportContext[1])));
}
// reportContext consists of:
// reportContext[0]: ConfigDigest
// reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round
// reportContext[2]: ExtraHash
bytes32 configDigest = reportContext[0];
ConfigInfo memory configInfo = s_configInfo;
if (configInfo.latestConfigDigest != configDigest)
revert ConfigDigestMismatch(configInfo.latestConfigDigest, configDigest);
// If the cached chainID at time of deployment doesn't match the current chainID, we reject all signed reports.
// This avoids a (rare) scenario where chain A forks into chain A and A', A' still has configDigest
// calculated from chain A and so OCR reports will be valid on both forks.
if (i_chainID != block.chainid) revert ForkedChain(i_chainID, block.chainid);
emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
uint256 expectedNumSignatures;
if (i_uniqueReports) {
expectedNumSignatures = (configInfo.n + configInfo.f) / 2 + 1;
} else {
expectedNumSignatures = configInfo.f + 1;
}
if (rs.length != expectedNumSignatures) revert WrongNumberOfSignatures();
if (rs.length != ss.length) revert SignaturesOutOfRegistration();
// Scoping this reduces stack pressure and gas usage
{
Oracle memory transmitter = s_oracles[msg.sender];
// Check that sender is authorized to report
if (!(transmitter.role == Role.Transmitter && msg.sender == s_transmitters[transmitter.index]))
revert UnauthorizedTransmitter();
}
// Scoping this reduces stack pressure and gas usage
{
uint256 expectedDataLength = uint256(TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT) +
report.length + // one byte pure entry in _report
rs.length *
32 + // 32 bytes per entry in _rs
ss.length *
32; // 32 bytes per entry in _ss)
if (msg.data.length != expectedDataLength) revert WrongMessageLength(expectedDataLength, msg.data.length);
}
// Verify signatures attached to report
bytes32 h = keccak256(abi.encodePacked(keccak256(report), reportContext));
bool[MAX_NUM_ORACLES] memory signed;
uint256 numberOfSignatures = rs.length;
for (uint256 i = 0; i < numberOfSignatures; ++i) {
// Safe from ECDSA malleability here since we check for duplicate signers.
address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]);
// Since we disallow address(0) as a valid signer address, it can
// never have a signer role.
Oracle memory oracle = s_oracles[signer];
if (oracle.role != Role.Signer) revert UnauthorizedSigner();
if (signed[oracle.index]) revert NonUniqueSignatures();
signed[oracle.index] = true;
}
}
/// @notice information about current offchain reporting protocol configuration
/// @return configCount ordinal number of current config, out of all configs applied to this contract so far
/// @return blockNumber block at which this config was set
/// @return configDigest domain-separation tag for current config (see _configDigestFromConfigData)
function latestConfigDetails()
external
view
override
returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest)
{
return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest);
}
/// @inheritdoc OCR2Abstract
function latestConfigDigestAndEpoch()
external
view
virtual
override
returns (bool scanLogs, bytes32 configDigest, uint32 epoch)
{
return (true, bytes32(0), uint32(0));
}
function _report(bytes calldata report, uint40 epochAndRound) internal virtual;
}
{
"compilationTarget": {
"src/v0.8/ccip/CommitStore.sol": "CommitStore"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 26000
},
"remappings": [
":@eth-optimism/=node_modules/@eth-optimism/",
":@openzeppelin/=node_modules/@openzeppelin/",
":@scroll-tech/=node_modules/@scroll-tech/",
":ds-test/=foundry-lib/forge-std/lib/ds-test/src/",
":forge-std/=foundry-lib/forge-std/src/",
":hardhat/=node_modules/hardhat/"
]
}
[{"inputs":[{"components":[{"internalType":"uint64","name":"chainSelector","type":"uint64"},{"internalType":"uint64","name":"sourceChainSelector","type":"uint64"},{"internalType":"address","name":"onRamp","type":"address"},{"internalType":"address","name":"armProxy","type":"address"}],"internalType":"struct CommitStore.StaticConfig","name":"staticConfig","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BadARMSignal","type":"error"},{"inputs":[{"internalType":"bytes32","name":"expected","type":"bytes32"},{"internalType":"bytes32","name":"actual","type":"bytes32"}],"name":"ConfigDigestMismatch","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"ForkedChain","type":"error"},{"inputs":[],"name":"InvalidCommitStoreConfig","type":"error"},{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"InvalidConfig","type":"error"},{"inputs":[{"components":[{"internalType":"uint64","name":"min","type":"uint64"},{"internalType":"uint64","name":"max","type":"uint64"}],"internalType":"struct CommitStore.Interval","name":"interval","type":"tuple"}],"name":"InvalidInterval","type":"error"},{"inputs":[],"name":"InvalidProof","type":"error"},{"inputs":[],"name":"InvalidRoot","type":"error"},{"inputs":[],"name":"LeavesCannotBeEmpty","type":"error"},{"inputs":[],"name":"NonUniqueSignatures","type":"error"},{"inputs":[],"name":"OracleCannotBeZeroAddress","type":"error"},{"inputs":[],"name":"PausedError","type":"error"},{"inputs":[],"name":"RootAlreadyCommitted","type":"error"},{"inputs":[],"name":"SignaturesOutOfRegistration","type":"error"},{"inputs":[],"name":"StaleReport","type":"error"},{"inputs":[],"name":"UnauthorizedSigner","type":"error"},{"inputs":[],"name":"UnauthorizedTransmitter","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"WrongMessageLength","type":"error"},{"inputs":[],"name":"WrongNumberOfSignatures","type":"error"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint64","name":"chainSelector","type":"uint64"},{"internalType":"uint64","name":"sourceChainSelector","type":"uint64"},{"internalType":"address","name":"onRamp","type":"address"},{"internalType":"address","name":"armProxy","type":"address"}],"indexed":false,"internalType":"struct CommitStore.StaticConfig","name":"staticConfig","type":"tuple"},{"components":[{"internalType":"address","name":"priceRegistry","type":"address"}],"indexed":false,"internalType":"struct CommitStore.DynamicConfig","name":"dynamicConfig","type":"tuple"}],"name":"ConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"previousConfigBlockNumber","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"configCount","type":"uint64"},{"indexed":false,"internalType":"address[]","name":"signers","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"transmitters","type":"address[]"},{"indexed":false,"internalType":"uint8","name":"f","type":"uint8"},{"indexed":false,"internalType":"bytes","name":"onchainConfig","type":"bytes"},{"indexed":false,"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"indexed":false,"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"ConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"components":[{"components":[{"components":[{"internalType":"address","name":"sourceToken","type":"address"},{"internalType":"uint224","name":"usdPerToken","type":"uint224"}],"internalType":"struct Internal.TokenPriceUpdate[]","name":"tokenPriceUpdates","type":"tuple[]"},{"components":[{"internalType":"uint64","name":"destChainSelector","type":"uint64"},{"internalType":"uint224","name":"usdPerUnitGas","type":"uint224"}],"internalType":"struct Internal.GasPriceUpdate[]","name":"gasPriceUpdates","type":"tuple[]"}],"internalType":"struct Internal.PriceUpdates","name":"priceUpdates","type":"tuple"},{"components":[{"internalType":"uint64","name":"min","type":"uint64"},{"internalType":"uint64","name":"max","type":"uint64"}],"internalType":"struct CommitStore.Interval","name":"interval","type":"tuple"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"indexed":false,"internalType":"struct CommitStore.CommitReport","name":"report","type":"tuple"}],"name":"ReportAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"RootRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"epoch","type":"uint32"}],"name":"Transmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getDynamicConfig","outputs":[{"components":[{"internalType":"address","name":"priceRegistry","type":"address"}],"internalType":"struct CommitStore.DynamicConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExpectedNextSequenceNumber","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLatestPriceEpochAndRound","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"getMerkleRoot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStaticConfig","outputs":[{"components":[{"internalType":"uint64","name":"chainSelector","type":"uint64"},{"internalType":"uint64","name":"sourceChainSelector","type":"uint64"},{"internalType":"address","name":"onRamp","type":"address"},{"internalType":"address","name":"armProxy","type":"address"}],"internalType":"struct CommitStore.StaticConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTransmitters","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isARMHealthy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"isBlessed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isUnpausedAndARMHealthy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestConfigDetails","outputs":[{"internalType":"uint32","name":"configCount","type":"uint32"},{"internalType":"uint32","name":"blockNumber","type":"uint32"},{"internalType":"bytes32","name":"configDigest","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestConfigDigestAndEpoch","outputs":[{"internalType":"bool","name":"scanLogs","type":"bool"},{"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"internalType":"uint32","name":"epoch","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"rootToReset","type":"bytes32[]"}],"name":"resetUnblessedRoots","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint40","name":"latestPriceEpochAndRound","type":"uint40"}],"name":"setLatestPriceEpochAndRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"minSeqNr","type":"uint64"}],"name":"setMinSeqNr","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"signers","type":"address[]"},{"internalType":"address[]","name":"transmitters","type":"address[]"},{"internalType":"uint8","name":"f","type":"uint8"},{"internalType":"bytes","name":"onchainConfig","type":"bytes"},{"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"setOCR2Config","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[3]","name":"reportContext","type":"bytes32[3]"},{"internalType":"bytes","name":"report","type":"bytes"},{"internalType":"bytes32[]","name":"rs","type":"bytes32[]"},{"internalType":"bytes32[]","name":"ss","type":"bytes32[]"},{"internalType":"bytes32","name":"rawVs","type":"bytes32"}],"name":"transmit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"hashedLeaves","type":"bytes32[]"},{"internalType":"bytes32[]","name":"proofs","type":"bytes32[]"},{"internalType":"uint256","name":"proofFlagBits","type":"uint256"}],"name":"verify","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"}]