Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Adds TimestampedHashRegistry contract #165

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions contracts/utils/TimestampedHashRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// import "@api3/airnode-protocol-v1/contracts/utils/SelfMulticall.sol";
import "../utils/SelfMulticall.sol"; // Uncomment line above and remove this line once this is moved to @api3/dapi-management
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "./interfaces/ITimestampedHashRegistry.sol";

contract TimestampedHashRegistry is
Ownable,
EIP712,
SelfMulticall,
ITimestampedHashRegistry
{
using ECDSA for bytes32;
using EnumerableSet for EnumerableSet.AddressSet;

mapping(bytes32 => EnumerableSet.AddressSet) private _hashTypeToSigners;

mapping(bytes32 => bytes32) public hashTypeToHash;

mapping(bytes32 => uint256) public hashTypeToTimestamp;

bytes32 private constant _SIGNED_HASH_TYPE_HASH =
keccak256(
"SignedHash(bytes32 hashType,bytes32 hash,uint256 timestamp)"
acenolaza marked this conversation as resolved.
Show resolved Hide resolved
);

constructor() EIP712("TimestampedHashRegistry", "1.0.0") {}

function setupSigners(
bytes32 hashType,
address[] calldata signers
) external override onlyOwner {
require(signers.length != 0, "Signers is empty");
require(
_hashTypeToSigners[hashType].length() == 0,
"Hash type signers is not empty"
);
for (uint256 ind = 0; ind < signers.length; ind++) {
_addSigner(hashType, signers[ind]);
}
emit SetupSigners(hashType, signers);
}

function _addSigner(bytes32 hashType, address signer) private {
require(hashType != bytes32(0), "Hash type is zero");
require(signer != address(0), "Signer is zero");
require(
_hashTypeToSigners[hashType].add(signer),
"Signer already exists"
);
}

function addSigner(
bytes32 hashType,
address signer
) external override onlyOwner {
_addSigner(hashType, signer);
emit AddedSigner(hashType, signer);
}

function removeSigner(
bytes32 hashType,
address signer
) external override onlyOwner {
require(hashType != bytes32(0), "Hash type is zero");
require(signer != address(0), "Signer is zero");
require(
_hashTypeToSigners[hashType].remove(signer),
"Signer does not exist"
);
emit RemovedSigner(hashType, signer);
}

function getSigners(
bytes32 hashType
) external view override returns (address[] memory signers) {
signers = _hashTypeToSigners[hashType].values();
}

function registerHash(
acenolaza marked this conversation as resolved.
Show resolved Hide resolved
bytes32 hashType,
bytes32 hash,
uint256 timestamp,
bytes[] calldata signatures
) external override {
require(hashType != bytes32(0), "Hash type is zero");
EnumerableSet.AddressSet storage signers = _hashTypeToSigners[hashType];
require(signers.length() != 0, "Signers have not been set");
require(
signatures.length == signers.length(),
"Invalid number of signatures"
);
acenolaza marked this conversation as resolved.
Show resolved Hide resolved
for (uint256 ind = 0; ind < signers.length(); ind++) {
acenolaza marked this conversation as resolved.
Show resolved Hide resolved
require(
_hashTypedDataV4(
keccak256(
abi.encode(
_SIGNED_HASH_TYPE_HASH,
hashType,
hash,
timestamp
)
)
).recover(signatures[ind]) == signers.at(ind),
"Signature mismatch"
);
}
hashTypeToHash[hashType] = hash;
hashTypeToTimestamp[hashType] = timestamp;
emit RegisteredHash(hashType, hash, timestamp);
}
}
44 changes: 44 additions & 0 deletions contracts/utils/interfaces/ITimestampedHashRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface ITimestampedHashRegistry {
event SetupSigners(bytes32 indexed hashType, address[] signers);

event AddedSigner(bytes32 indexed hashType, address signer);

event RemovedSigner(bytes32 indexed hashType, address signer);

event RegisteredHash(
bytes32 indexed hashType,
bytes32 hash,
uint256 timestamp
);

function setupSigners(
bytes32 hashType,
address[] calldata signers
) external;

function addSigner(bytes32 hashType, address signer) external;

function removeSigner(bytes32 hashType, address signer) external;

function getSigners(
bytes32 hashType
) external view returns (address[] memory signers);

function registerHash(
bytes32 hashType,
bytes32 hash,
uint256 timestamp,
bytes[] calldata signatures
) external;

function hashTypeToHash(
bytes32 hashType
) external view returns (bytes32 hash);

function hashTypeToTimestamp(
bytes32 hashType
) external view returns (uint256 timestamp);
}
12 changes: 8 additions & 4 deletions test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,18 @@ module.exports = {
[adminRole, ethers.utils.solidityKeccak256(['string'], [roleDescription])]
);
},
expiringMetaTxDomain: async (expiringMetaTxForwarder) => {
buildEIP712Domain: (name, chainId, verifyingContract) => {
return {
name: 'ExpiringMetaTxForwarder',
name,
version: '1.0.0',
chainId: (await expiringMetaTxForwarder.provider.getNetwork()).chainId,
verifyingContract: expiringMetaTxForwarder.address,
chainId,
verifyingContract,
};
},
expiringMetaTxDomain: async (expiringMetaTxForwarder) => {
const chainId = (await expiringMetaTxForwarder.provider.getNetwork()).chainId;
return module.exports.buildEIP712Domain('ExpiringMetaTxForwarder', chainId, expiringMetaTxForwarder.address);
},
expiringMetaTxTypes: () => {
return {
ExpiringMetaTx: [
Expand Down
Loading
Loading