Some checks failed
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 37s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m50s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 2m19s
Validation / validate-genesis (push) Successful in 51s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 10s
CI/CD Pipeline / Solidity Contracts (push) Failing after 12m56s
Validation / validate-smart-contracts (push) Failing after 12s
CI/CD Pipeline / Security Scanning (push) Failing after 15m52s
Validation / validate-security (push) Failing after 10m59s
Validation / validate-documentation (push) Failing after 17s
Validate Token List / validate (push) Failing after 30s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 26s
Verify Deployment / Verify Deployment (push) Failing after 56s
176 lines
6.3 KiB
Solidity
176 lines
6.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
|
|
/**
|
|
* @title ReserveCommitmentStore
|
|
* @notice Stores attested reserve R per line with TTL, merkle evidence root, versioning, primary + mirror,
|
|
* and optional threshold ECDSA attestation for commits.
|
|
*/
|
|
contract ReserveCommitmentStore is AccessControl {
|
|
bytes32 public constant RESERVE_COMMITTER_ROLE = keccak256("RESERVE_COMMITTER_ROLE");
|
|
bytes32 public constant ATTESTATION_ADMIN_ROLE = keccak256("ATTESTATION_ADMIN_ROLE");
|
|
|
|
/// @notice EIP-191 structured preimage prefix for attested commits (see commitReserveAttested).
|
|
bytes32 public constant ATTESTATION_TYPEHASH =
|
|
keccak256("OMNLReserveCommit(uint256 chainId,address store,bytes32 lineId,uint256 R,uint256 validUntil,bytes32 evidenceHash,bytes32 merkleRoot,uint256 nonce)");
|
|
|
|
struct Commitment {
|
|
uint256 R;
|
|
uint256 validUntil;
|
|
bytes32 evidenceHash;
|
|
bytes32 merkleRoot;
|
|
uint256 version;
|
|
}
|
|
|
|
address public mirrorReceiver;
|
|
|
|
mapping(bytes32 lineId => Commitment) private _commitments;
|
|
mapping(bytes32 lineId => uint256) public lineAttestationNonce;
|
|
|
|
mapping(address => bool) public isAttestationSigner;
|
|
uint256 public attestationThreshold;
|
|
|
|
event ReserveCommitted(
|
|
bytes32 indexed lineId,
|
|
uint256 R,
|
|
uint256 validUntil,
|
|
bytes32 evidenceHash,
|
|
bytes32 merkleRoot,
|
|
uint256 version,
|
|
address indexed by
|
|
);
|
|
event MirrorReceiverUpdated(address indexed oldReceiver, address indexed newReceiver);
|
|
event AttestationSignersUpdated(uint256 threshold);
|
|
event AttestationSignerSet(address indexed signer, bool active);
|
|
|
|
constructor(address admin) {
|
|
require(admin != address(0), "ReserveCommitmentStore: zero admin");
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(RESERVE_COMMITTER_ROLE, admin);
|
|
_grantRole(ATTESTATION_ADMIN_ROLE, admin);
|
|
}
|
|
|
|
function setMirrorReceiver(address receiver) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
address old = mirrorReceiver;
|
|
mirrorReceiver = receiver;
|
|
emit MirrorReceiverUpdated(old, receiver);
|
|
}
|
|
|
|
function setAttestationSigner(address signer, bool active) external onlyRole(ATTESTATION_ADMIN_ROLE) {
|
|
isAttestationSigner[signer] = active;
|
|
emit AttestationSignerSet(signer, active);
|
|
}
|
|
|
|
function setAttestationThreshold(uint256 threshold) external onlyRole(ATTESTATION_ADMIN_ROLE) {
|
|
attestationThreshold = threshold;
|
|
emit AttestationSignersUpdated(threshold);
|
|
}
|
|
|
|
/// @notice Primary chain (or designated committer) updates reserves.
|
|
function commitReserve(
|
|
bytes32 lineId,
|
|
uint256 R,
|
|
uint256 validUntil,
|
|
bytes32 evidenceHash,
|
|
bytes32 merkleRoot
|
|
) external onlyRole(RESERVE_COMMITTER_ROLE) {
|
|
_commit(lineId, R, validUntil, evidenceHash, merkleRoot, msg.sender);
|
|
}
|
|
|
|
/// @notice Threshold ECDSA attestation (any EOA may submit once enough distinct signers included).
|
|
function commitReserveAttested(
|
|
bytes32 lineId,
|
|
uint256 R,
|
|
uint256 validUntil,
|
|
bytes32 evidenceHash,
|
|
bytes32 merkleRoot,
|
|
uint256 nonce,
|
|
bytes[] calldata signatures
|
|
) external {
|
|
require(attestationThreshold > 0, "ReserveCommitmentStore: attestation off");
|
|
require(nonce == lineAttestationNonce[lineId], "ReserveCommitmentStore: nonce");
|
|
|
|
bytes32 digest = keccak256(
|
|
abi.encode(
|
|
ATTESTATION_TYPEHASH,
|
|
block.chainid,
|
|
address(this),
|
|
lineId,
|
|
R,
|
|
validUntil,
|
|
evidenceHash,
|
|
merkleRoot,
|
|
nonce
|
|
)
|
|
);
|
|
bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));
|
|
|
|
uint256 n = signatures.length;
|
|
require(n >= attestationThreshold, "ReserveCommitmentStore: sig count");
|
|
address[] memory seen = new address[](n);
|
|
uint256 uniq = 0;
|
|
|
|
for (uint256 i = 0; i < n; i++) {
|
|
address recovered = ECDSA.recover(ethSignedMessageHash, signatures[i]);
|
|
require(isAttestationSigner[recovered], "ReserveCommitmentStore: not signer");
|
|
for (uint256 j = 0; j < uniq; j++) {
|
|
require(seen[j] != recovered, "ReserveCommitmentStore: duplicate signer");
|
|
}
|
|
seen[uniq] = recovered;
|
|
unchecked {
|
|
++uniq;
|
|
}
|
|
}
|
|
require(uniq >= attestationThreshold, "ReserveCommitmentStore: threshold");
|
|
|
|
lineAttestationNonce[lineId] = nonce + 1;
|
|
_commit(lineId, R, validUntil, evidenceHash, merkleRoot, msg.sender);
|
|
}
|
|
|
|
function _commit(
|
|
bytes32 lineId,
|
|
uint256 R,
|
|
uint256 validUntil,
|
|
bytes32 evidenceHash,
|
|
bytes32 merkleRoot,
|
|
address by
|
|
) internal {
|
|
Commitment storage c = _commitments[lineId];
|
|
unchecked {
|
|
c.version += 1;
|
|
}
|
|
c.R = R;
|
|
c.validUntil = validUntil;
|
|
c.evidenceHash = evidenceHash;
|
|
c.merkleRoot = merkleRoot;
|
|
emit ReserveCommitted(lineId, R, validUntil, evidenceHash, merkleRoot, c.version, by);
|
|
}
|
|
|
|
/// @notice Applied by OMNLMirrorReceiver after CCIP validation (monotonic version).
|
|
function applyMirrorCommit(
|
|
bytes32 lineId,
|
|
uint256 R,
|
|
uint256 validUntil,
|
|
bytes32 evidenceHash,
|
|
bytes32 merkleRoot,
|
|
uint256 version
|
|
) external {
|
|
require(msg.sender == mirrorReceiver, "ReserveCommitmentStore: only mirror");
|
|
Commitment storage c = _commitments[lineId];
|
|
require(version > c.version, "ReserveCommitmentStore: version");
|
|
c.version = version;
|
|
c.R = R;
|
|
c.validUntil = validUntil;
|
|
c.evidenceHash = evidenceHash;
|
|
c.merkleRoot = merkleRoot;
|
|
emit ReserveCommitted(lineId, R, validUntil, evidenceHash, merkleRoot, version, msg.sender);
|
|
}
|
|
|
|
function getCommitment(bytes32 lineId) external view returns (Commitment memory) {
|
|
return _commitments[lineId];
|
|
}
|
|
}
|