The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/solidity/core/contracts/ValidatorManager.sol

132 lines
4.3 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {IValidatorManager} from "../interfaces/IValidatorManager.sol";
import {Outbox} from "./Outbox.sol";
// ============ External Imports ============
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ECDSA} from "@openzeppelin/contracts/cryptography/ECDSA.sol";
/**
* @title ValidatorManager
* @author Celo Labs Inc.
* @notice MVP version of contract that will manage Validator selection and
* rotataion.
*/
contract ValidatorManager is IValidatorManager, Ownable {
// Mapping of domain -> validator address.
mapping(uint32 => address) public validators;
// ============ Events ============
/**
* @notice Emitted when a validator is enrolled
* @param domain The domain for which the validator is being enrolled
* @param validator The address of the validator
*/
event ValidatorEnrolled(uint32 indexed domain, address indexed validator);
/**
* @notice Emitted when proof of an improper checkpoint is submitted,
* which sets the contract to FAILED state
* @param root Root of the improper checkpoint
* @param index Index of the improper checkpoint
* @param signature Signature on `root` and `index`
*/
event ImproperCheckpoint(
address indexed outbox,
uint32 indexed domain,
address indexed validator,
bytes32 root,
uint256 index,
bytes signature
);
// ============ Constructor ============
constructor() Ownable() {}
// ============ External Functions ============
/**
* @notice Enroll a validator for the given domain
* @dev only callable by trusted owner
* @param _domain The domain for which the validator is being set
* @param _validator The address of the validator
*/
function enrollValidator(uint32 _domain, address _validator)
external
onlyOwner
{
validators[_domain] = _validator;
emit ValidatorEnrolled(_domain, _validator);
}
/**
* @notice Check if an Checkpoint is an Improper Checkpoint;
* if so, set the provided Outbox contract to FAILED state.
*
* An Improper Checkpoint is an checkpoint that was not previously checkpointed.
* @param _outbox Address of the Outbox contract to set to FAILED.
* @param _root Merkle root of the improper checkpoint
* @param _index Index root of the improper checkpoint
* @param _signature Validator signature on `_root` and `_index`
* @return TRUE if checkpoint was an Improper Checkpoint (implying Validator was slashed)
*/
function improperCheckpoint(
address _outbox,
bytes32 _root,
uint256 _index,
bytes calldata _signature
) external returns (bool) {
uint32 _domain = Outbox(_outbox).localDomain();
require(
isValidatorSignature(_domain, _root, _index, _signature),
"!validator sig"
);
require(Outbox(_outbox).checkpoints(_root) != _index, "!improper");
Outbox(_outbox).fail();
emit ImproperCheckpoint(
_outbox,
_domain,
validators[_domain],
_root,
_index,
_signature
);
return true;
}
// ============ Public Functions ============
/**
* @notice Checks that signature was signed by Validator
* @param _domain Domain of Outbox contract
* @param _root Merkle root
* @param _index Corresponding leaf index
* @param _signature Signature on `_root` and `_index`
* @return TRUE iff signature is valid signed by validator
**/
function isValidatorSignature(
uint32 _domain,
bytes32 _root,
uint256 _index,
bytes calldata _signature
) public view override returns (bool) {
bytes32 _digest = keccak256(
abi.encodePacked(domainHash(_domain), _root, _index)
);
_digest = ECDSA.toEthSignedMessageHash(_digest);
return (ECDSA.recover(_digest, _signature) == validators[_domain]);
}
/**
* @notice Hash of domain concatenated with "ABACUS"
* @param _domain the domain to hash
*/
function domainHash(uint32 _domain) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_domain, "ABACUS"));
}
}