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

Commit

Permalink
Make keyper set threshold publish eon key
Browse files Browse the repository at this point in the history
This adds a new contract and contract role to the Keyper contracts:

The contract `EonKeyPublish` is the entrypoint for keypers to announce a
new Eon key. The contract tracks that the configured threshold of
keypers agreed on the same eon key, and only then forwards it to the
`KeyBroadcastContract`. The existing logic was changed, so that only a
contract, that implements the interface `EonKeyPublisher`, is allowed to
call the broadcasting contract. A publisher must be set for each
`KeyperSet` before it gets finalized.
  • Loading branch information
konradkonrad committed Feb 1, 2024
1 parent 7d7803c commit dff2b70
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 12 deletions.
59 changes: 59 additions & 0 deletions src/EonKeyPublish.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "src/KeyBroadcastContract.sol";

contract EonKeyPublish is EonKeyPublisher {
mapping(uint64 => bytes) private keys;
uint64[] publishers;
KeyperSet keyperSet;
KeyBroadcastContract broadcaster;
uint64 eon;

constructor(
KeyperSet _keyperSet,
KeyBroadcastContract _broadcaster,
uint64 _eon
) {
keyperSet = KeyperSet(_keyperSet);
broadcaster = KeyBroadcastContract(_broadcaster);
eon = _eon;
}

function eonKeyConfirmed(bytes memory eonKey) public view returns (bool) {
uint required = keyperSet.getThreshold();
if (publishers.length >= keyperSet.getThreshold()) {
for (uint i = 0; i < publishers.length; i++) {
if (keccak256(keys[publishers[i]]) == keccak256(eonKey)) {
required--;
if (required == 0) {
break;
}
}
}
}
return required <= 0;
}

function publishEonKey(bytes memory eonKey) public {
if (eonKey.length == 0) {
revert InvalidKey();
}
if (!keyperSet.isFinalized()) {
revert KeyperSetNotFinalized();
}
uint64 keyperId = uint64(keyperSet.keyperIndex(msg.sender));
if (keyperId < 0) {
revert NotAllowed();
}
publishers.push(keyperId);
if (eonKeyConfirmed(eonKey)) {
// broadcast
broadcaster.broadcastEonKey(eon, eonKey);
//exit
return;
}
// only store key, if not yet broadcast
keys[keyperId] = eonKey;
}
}
15 changes: 11 additions & 4 deletions src/KeyBroadcastContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ error InvalidKey();
error AlreadyHaveKey();
error NotAllowed();

interface EonKeyPublisher {
function eonKeyConfirmed(bytes memory eonKey) external returns (bool);
}

