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

Commit

Permalink
feat(contracts): add quorum
Browse files Browse the repository at this point in the history
  • Loading branch information
guidanoli committed Jul 25, 2023
1 parent 79a6312 commit 7be0b31
Showing 1 changed file with 133 additions and 0 deletions.
133 changes: 133 additions & 0 deletions onchain/rollups/contracts/consensus/quorum/Quorum.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright Cartesi Pte. Ltd.

// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

pragma solidity ^0.8.8;

import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {AbstractConsensus} from "../AbstractConsensus.sol";
import {IConsensus} from "../IConsensus.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 inherits from `AbstractConsensus` and OpenZeppelin's `AccessControlEnumerable` contract.
/// For more information on `AccessControlEnumerable`, please consult OpenZeppelin's official documentation.
contract Quorum is AbstractConsensus, AccessControlEnumerable {
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 An ERC-20 token transfer failed (`transfer` returned `false`).
error ERC20TokenTransferFailed();

/// @notice Construct a Quorum consensus
/// @param _validators the list of validators
constructor(IHistory _history, address[] memory _validators) {
// 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 Get the history contract.
/// @return The history contract
function getHistory() external view returns (IHistory) {
return history;
}

/// @notice Get a claim from the current history.
/// The encoding of `_proofContext` might vary depending on the
/// implementation of the current history contract.
/// @inheritdoc IConsensus
function getClaim(
address _dapp,
bytes calldata _proofContext
) external view override returns (bytes32, uint256, uint256) {
return history.getClaim(_dapp, _proofContext);
}

/// @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.
claimYeas.add(msg.sender);

// Get number of validators in favour of the claim.
uint256 numOfVotesInFavour = claimYeas.length();

// Get the number of validators in the quorum.
uint256 quorumSize = getRoleMemberCount(VALIDATOR_ROLE);

// If this claim already has half of the quorum's approval,
// then we can submit it to the history contract.
if (numOfVotesInFavour > quorumSize / 2) {
history.submitClaim(_claimData);
}
}

/// @notice Equally share all tokens from some ERC-20 contract
/// amongst all validators in the quorum.
/// @param _token The token contract
function shareERC20Tokens(IERC20 _token) external {
// Get the total amount of ERC-20 tokens held by the quorum.
uint256 balance = _token.balanceOf(address(this));

// Get the number of validators in the quorum.
uint256 quorumSize = getRoleMemberCount(VALIDATOR_ROLE);

// Calculate the share of ERC-20 tokens for each validator.
uint256 tokensPerValidator = balance / quorumSize;

// Iterate through the validator set.
for (uint256 i; i < quorumSize; ++i) {
// Get the i-th validator.
address validator = getRoleMember(VALIDATOR_ROLE, i);

// Transfer the share of ERC-20 tokens to the i-th validator.
bool success = _token.transfer(validator, tokensPerValidator);

// If the transfer fails, revert.
if (!success) {
revert ERC20TokenTransferFailed();
}
}
}
}

0 comments on commit 7be0b31

Please sign in to comment.