-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
144 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// (c) Cartesi and individual authors (see AUTHORS) | ||
// SPDX-License-Identifier: Apache-2.0 (see LICENSE) | ||
|
||
pragma solidity ^0.8.8; | ||
|
||
import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; | ||
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | ||
import {PaymentSplitter} from "@openzeppelin/contracts/finance/PaymentSplitter.sol"; | ||
|
||
import {AbstractConsensus} from "../AbstractConsensus.sol"; | ||
import {IHistory} from "../../history/IHistory.sol"; | ||
|
||
/// @title Quorum consensus | ||
/// @notice A consensus model controlled by a small set of addresses, the validators. | ||
/// @dev This contract uses several OpenZeppelin contracts: | ||
/// `AccessControlEnumerable`, `EnumerableSet`, and `PaymentSplitter`. | ||
/// For more information on those, please consult OpenZeppelin's official documentation. | ||
contract Quorum is AbstractConsensus, AccessControlEnumerable, PaymentSplitter { | ||
using EnumerableSet for EnumerableSet.AddressSet; | ||
|
||
/// @notice The validator role. | ||
/// @dev Only validators can submit claims. | ||
bytes32 public constant VALIDATOR_ROLE = keccak256("VALIDATOR_ROLE"); | ||
|
||
/// @notice The history contract. | ||
/// @dev See the `getHistory` function. | ||
IHistory internal immutable history; | ||
|
||
/// @notice For each claim, the set of validators that agree | ||
/// that it should be submitted to the history contract. | ||
mapping(bytes => EnumerableSet.AddressSet) internal yeas; | ||
|
||
/// @notice Construct a Quorum consensus | ||
/// @param _validators the list of validators | ||
/// @param _shares the list of shares | ||
/// @param _history the history contract | ||
constructor( | ||
address[] memory _validators, | ||
uint256[] memory _shares, | ||
IHistory _history | ||
) PaymentSplitter(_validators, _shares) { | ||
// Iterate through the array of validators, | ||
// and grant to each the validator role. | ||
for (uint256 i; i < _validators.length; ++i) { | ||
_grantRole(VALIDATOR_ROLE, _validators[i]); | ||
} | ||
|
||
// Set history. | ||
history = _history; | ||
} | ||
|
||
/// @notice Submits a claim for voting. | ||
/// If this is the claim that reaches the majority, then | ||
/// the claim is submitted to the history contract. | ||
/// The encoding of `_claimData` might vary depending on the | ||
/// implementation of the current history contract. | ||
/// @param _claimData Data for submitting a claim | ||
/// @dev Can only be called by a validator, | ||
/// and the `Quorum` contract must have ownership over | ||
/// its current history contract. | ||
function submitClaim( | ||
bytes calldata _claimData | ||
) external onlyRole(VALIDATOR_ROLE) { | ||
// Get the set of validators in favour of the claim | ||
EnumerableSet.AddressSet storage claimYeas = yeas[_claimData]; | ||
|
||
// Add the message sender to such set. | ||
// If the `add` function returns `true`, | ||
// then the message sender was not in the set. | ||
if (claimYeas.add(msg.sender)) { | ||
// Get the number of validators in favour of the claim, | ||
// taking into account the message sender as well. | ||
uint256 numOfVotesInFavour = claimYeas.length(); | ||
|
||
// Get the number of validators in the quorum. | ||
uint256 quorumSize = getRoleMemberCount(VALIDATOR_ROLE); | ||
|
||
// If this claim has now just over half of the quorum's approval, | ||
// then we can submit it to the history contract. | ||
if (numOfVotesInFavour == 1 + quorumSize / 2) { | ||
history.submitClaim(_claimData); | ||
} | ||
} | ||
} | ||
|
||
/// @notice Get the history contract. | ||
/// @return The history contract | ||
function getHistory() external view returns (IHistory) { | ||
return history; | ||
} | ||
|
||
function getClaim( | ||
address _dapp, | ||
bytes calldata _proofContext | ||
) external view override returns (bytes32, uint256, uint256) { | ||
return history.getClaim(_dapp, _proofContext); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
onchain/rollups/test/foundry/consensus/quorum/Quorum.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// (c) Cartesi and individual authors (see AUTHORS) | ||
// SPDX-License-Identifier: Apache-2.0 (see LICENSE) | ||
|
||
pragma solidity ^0.8.8; | ||
|
||
import {Vm} from "forge-std/Vm.sol"; | ||
|
||
import {Quorum} from "contracts/consensus/quorum/Quorum.sol"; | ||
import {History} from "contracts/history/History.sol"; | ||
import {IHistory} from "contracts/history/IHistory.sol"; | ||
|
||
import {TestBase} from "../../util/TestBase.sol"; | ||
|
||
import "forge-std/console.sol"; | ||
|
||
contract QuorumTest is TestBase { | ||
Quorum quorum; | ||
uint256 constant numOfValidators = 3; | ||
address[] validators; | ||
uint256[] shares; | ||
|
||
function setUp() external { | ||
for (uint256 i; i < numOfValidators; ++i) { | ||
validators.push(vm.addr(i + 1)); | ||
shares.push(i + 1); | ||
} | ||
|
||
quorum = new Quorum(validators, shares, IHistory(vm.addr(1))); | ||
} | ||
|
||
function testSubmitClaim(address _dapp, History.Claim calldata _claim) external { | ||
bytes memory claimData = abi.encode(_dapp, _claim); | ||
|
||
vm.mockCall( | ||
address(quorum.getHistory()), | ||
abi.encodeWithSelector(IHistory.submitClaim.selector, claimData), | ||
abi.encode() | ||
); | ||
|
||
for (uint256 i; i < numOfValidators; ++i) { | ||
vm.prank(validators[i]); | ||
quorum.submitClaim(claimData); | ||
} | ||
} | ||
} |