contract KeyBroadcastContract {
mapping(uint64 => bytes) private keys;
KeyperSetManager private keyperSetManager;
Expand All @@ -25,10 +29,13 @@ contract KeyBroadcastContract {
if (keys[eon].length > 0) {
revert AlreadyHaveKey();
}
if (
!KeyperSet(keyperSetManager.getKeyperSetAddress(eon))
.isAllowedToBroadcastEonKey(msg.sender)
) {
KeyperSet keyperSet = KeyperSet(
keyperSetManager.getKeyperSetAddress(eon)
);
if (!keyperSet.isAllowedToBroadcastEonKey(msg.sender)) {
revert NotAllowed();
}
if (!EonKeyPublisher(keyperSet.getPublisher()).eonKeyConfirmed(key)) {
revert NotAllowed();
}

Expand Down
24 changes: 23 additions & 1 deletion src/KeyperSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ pragma solidity ^0.8.22;
import "openzeppelin/contracts/access/Ownable.sol";

error AlreadyFinalized();
error NotRegistered();

contract KeyperSet is Ownable {
bool finalized;
address[] members;
uint64 threshold;
address broadcaster;
address publisher;

constructor() Ownable(msg.sender) {}

Expand All @@ -33,6 +35,10 @@ contract KeyperSet is Ownable {
return threshold;
}

function getPublisher() external view returns (address) {
return publisher;
}

function addMembers(address[] calldata newMembers) public onlyOwner {
if (finalized) {
revert AlreadyFinalized();
Expand All @@ -56,13 +62,29 @@ contract KeyperSet is Ownable {
broadcaster = _broadcaster;
}

function setPublisher(address _publisher) public onlyOwner {
if (finalized) {
revert AlreadyFinalized();
}
publisher = _publisher;
}

function setFinalized() public onlyOwner {
finalized = true;
}

function keyperIndex(address candidate) public view returns (int64) {
for (uint64 i = 0; i < members.length; i++) {
if (members[i] == candidate) {
return int64(i);
}
}
return -1;
}

function isAllowedToBroadcastEonKey(
address a
) external view returns (bool) {
return a == broadcaster;
return a == publisher;
}
}
96 changes: 96 additions & 0 deletions test/EonKeyPublish.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "forge-std/Test.sol";
import "../src/EonKeyPublish.sol";

contract EonKeyPublishTest is Test {
KeyperSetManager public manager;
EonKeyPublish public eonKeyPublish;
KeyperSet public keyperSet0;
KeyperSet public keyperSet;
KeyBroadcastContract public broadcastContract;
address public initializer;
address public dao;

function setUp() public {
initializer = address(19);
dao = address(42);
manager = new KeyperSetManager(initializer);
vm.prank(initializer);
manager.initialize(dao, address(420));
broadcastContract = new KeyBroadcastContract(address(manager));
keyperSet = new KeyperSet();
keyperSet0 = new KeyperSet();
keyperSet0.setFinalized();
vm.prank(dao);
manager.addKeyperSet(uint64(10), address(keyperSet0));
eonKeyPublish = new EonKeyPublish(keyperSet, broadcastContract, 1);
keyperSet.setKeyBroadcaster(address(broadcastContract));
}

function testPublishEonKeyNotFinalized() public {
vm.expectRevert(KeyperSetNotFinalized.selector);
eonKeyPublish.publishEonKey("deadbeef");
}

function testAllowedToBroadcast() public {
KeyperSet ks;
EonKeyPublish publisher;
uint64 eon = 2;
address[] memory members = new address[](3);
members[0] = address(81);
members[1] = address(82);
members[2] = address(83);
ks = new KeyperSet();
publisher = new EonKeyPublish(ks, broadcastContract, eon);
ks.setKeyBroadcaster(address(broadcastContract));
ks.setPublisher(address(publisher));
ks.addMembers(members);
ks.setFinalized();
vm.prank(dao);
manager.addKeyperSet(uint64(10), address(ks));
vm.prank(address(publisher));
assertEq(ks.isAllowedToBroadcastEonKey(address(publisher)), true);
}

function testPublishEonKey() public {
uint64 eon = 1;
address[] memory members = new address[](5);
members[0] = address(91);
members[1] = address(92);
members[2] = address(93);
members[3] = address(94);
members[4] = address(95);
keyperSet.addMembers(members);
keyperSet.setThreshold(3);
keyperSet.setPublisher(address(eonKeyPublish));
keyperSet.setFinalized();
assertEq(keyperSet.getThreshold(), 3);
vm.startPrank(dao);
manager.addKeyperSet(uint64(block.number + 10), address(keyperSet));
vm.stopPrank();
for (uint i = 0; i < keyperSet.getThreshold(); i++) {
vm.prank(members[i]);
eonKeyPublish.publishEonKey(bytes("deadbeef"));
}
assertEq(keyperSet.getPublisher(), address(eonKeyPublish));
assertEq(
keyperSet.isAllowedToBroadcastEonKey(address(eonKeyPublish)),
true
);
uint threshold = keyperSet.getThreshold();
vm.startPrank(members[threshold]);
assertEq(broadcastContract.getEonKey(eon), bytes(""));
// calling broadcast directly should not be allowed:
assertEq(
keyperSet.isAllowedToBroadcastEonKey(members[threshold]),
false
);
vm.expectRevert(NotAllowed.selector);
broadcastContract.broadcastEonKey(eon, bytes("deadbeef"));
eonKeyPublish.publishEonKey(bytes("deadbeef"));
assertEq(broadcastContract.getEonKey(eon), bytes("deadbeef"));
vm.stopPrank();
}
}
20 changes: 16 additions & 4 deletions test/KeyBroadcastContract.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import "../src/KeyBroadcastContract.sol";
import "../src/KeyperSetManager.sol";
import "../src/KeyperSet.sol";

contract MockPublisher is EonKeyPublisher {
function eonKeyConfirmed(bytes memory eonKey) external returns (bool) {
return true;
}
}

contract KeyBroadcastTest is Test {
KeyBroadcastContract public keyBroadcastContract;
KeyperSetManager public keyperSetManager;
Expand All @@ -15,6 +21,8 @@ contract KeyBroadcastTest is Test {
address public sequencer;
address public broadcaster0;
address public broadcaster1;
MockPublisher public publisher0;
MockPublisher public publisher1;

event EonKeyBroadcast(uint64 eon, bytes key);

Expand All @@ -24,6 +32,8 @@ contract KeyBroadcastTest is Test {
sequencer = address(420);
broadcaster0 = address(1);
broadcaster1 = address(2);
publisher0 = new MockPublisher();
publisher1 = new MockPublisher();

keyperSetManager = new KeyperSetManager(initializer);
vm.prank(initializer);
Expand All @@ -32,13 +42,15 @@ contract KeyBroadcastTest is Test {
address(keyperSetManager)
);
keyperSet0 = new KeyperSet();
keyperSet0.setPublisher(address(publisher0));
keyperSet0.setKeyBroadcaster(broadcaster0);
keyperSet0.setFinalized();

vm.prank(dao);
keyperSetManager.addKeyperSet(100, address(keyperSet0));

keyperSet1 = new KeyperSet();
keyperSet1.setPublisher(address(publisher1));
keyperSet1.setKeyBroadcaster(broadcaster1);
keyperSet1.setFinalized();

Expand All @@ -62,25 +74,25 @@ contract KeyBroadcastTest is Test {

function testBroadcastEonKeyDuplicate() public {
bytes memory key = bytes("foo bar");
vm.prank(broadcaster1);
vm.prank(address(publisher1));
keyBroadcastContract.broadcastEonKey(1, key);

vm.expectRevert(AlreadyHaveKey.selector);
vm.prank(broadcaster1);
vm.prank(address(publisher1));
keyBroadcastContract.broadcastEonKey(1, key);
}

function testBroadcastEonKeyEmitsEvent() public {
vm.expectEmit(address(keyBroadcastContract));
bytes memory key = bytes("foo bar");
emit EonKeyBroadcast(1, key);
vm.prank(broadcaster1);
vm.prank(address(publisher1));
keyBroadcastContract.broadcastEonKey(1, key);
}

function testGetEonKey() public {
assertEq(keyBroadcastContract.getEonKey(1), bytes(""));
vm.prank(broadcaster1);
vm.prank(address(publisher1));
keyBroadcastContract.broadcastEonKey(1, bytes("foo bar"));
assertEq(keyBroadcastContract.getEonKey(1), bytes("foo bar"));
}
Expand Down
25 changes: 22 additions & 3 deletions test/KeyperSet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ pragma solidity ^0.8.22;

import "forge-std/Test.sol";
import "../src/KeyperSet.sol";
import "../src/KeyBroadcastContract.sol";

contract MockPublisher is EonKeyPublisher {
function eonKeyConfirmed(bytes memory eonKey) external returns (bool) {
return true;
}
}

contract KeyperSetRevertAfterFinalizedTest is Test {
KeyperSet public keyperSet;
Expand Down Expand Up @@ -129,10 +136,22 @@ contract KeyperSetTest is Test {
}

function testBroadcaster() public {
keyperSet.setKeyBroadcaster(address(5));
keyperSet.setKeyBroadcaster(address(19));
MockPublisher publisher = new MockPublisher();
keyperSet.setPublisher(address(publisher));
address[] memory members = new address[](2);
members[0] = address(1);
members[1] = address(2);
keyperSet.addMembers(members);

keyperSet.setFinalized();

assertEq(keyperSet.isAllowedToBroadcastEonKey(address(1)), false);
assertEq(keyperSet.isAllowedToBroadcastEonKey(address(5)), true);
assertEq(
keyperSet.isAllowedToBroadcastEonKey(address(publisher)),
true
);
assertEq(keyperSet.isAllowedToBroadcastEonKey(members[0]), false);
assertEq(keyperSet.isAllowedToBroadcastEonKey(members[1]), false);
assertEq(keyperSet.isAllowedToBroadcastEonKey(address(19)), false);
}
}

0 comments on commit dff2b70

Please sign in to comment.