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/contracts/isms/multisig/ValidatorAnnounce.sol

138 lines
4.7 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {IValidatorAnnounce} from "../../interfaces/IValidatorAnnounce.sol";
import {IMailbox} from "../../interfaces/IMailbox.sol";
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {MailboxClient} from "../../client/MailboxClient.sol";
// ============ External Imports ============
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @title ValidatorAnnounce
* @notice Stores the location(s) of validator signed checkpoints
*/
contract ValidatorAnnounce is MailboxClient, IValidatorAnnounce {
// ============ Libraries ============
using EnumerableSet for EnumerableSet.AddressSet;
using TypeCasts for address;
// ============ Public Storage ============
// The set of validators that have announced
EnumerableSet.AddressSet private validators;
// Storage locations of validator signed checkpoints
mapping(address => string[]) private storageLocations;
// Mapping to prevent the same announcement from being registered
// multiple times.
mapping(bytes32 => bool) private replayProtection;
// ============ Events ============
/**
* @notice Emitted when a new validator announcement is made
* @param validator The address of the announcing validator
* @param storageLocation The storage location being announced
*/
event ValidatorAnnouncement(
address indexed validator,
string storageLocation
);
// ============ Constructor ============
constructor(address _mailbox) MailboxClient(_mailbox) {}
// ============ External Functions ============
/**
* @notice Announces a validator signature storage location
* @param _storageLocation Information encoding the location of signed
* checkpoints
* @param _signature The signed validator announcement
* @return True upon success
*/
function announce(
address _validator,
string calldata _storageLocation,
bytes calldata _signature
) external returns (bool) {
// Ensure that the same storage metadata isn't being announced
// multiple times for the same validator.
bytes32 _replayId = keccak256(
abi.encodePacked(_validator, _storageLocation)
);
require(replayProtection[_replayId] == false, "replay");
replayProtection[_replayId] = true;
// Verify that the signature matches the declared validator
bytes32 _announcementDigest = getAnnouncementDigest(_storageLocation);
address _signer = ECDSA.recover(_announcementDigest, _signature);
require(_signer == _validator, "!signature");
// Store the announcement
if (!validators.contains(_validator)) {
validators.add(_validator);
}
storageLocations[_validator].push(_storageLocation);
emit ValidatorAnnouncement(_validator, _storageLocation);
return true;
}
/**
* @notice Returns a list of all announced storage locations
* @param _validators The list of validators to get registrations for
* @return A list of registered storage metadata
*/
function getAnnouncedStorageLocations(address[] calldata _validators)
external
view
returns (string[][] memory)
{
string[][] memory _metadata = new string[][](_validators.length);
for (uint256 i = 0; i < _validators.length; i++) {
_metadata[i] = storageLocations[_validators[i]];
}
return _metadata;
}
/// @notice Returns a list of validators that have made announcements
function getAnnouncedValidators() external view returns (address[] memory) {
return validators.values();
}
/**
* @notice Returns the digest validators are expected to sign when signing announcements.
* @param _storageLocation Storage location string.
* @return The digest of the announcement.
*/
function getAnnouncementDigest(string memory _storageLocation)
public
view
returns (bytes32)
{
return
ECDSA.toEthSignedMessageHash(
keccak256(abi.encodePacked(_domainHash(), _storageLocation))
);
}
/**
* @notice Returns the domain separator used in validator announcements.
*/
function _domainHash() internal view returns (bytes32) {
return
keccak256(
abi.encodePacked(
localDomain,
address(mailbox).addressToBytes32(),
"HYPERLANE_ANNOUNCEMENT"
)
);
}
}