Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m3s
CI/CD Pipeline / Security Scanning (push) Successful in 2m18s
CI/CD Pipeline / Lint and Format (push) Failing after 34s
CI/CD Pipeline / Terraform Validation (push) Failing after 20s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 22s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 40s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 49s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 21s
Validation / validate-genesis (push) Successful in 25s
Validation / validate-terraform (push) Failing after 21s
Validation / validate-kubernetes (push) Failing after 8s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m11s
Validation / validate-documentation (push) Failing after 14s
Verify Deployment / Verify Deployment (push) Failing after 45s
Ship AddressActivityRegistry V1/V2, ISO20022IntakeGateway, Chain138ParticipantSurface, checkpoint hub contracts, checkpoint-core package, aggregator/indexer/sdk services, relay profile guards, M00 diamond bridge facet, and OMNL compliance contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
550 lines
24 KiB
Solidity
550 lines
24 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
|
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
|
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
import "../vendor/openzeppelin/ReentrancyGuardUpgradeable.sol";
|
|
import "../ccip/IRouterClient.sol";
|
|
|
|
import {CheckpointStorage} from "./storage/CheckpointStorage.sol";
|
|
import {CheckpointLeaf} from "./libraries/CheckpointLeaf.sol";
|
|
import {CheckpointFlags} from "./libraries/CheckpointFlags.sol";
|
|
import {ICheckpointExtension} from "./interfaces/ICheckpointExtension.sol";
|
|
import {IChain138MainnetCheckpoint} from "./interfaces/IChain138MainnetCheckpoint.sol";
|
|
import {CheckpointEIP712} from "./libraries/CheckpointEIP712.sol";
|
|
import {CheckpointHubConfig} from "./libraries/CheckpointHubConfig.sol";
|
|
import {CheckpointErrors} from "./libraries/CheckpointErrors.sol";
|
|
import {CheckpointPaymentsLib} from "./libraries/CheckpointPaymentsLib.sol";
|
|
import {ExtensionIds} from "./libraries/ExtensionIds.sol";
|
|
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
|
|
/**
|
|
* @title Chain138MainnetCheckpoint
|
|
* @notice Upgradeable hub: state + payment batches from Chain 138 (default 10 txs).
|
|
* @dev UUPS + EIP-7201 storage + extension registry. cW mint remains on CWMultiTokenBridge.
|
|
*/
|
|
contract Chain138MainnetCheckpoint is
|
|
Initializable,
|
|
AccessControlUpgradeable,
|
|
ReentrancyGuardUpgradeable,
|
|
UUPSUpgradeable,
|
|
IChain138MainnetCheckpoint
|
|
{
|
|
using CheckpointStorage for CheckpointStorage.CheckpointStorageStruct;
|
|
|
|
uint256 public constant IMPLEMENTATION_VERSION = 4;
|
|
uint64 public constant CHAIN_138 = 138;
|
|
|
|
bytes32 public constant SUBMITTER_ROLE = keccak256("SUBMITTER_ROLE");
|
|
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
|
|
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
|
|
bytes32 public constant EXTENSION_ADMIN_ROLE = keccak256("EXTENSION_ADMIN_ROLE");
|
|
bytes32 public constant EMERGENCY_ROLE = keccak256("EMERGENCY_ROLE");
|
|
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
|
|
|
|
bytes32 public constant CHECKPOINT_ATTEST_TYPEHASH =
|
|
keccak256("BatchAttestation(uint64 chainId,uint64 batchId,uint256 checkpointBlock,bytes32 blockHash,bytes32 stateRoot,bytes32 paymentsRoot,uint64 previousBatchId)");
|
|
|
|
uint32 public constant HOOK_BEFORE_SUBMIT = 1 << 0;
|
|
uint32 public constant HOOK_AFTER_SUBMIT = 1 << 1;
|
|
uint32 public constant HOOK_ON_CCIP = 1 << 2;
|
|
uint32 public constant HOOK_VERIFY_LEAF = 1 << 3;
|
|
|
|
event HubConfigApplied(CheckpointHubConfig.HubConfig config);
|
|
|
|
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
constructor() {
|
|
_disableInitializers();
|
|
}
|
|
|
|
function initialize(
|
|
address admin,
|
|
address ccipRouter,
|
|
uint64 sourceChainSelector,
|
|
address batchEmitterOnSource
|
|
) external initializer {
|
|
__AccessControl_init();
|
|
__ReentrancyGuard_init();
|
|
__UUPSUpgradeable_init();
|
|
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(UPGRADER_ROLE, admin);
|
|
_grantRole(SUBMITTER_ROLE, admin);
|
|
_grantRole(EXTENSION_ADMIN_ROLE, admin);
|
|
_grantRole(PAUSER_ROLE, admin);
|
|
_grantRole(EMERGENCY_ROLE, admin);
|
|
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
$.chainId = CHAIN_138;
|
|
$.batchSize = 10;
|
|
$.maxBatchWaitSeconds = 300;
|
|
$.requireValidatorSigs = true;
|
|
$.allowCalldataOnlySubmit = true;
|
|
$.allowCCIPIngress = true;
|
|
$.enforcePreviousBatchId = true;
|
|
$.ccipRouter = ccipRouter;
|
|
$.expectedSourceChainSelector = sourceChainSelector;
|
|
$.batchEmitterOnSource = batchEmitterOnSource;
|
|
}
|
|
|
|
function initializeV2(address legacyMirror, address legacyTether, address attestationSigner) external reinitializer(2) {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
$.legacyMirrorV1 = legacyMirror;
|
|
$.legacyTetherV1 = legacyTether;
|
|
$.submitterAttestationSigner = attestationSigner;
|
|
}
|
|
|
|
// --- views ---
|
|
|
|
function getConfig()
|
|
external
|
|
view
|
|
returns (
|
|
uint16 batchSize,
|
|
uint32 maxBatchWaitSeconds,
|
|
uint256 minPaymentValueWei,
|
|
bool requireValidatorSigs,
|
|
bool allowCalldataOnlySubmit,
|
|
bool allowCCIPIngress
|
|
)
|
|
{
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
return (
|
|
$.batchSize,
|
|
$.maxBatchWaitSeconds,
|
|
$.minPaymentValueWei,
|
|
$.requireValidatorSigs,
|
|
$.allowCalldataOnlySubmit,
|
|
$.allowCCIPIngress
|
|
);
|
|
}
|
|
|
|
function getFullConfig() external view returns (CheckpointHubConfig.HubConfig memory cfg) {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
cfg.chainId = $.chainId;
|
|
cfg.batchSize = $.batchSize;
|
|
cfg.maxBatchWaitSeconds = $.maxBatchWaitSeconds;
|
|
cfg.minPaymentValueWei = $.minPaymentValueWei;
|
|
cfg.requireValidatorSigs = $.requireValidatorSigs;
|
|
cfg.allowCalldataOnlySubmit = $.allowCalldataOnlySubmit;
|
|
cfg.allowCCIPIngress = $.allowCCIPIngress;
|
|
cfg.enforcePreviousBatchId = $.enforcePreviousBatchId;
|
|
cfg.ccipRouter = $.ccipRouter;
|
|
cfg.sourceChainSelector = $.expectedSourceChainSelector;
|
|
cfg.batchEmitterOnSource = $.batchEmitterOnSource;
|
|
cfg.legacyMirrorV1 = $.legacyMirrorV1;
|
|
cfg.legacyTetherV1 = $.legacyTetherV1;
|
|
cfg.submitterAttestationSigner = $.submitterAttestationSigner;
|
|
}
|
|
|
|
function applyConfig(CheckpointHubConfig.HubConfig calldata cfg) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
CheckpointHubConfig.validate(cfg);
|
|
_applyHubConfig(cfg);
|
|
emit HubConfigApplied(cfg);
|
|
}
|
|
|
|
function extensionCount() external view returns (uint256) {
|
|
return CheckpointStorage.get().extensionList.length;
|
|
}
|
|
|
|
function getExtension(bytes32 extensionId)
|
|
external
|
|
view
|
|
returns (address module, uint32 hooks, bool active)
|
|
{
|
|
CheckpointStorage.ExtensionConfig storage cfg = CheckpointStorage.get().extensions[extensionId];
|
|
return (cfg.module, cfg.hooks, cfg.active);
|
|
}
|
|
|
|
function getExtensionList() external view returns (bytes32[] memory) {
|
|
return CheckpointStorage.get().extensionList;
|
|
}
|
|
|
|
function getLatestCheckpoint() external view returns (CheckpointStorage.CheckpointHeader memory) {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
return $.checkpoints[$.latestBatchId];
|
|
}
|
|
|
|
function getCheckpoint(uint64 batchId) external view returns (CheckpointStorage.CheckpointHeader memory) {
|
|
return CheckpointStorage.get().checkpoints[batchId];
|
|
}
|
|
|
|
function getLatestBatchId() external view returns (uint64) {
|
|
return CheckpointStorage.get().latestBatchId;
|
|
}
|
|
|
|
function latestCheckpointBlock() external view returns (uint256) {
|
|
return CheckpointStorage.get().latestCheckpointBlock;
|
|
}
|
|
|
|
function verifyPaymentInBatch(
|
|
uint64 batchId,
|
|
CheckpointLeaf.PaymentLeafV1 calldata leaf,
|
|
bytes32[] calldata proof
|
|
) external view returns (bool) {
|
|
CheckpointStorage.CheckpointHeader storage h = CheckpointStorage.get().checkpoints[batchId];
|
|
if (h.batchId == 0) revert CheckpointErrors.UnknownBatch();
|
|
bytes32 leafHash = CheckpointLeaf.paymentLeafV1(h.chainId, leaf);
|
|
CheckpointStorage.CheckpointHeader memory hm = h;
|
|
if (_extensionVerifyLeaf(hm, leaf, proof)) return true;
|
|
return CheckpointLeaf.verifyMerkle(h.paymentsRoot, leafHash, proof);
|
|
}
|
|
|
|
function isTxIncluded(bytes32 txHash) external view returns (bool included, uint64 batchId) {
|
|
batchId = CheckpointStorage.get().txHashToBatchId[txHash];
|
|
included = batchId != 0;
|
|
}
|
|
|
|
// --- submit ---
|
|
|
|
function paused() external view returns (bool) {
|
|
return CheckpointStorage.get().paused;
|
|
}
|
|
|
|
function pause() external onlyRole(PAUSER_ROLE) {
|
|
CheckpointStorage.get().paused = true;
|
|
}
|
|
|
|
function unpause() external onlyRole(PAUSER_ROLE) {
|
|
CheckpointStorage.get().paused = false;
|
|
}
|
|
|
|
function submitCheckpoint(
|
|
CheckpointStorage.CheckpointHeader calldata header,
|
|
bytes calldata validatorSignatures,
|
|
bytes32[] calldata txHashes,
|
|
bytes calldata extensionData
|
|
) external onlyRole(SUBMITTER_ROLE) nonReentrant {
|
|
_requireNotPaused();
|
|
_runBeforeSubmitExtensions(header, validatorSignatures, extensionData);
|
|
_submit(header, validatorSignatures, txHashes, bytes32(0), false, false, extensionData);
|
|
_runExtensions(HOOK_AFTER_SUBMIT, header, extensionData);
|
|
}
|
|
|
|
function submitCheckpointByRelayer(
|
|
CheckpointStorage.CheckpointHeader calldata header,
|
|
bytes calldata validatorSignatures,
|
|
bytes32[] calldata txHashes,
|
|
bytes calldata extensionData,
|
|
bytes calldata submitterSignature
|
|
) external onlyRole(RELAYER_ROLE) nonReentrant {
|
|
_requireNotPaused();
|
|
_verifySubmitterAttestation(header, submitterSignature);
|
|
CheckpointStorage.CheckpointHeader memory h = header;
|
|
h.flags |= CheckpointFlags.RELAYER_SUBMIT;
|
|
_runBeforeSubmitExtensions(h, validatorSignatures, extensionData);
|
|
_submit(h, validatorSignatures, txHashes, bytes32(0), false, false, extensionData);
|
|
_runExtensions(HOOK_AFTER_SUBMIT, h, extensionData);
|
|
}
|
|
|
|
function submitCheckpointWithLeaves(
|
|
CheckpointStorage.CheckpointHeader calldata header,
|
|
bytes calldata validatorSignatures,
|
|
bytes32[] calldata txHashes,
|
|
CheckpointLeaf.PaymentLeafV1[] calldata leaves
|
|
) external onlyRole(SUBMITTER_ROLE) nonReentrant {
|
|
_requireNotPaused();
|
|
CheckpointPaymentsLib.assertPaymentsRootV1(header.chainId, header.paymentsRoot, leaves);
|
|
bytes memory leafPayload = abi.encode(leaves);
|
|
_runBeforeSubmitExtensions(header, validatorSignatures, leafPayload);
|
|
_submit(header, validatorSignatures, txHashes, bytes32(0), false, false, leafPayload);
|
|
_runExtensions(HOOK_AFTER_SUBMIT, header, leafPayload);
|
|
}
|
|
|
|
function forceCheckpoint(
|
|
CheckpointStorage.CheckpointHeader calldata header,
|
|
bytes calldata validatorSignatures,
|
|
bytes32[] calldata txHashes,
|
|
bytes calldata extensionData
|
|
) external onlyRole(EMERGENCY_ROLE) nonReentrant {
|
|
CheckpointStorage.CheckpointHeader memory h = header;
|
|
h.flags |= CheckpointFlags.EMERGENCY;
|
|
_runBeforeSubmitExtensions(h, validatorSignatures, extensionData);
|
|
_submit(h, validatorSignatures, txHashes, bytes32(0), false, true, extensionData);
|
|
_runExtensions(HOOK_AFTER_SUBMIT, h, extensionData);
|
|
}
|
|
|
|
function submitCheckpointCommitment(
|
|
CheckpointStorage.CheckpointHeader calldata header,
|
|
bytes calldata validatorSignatures,
|
|
bytes32 contentURI,
|
|
bytes calldata extensionData
|
|
) external onlyRole(SUBMITTER_ROLE) nonReentrant {
|
|
_requireNotPaused();
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
if (!$.allowCalldataOnlySubmit) revert CheckpointErrors.CalldataOnlyDisabled();
|
|
_runBeforeSubmitExtensions(header, validatorSignatures, extensionData);
|
|
_submit(header, validatorSignatures, new bytes32[](0), contentURI, true, false, extensionData);
|
|
_runExtensions(HOOK_AFTER_SUBMIT, header, extensionData);
|
|
}
|
|
|
|
function ccipReceive(IRouterClient.Any2EVMMessage calldata message) external nonReentrant {
|
|
_requireNotPaused();
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
if (!$.allowCCIPIngress) revert CheckpointErrors.CcipDisabled();
|
|
if (msg.sender != $.ccipRouter) revert CheckpointErrors.OnlyRouter();
|
|
if (message.sourceChainSelector != $.expectedSourceChainSelector) revert CheckpointErrors.BadSelector();
|
|
|
|
address sender = message.sender.length >= 32
|
|
? address(uint160(uint256(bytes32(message.sender))))
|
|
: address(bytes20(message.sender));
|
|
if (sender != $.batchEmitterOnSource) revert CheckpointErrors.BadEmitter();
|
|
|
|
(
|
|
CheckpointStorage.CheckpointHeader memory header,
|
|
bytes memory validatorSignatures,
|
|
bytes32[] memory txHashes,
|
|
bytes32 contentURI,
|
|
bytes memory extensionData
|
|
) = abi.decode(
|
|
message.data,
|
|
(CheckpointStorage.CheckpointHeader, bytes, bytes32[], bytes32, bytes)
|
|
);
|
|
|
|
header.flags |= CheckpointFlags.CCIP_INGRESS;
|
|
_runExtensions(HOOK_ON_CCIP, header, message.data);
|
|
_runBeforeSubmitExtensions(header, validatorSignatures, extensionData);
|
|
_submit(
|
|
header,
|
|
validatorSignatures,
|
|
txHashes,
|
|
contentURI,
|
|
CheckpointFlags.has(header.flags, CheckpointFlags.CALLDATA_ONLY),
|
|
false,
|
|
extensionData
|
|
);
|
|
_runExtensions(HOOK_AFTER_SUBMIT, header, extensionData);
|
|
}
|
|
|
|
// --- extensions ---
|
|
|
|
function registerExtension(bytes32 extensionId, address module, uint32 hooks) external onlyRole(EXTENSION_ADMIN_ROLE) {
|
|
if (module == address(0)) revert CheckpointErrors.ZeroModule();
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
if ($.extensions[extensionId].active) revert CheckpointErrors.ExtensionExists();
|
|
$.extensions[extensionId] = CheckpointStorage.ExtensionConfig({module: module, hooks: hooks, active: true});
|
|
$.extensionList.push(extensionId);
|
|
emit ExtensionRegistered(extensionId, module, hooks);
|
|
}
|
|
|
|
function revokeExtension(bytes32 extensionId) external onlyRole(EXTENSION_ADMIN_ROLE) {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
if (!$.extensions[extensionId].active) revert CheckpointErrors.ExtensionMissing();
|
|
delete $.extensions[extensionId];
|
|
bytes32[] storage list = $.extensionList;
|
|
for (uint256 i = 0; i < list.length; i++) {
|
|
if (list[i] == extensionId) {
|
|
list[i] = list[list.length - 1];
|
|
list.pop();
|
|
break;
|
|
}
|
|
}
|
|
emit ExtensionRevoked(extensionId);
|
|
}
|
|
|
|
function setConfig(
|
|
uint16 batchSize,
|
|
uint32 maxBatchWaitSeconds,
|
|
uint256 minPaymentValueWei,
|
|
bool requireValidatorSigs,
|
|
bool allowCalldataOnlySubmit,
|
|
bool allowCCIPIngress
|
|
) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
if (batchSize == 0 || batchSize > 256) revert CheckpointErrors.BatchSize();
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
$.batchSize = batchSize;
|
|
$.maxBatchWaitSeconds = maxBatchWaitSeconds;
|
|
$.minPaymentValueWei = minPaymentValueWei;
|
|
$.requireValidatorSigs = requireValidatorSigs;
|
|
$.allowCalldataOnlySubmit = allowCalldataOnlySubmit;
|
|
$.allowCCIPIngress = allowCCIPIngress;
|
|
}
|
|
|
|
function setExtensionActive(bytes32 extensionId, bool active) external onlyRole(EXTENSION_ADMIN_ROLE) {
|
|
CheckpointStorage.ExtensionConfig storage cfg = CheckpointStorage.get().extensions[extensionId];
|
|
if (cfg.module == address(0)) revert CheckpointErrors.ExtensionMissing();
|
|
cfg.active = active;
|
|
}
|
|
|
|
function updateExtensionHooks(bytes32 extensionId, uint32 hooks) external onlyRole(EXTENSION_ADMIN_ROLE) {
|
|
CheckpointStorage.ExtensionConfig storage cfg = CheckpointStorage.get().extensions[extensionId];
|
|
if (cfg.module == address(0)) revert CheckpointErrors.ExtensionMissing();
|
|
cfg.hooks = hooks;
|
|
}
|
|
|
|
function _authorizeUpgrade(address) internal override onlyRole(UPGRADER_ROLE) {}
|
|
|
|
function _applyHubConfig(CheckpointHubConfig.HubConfig calldata cfg) internal {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
$.chainId = cfg.chainId;
|
|
$.batchSize = cfg.batchSize;
|
|
$.maxBatchWaitSeconds = cfg.maxBatchWaitSeconds;
|
|
$.minPaymentValueWei = cfg.minPaymentValueWei;
|
|
$.requireValidatorSigs = cfg.requireValidatorSigs;
|
|
$.allowCalldataOnlySubmit = cfg.allowCalldataOnlySubmit;
|
|
$.allowCCIPIngress = cfg.allowCCIPIngress;
|
|
$.enforcePreviousBatchId = cfg.enforcePreviousBatchId;
|
|
if (cfg.ccipRouter != address(0)) $.ccipRouter = cfg.ccipRouter;
|
|
if (cfg.sourceChainSelector != 0) $.expectedSourceChainSelector = cfg.sourceChainSelector;
|
|
if (cfg.batchEmitterOnSource != address(0)) $.batchEmitterOnSource = cfg.batchEmitterOnSource;
|
|
if (cfg.legacyMirrorV1 != address(0)) $.legacyMirrorV1 = cfg.legacyMirrorV1;
|
|
if (cfg.legacyTetherV1 != address(0)) $.legacyTetherV1 = cfg.legacyTetherV1;
|
|
if (cfg.submitterAttestationSigner != address(0)) {
|
|
$.submitterAttestationSigner = cfg.submitterAttestationSigner;
|
|
}
|
|
}
|
|
|
|
// --- internal ---
|
|
|
|
function _requireNotPaused() internal view {
|
|
if (CheckpointStorage.get().paused) revert CheckpointErrors.Paused();
|
|
}
|
|
|
|
function _submit(
|
|
CheckpointStorage.CheckpointHeader memory header,
|
|
bytes memory validatorSignatures,
|
|
bytes32[] memory txHashes,
|
|
bytes32 contentURI,
|
|
bool calldataOnly,
|
|
bool emergency,
|
|
bytes memory extensionData
|
|
) internal {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
if ($.processedBatchIds[header.batchId]) revert CheckpointErrors.BatchDone();
|
|
if (header.chainId != $.chainId) revert CheckpointErrors.BadChain();
|
|
if (header.batchId <= $.latestBatchId) revert CheckpointErrors.BatchOrder();
|
|
if ($.enforcePreviousBatchId && $.latestBatchId > 0 && header.previousBatchId != $.latestBatchId) {
|
|
revert CheckpointErrors.PrevBatch();
|
|
}
|
|
if (header.endBlock > 0 && header.endBlock < header.startBlock) revert CheckpointErrors.BadBlocks();
|
|
if (header.txCount == 0 || header.txCount > $.batchSize) revert CheckpointErrors.TxCount();
|
|
if (!emergency && !CheckpointFlags.has(header.flags, CheckpointFlags.PARTIAL_BATCH) && header.txCount != $.batchSize) {
|
|
revert CheckpointErrors.IncompleteBatch();
|
|
}
|
|
if (header.paymentsRoot == bytes32(0)) revert CheckpointErrors.PaymentsRoot();
|
|
if (header.stateRoot == bytes32(0)) revert CheckpointErrors.StateRoot();
|
|
if (header.blockHash == bytes32(0)) revert CheckpointErrors.BlockHash();
|
|
|
|
if ($.requireValidatorSigs && validatorSignatures.length == 0) revert CheckpointErrors.Signatures();
|
|
if ($.minPaymentValueWei > 0 && extensionData.length > 0) {
|
|
CheckpointPaymentsLib.enforceMinPaymentValueV1(extensionData, $.minPaymentValueWei);
|
|
}
|
|
|
|
bytes32 proofHash = keccak256(
|
|
abi.encodePacked(
|
|
header.batchId,
|
|
header.paymentsRoot,
|
|
header.stateRoot,
|
|
header.checkpointBlock,
|
|
validatorSignatures
|
|
)
|
|
);
|
|
if ($.processedProofHashes[proofHash]) revert CheckpointErrors.Replay();
|
|
$.processedProofHashes[proofHash] = true;
|
|
|
|
if (calldataOnly) {
|
|
header.flags |= CheckpointFlags.CALLDATA_ONLY;
|
|
}
|
|
if (contentURI != bytes32(0)) {
|
|
header.flags |= CheckpointFlags.HAS_CONTENT_URI;
|
|
header.contentURI = contentURI;
|
|
}
|
|
header.submittedAt = uint64(block.timestamp);
|
|
header.submitter = msg.sender;
|
|
|
|
for (uint256 i = 0; i < txHashes.length; i++) {
|
|
if (txHashes[i] == bytes32(0)) revert CheckpointErrors.ZeroTx();
|
|
if ($.txHashToBatchId[txHashes[i]] != 0) revert CheckpointErrors.TxSeen();
|
|
$.txHashToBatchId[txHashes[i]] = header.batchId;
|
|
}
|
|
|
|
$.checkpoints[header.batchId] = header;
|
|
$.processedBatchIds[header.batchId] = true;
|
|
$.latestBatchId = header.batchId;
|
|
$.latestCheckpointBlock = header.checkpointBlock;
|
|
$.latestPaymentsRoot = header.paymentsRoot;
|
|
|
|
emit CheckpointSubmitted(
|
|
header.batchId,
|
|
header.checkpointBlock,
|
|
header.paymentsRoot,
|
|
header.txCount,
|
|
header.flags,
|
|
header.contentURI
|
|
);
|
|
}
|
|
|
|
/// @dev Validator extension expects ECDSA bytes; other extensions expect leaf/extension payload.
|
|
function _runBeforeSubmitExtensions(
|
|
CheckpointStorage.CheckpointHeader memory header,
|
|
bytes memory validatorSignatures,
|
|
bytes memory extensionData
|
|
) internal {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
bytes32[] storage list = $.extensionList;
|
|
bytes32 validatorId = ExtensionIds.VALIDATOR_SIG;
|
|
CheckpointStorage.ExtensionConfig storage validatorCfg = $.extensions[validatorId];
|
|
if (validatorCfg.active && (validatorCfg.hooks & HOOK_BEFORE_SUBMIT) != 0) {
|
|
ICheckpointExtension(validatorCfg.module).beforeSubmit(header, validatorSignatures);
|
|
}
|
|
for (uint256 i = 0; i < list.length; i++) {
|
|
bytes32 id = list[i];
|
|
if (id == validatorId) continue;
|
|
CheckpointStorage.ExtensionConfig storage cfg = $.extensions[id];
|
|
if (!cfg.active || (cfg.hooks & HOOK_BEFORE_SUBMIT) == 0) continue;
|
|
ICheckpointExtension(cfg.module).beforeSubmit(header, extensionData);
|
|
}
|
|
}
|
|
|
|
function _runExtensions(
|
|
uint32 hook,
|
|
CheckpointStorage.CheckpointHeader memory header,
|
|
bytes memory data
|
|
) internal {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
bytes32[] storage list = $.extensionList;
|
|
for (uint256 i = 0; i < list.length; i++) {
|
|
CheckpointStorage.ExtensionConfig storage cfg = $.extensions[list[i]];
|
|
if (!cfg.active || (cfg.hooks & hook) == 0) continue;
|
|
if (hook == HOOK_BEFORE_SUBMIT) {
|
|
ICheckpointExtension(cfg.module).beforeSubmit(header, data);
|
|
} else if (hook == HOOK_AFTER_SUBMIT) {
|
|
ICheckpointExtension(cfg.module).afterSubmit(header, data);
|
|
} else if (hook == HOOK_ON_CCIP) {
|
|
ICheckpointExtension(cfg.module).onCCIPReceive(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _verifySubmitterAttestation(
|
|
CheckpointStorage.CheckpointHeader calldata header,
|
|
bytes calldata submitterSignature
|
|
) internal view {
|
|
if (submitterSignature.length != 65) revert CheckpointErrors.SubmitterSigLen();
|
|
bytes32 digest = CheckpointEIP712.digest(address(this), block.chainid, header);
|
|
address signer = ECDSA.recover(digest, submitterSignature);
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
if ($.submitterAttestationSigner != address(0)) {
|
|
if (signer != $.submitterAttestationSigner) revert CheckpointErrors.AttestSigner();
|
|
return;
|
|
}
|
|
if (!hasRole(SUBMITTER_ROLE, signer)) revert CheckpointErrors.SubmitterRole();
|
|
}
|
|
|
|
function _extensionVerifyLeaf(
|
|
CheckpointStorage.CheckpointHeader memory h,
|
|
CheckpointLeaf.PaymentLeafV1 calldata leaf,
|
|
bytes32[] calldata proof
|
|
) internal view returns (bool) {
|
|
CheckpointStorage.CheckpointStorageStruct storage $ = CheckpointStorage.get();
|
|
bytes32[] storage list = $.extensionList;
|
|
for (uint256 i = 0; i < list.length; i++) {
|
|
CheckpointStorage.ExtensionConfig storage cfg = $.extensions[list[i]];
|
|
if (!cfg.active || (cfg.hooks & HOOK_VERIFY_LEAF) == 0) continue;
|
|
if (ICheckpointExtension(cfg.module).verifyLeaf(h, leaf, proof)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|