231 lines
7.5 KiB
Solidity
231 lines
7.5 KiB
Solidity
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||
|
|
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
||
|
|
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @title BridgeVerifier
|
||
|
|
* @notice Verifies cross-chain proofs and attestor signatures for bridge operations
|
||
|
|
* @dev Supports multi-sig quorum for attestations
|
||
|
|
*/
|
||
|
|
contract BridgeVerifier is AccessControl, EIP712 {
|
||
|
|
using ECDSA for bytes32;
|
||
|
|
|
||
|
|
bytes32 public constant ATTESTOR_ROLE = keccak256("ATTESTOR_ROLE");
|
||
|
|
|
||
|
|
bytes32 private constant ATTESTATION_TYPEHASH =
|
||
|
|
keccak256("Attestation(bytes32 transferId,bytes32 proofHash,uint256 nonce,uint256 deadline)");
|
||
|
|
|
||
|
|
struct Attestation {
|
||
|
|
bytes32 transferId;
|
||
|
|
bytes32 proofHash;
|
||
|
|
uint256 nonce;
|
||
|
|
uint256 deadline;
|
||
|
|
bytes signature;
|
||
|
|
}
|
||
|
|
|
||
|
|
struct AttestorConfig {
|
||
|
|
address attestor;
|
||
|
|
bool enabled;
|
||
|
|
uint256 weight; // Weight for quorum calculation
|
||
|
|
}
|
||
|
|
|
||
|
|
mapping(address => AttestorConfig) public attestors;
|
||
|
|
mapping(bytes32 => mapping(address => bool)) public attestations; // transferId -> attestor -> attested
|
||
|
|
mapping(bytes32 => uint256) public attestationWeights; // transferId -> total weight
|
||
|
|
mapping(uint256 => bool) public usedNonces;
|
||
|
|
|
||
|
|
address[] public attestorList;
|
||
|
|
uint256 public totalAttestorWeight;
|
||
|
|
uint256 public quorumThreshold; // Minimum weight required (in basis points, 10000 = 100%)
|
||
|
|
|
||
|
|
event AttestationSubmitted(
|
||
|
|
bytes32 indexed transferId,
|
||
|
|
address indexed attestor,
|
||
|
|
bytes32 proofHash
|
||
|
|
);
|
||
|
|
|
||
|
|
event AttestationVerified(
|
||
|
|
bytes32 indexed transferId,
|
||
|
|
uint256 totalWeight,
|
||
|
|
bool quorumMet
|
||
|
|
);
|
||
|
|
|
||
|
|
event AttestorAdded(address indexed attestor, uint256 weight);
|
||
|
|
event AttestorRemoved(address indexed attestor);
|
||
|
|
event AttestorUpdated(address indexed attestor, bool enabled, uint256 weight);
|
||
|
|
event QuorumThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);
|
||
|
|
|
||
|
|
error ZeroAddress();
|
||
|
|
error AttestorNotFound();
|
||
|
|
error InvalidSignature();
|
||
|
|
error NonceAlreadyUsed();
|
||
|
|
error DeadlineExpired();
|
||
|
|
error InvalidQuorum();
|
||
|
|
error AlreadyAttested();
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
address admin,
|
||
|
|
uint256 _quorumThreshold
|
||
|
|
) EIP712("BridgeVerifier", "1") {
|
||
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
||
|
|
_grantRole(ATTESTOR_ROLE, admin);
|
||
|
|
if (_quorumThreshold > 10000) revert InvalidQuorum();
|
||
|
|
quorumThreshold = _quorumThreshold;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Add an attestor
|
||
|
|
* @param attestor Attestor address
|
||
|
|
* @param weight Weight for quorum calculation
|
||
|
|
*/
|
||
|
|
function addAttestor(
|
||
|
|
address attestor,
|
||
|
|
uint256 weight
|
||
|
|
) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
if (attestor == address(0)) revert ZeroAddress();
|
||
|
|
if (attestors[attestor].attestor != address(0)) revert AttestorNotFound();
|
||
|
|
|
||
|
|
attestors[attestor] = AttestorConfig({
|
||
|
|
attestor: attestor,
|
||
|
|
enabled: true,
|
||
|
|
weight: weight
|
||
|
|
});
|
||
|
|
|
||
|
|
attestorList.push(attestor);
|
||
|
|
totalAttestorWeight += weight;
|
||
|
|
|
||
|
|
emit AttestorAdded(attestor, weight);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Remove an attestor
|
||
|
|
* @param attestor Attestor address
|
||
|
|
*/
|
||
|
|
function removeAttestor(address attestor) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
AttestorConfig memory config = attestors[attestor];
|
||
|
|
if (config.attestor == address(0)) revert AttestorNotFound();
|
||
|
|
|
||
|
|
totalAttestorWeight -= config.weight;
|
||
|
|
delete attestors[attestor];
|
||
|
|
|
||
|
|
// Remove from list
|
||
|
|
for (uint256 i = 0; i < attestorList.length; i++) {
|
||
|
|
if (attestorList[i] == attestor) {
|
||
|
|
attestorList[i] = attestorList[attestorList.length - 1];
|
||
|
|
attestorList.pop();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
emit AttestorRemoved(attestor);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Update attestor configuration
|
||
|
|
* @param attestor Attestor address
|
||
|
|
* @param enabled Enabled status
|
||
|
|
* @param weight New weight
|
||
|
|
*/
|
||
|
|
function updateAttestor(
|
||
|
|
address attestor,
|
||
|
|
bool enabled,
|
||
|
|
uint256 weight
|
||
|
|
) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
AttestorConfig storage config = attestors[attestor];
|
||
|
|
if (config.attestor == address(0)) revert AttestorNotFound();
|
||
|
|
|
||
|
|
uint256 oldWeight = config.weight;
|
||
|
|
totalAttestorWeight = totalAttestorWeight - oldWeight + weight;
|
||
|
|
|
||
|
|
config.enabled = enabled;
|
||
|
|
config.weight = weight;
|
||
|
|
|
||
|
|
emit AttestorUpdated(attestor, enabled, weight);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Update quorum threshold
|
||
|
|
* @param newThreshold New threshold in basis points
|
||
|
|
*/
|
||
|
|
function setQuorumThreshold(uint256 newThreshold) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
if (newThreshold > 10000) revert InvalidQuorum();
|
||
|
|
uint256 oldThreshold = quorumThreshold;
|
||
|
|
quorumThreshold = newThreshold;
|
||
|
|
emit QuorumThresholdUpdated(oldThreshold, newThreshold);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Submit an attestation
|
||
|
|
* @param attestation Attestation with signature
|
||
|
|
*/
|
||
|
|
function submitAttestation(Attestation calldata attestation) external {
|
||
|
|
AttestorConfig memory config = attestors[msg.sender];
|
||
|
|
if (config.attestor == address(0) || !config.enabled) revert AttestorNotFound();
|
||
|
|
if (block.timestamp > attestation.deadline) revert DeadlineExpired();
|
||
|
|
if (usedNonces[attestation.nonce]) revert NonceAlreadyUsed();
|
||
|
|
if (attestations[attestation.transferId][msg.sender]) revert AlreadyAttested();
|
||
|
|
|
||
|
|
// Verify signature
|
||
|
|
bytes32 structHash = keccak256(
|
||
|
|
abi.encode(
|
||
|
|
ATTESTATION_TYPEHASH,
|
||
|
|
attestation.transferId,
|
||
|
|
attestation.proofHash,
|
||
|
|
attestation.nonce,
|
||
|
|
attestation.deadline
|
||
|
|
)
|
||
|
|
);
|
||
|
|
bytes32 hash = _hashTypedDataV4(structHash);
|
||
|
|
|
||
|
|
if (hash.recover(attestation.signature) != msg.sender) {
|
||
|
|
revert InvalidSignature();
|
||
|
|
}
|
||
|
|
|
||
|
|
usedNonces[attestation.nonce] = true;
|
||
|
|
attestations[attestation.transferId][msg.sender] = true;
|
||
|
|
attestationWeights[attestation.transferId] += config.weight;
|
||
|
|
|
||
|
|
emit AttestationSubmitted(attestation.transferId, msg.sender, attestation.proofHash);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Verify if quorum is met for a transfer
|
||
|
|
* @param transferId Transfer identifier
|
||
|
|
* @return quorumMet Whether quorum threshold is met
|
||
|
|
* @return totalWeight Total weight of attestations
|
||
|
|
* @return requiredWeight Required weight for quorum
|
||
|
|
*/
|
||
|
|
function verifyQuorum(
|
||
|
|
bytes32 transferId
|
||
|
|
) external view returns (bool quorumMet, uint256 totalWeight, uint256 requiredWeight) {
|
||
|
|
totalWeight = attestationWeights[transferId];
|
||
|
|
requiredWeight = (totalAttestorWeight * quorumThreshold) / 10000;
|
||
|
|
quorumMet = totalWeight >= requiredWeight;
|
||
|
|
return (quorumMet, totalWeight, requiredWeight);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Check if an attestor has attested to a transfer
|
||
|
|
* @param transferId Transfer identifier
|
||
|
|
* @param attestor Attestor address
|
||
|
|
* @return True if attested
|
||
|
|
*/
|
||
|
|
function hasAttested(
|
||
|
|
bytes32 transferId,
|
||
|
|
address attestor
|
||
|
|
) external view returns (bool) {
|
||
|
|
return attestations[transferId][attestor];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Get all attestors
|
||
|
|
* @return Array of attestor addresses
|
||
|
|
*/
|
||
|
|
function getAllAttestors() external view returns (address[] memory) {
|
||
|
|
return attestorList;
|
||
|
|
}
|
||
|
|
}
|