Skip to content

Commit

Permalink
feat: quorumFactory, interface for quorumFactory and deployment scrip…
Browse files Browse the repository at this point in the history
…t for new contracts.
  • Loading branch information
Daniel Ortega committed Oct 9, 2023
1 parent a3b1038 commit 3f5cede
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `abi` directory to the `@cartesi/rollups` package, ideal for language bindings.
- `Quorum`: Consensus reached by the majority of signers
- `Quorum`: Consensus reached by the majority of signers.
- `QuorumFactory`: Allows anyone to deploy `Quorum` contracts. Supports deterministic deployment.

### Removed

Expand Down
Binary file added onchain/.yarn/install-state.gz
Binary file not shown.
56 changes: 56 additions & 0 deletions onchain/rollups/contracts/consensus/quorum/IQuorumFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {Quorum} from "./Quorum.sol";
import {IHistory} from "../../history/IHistory.sol";


/// @title Quorum factory interface
interface IQuorumFactory {
// Events

/// @notice A new quorum was deployed.
/// @param _quorumValidators The initial set of quorum validators
/// @param quorum The quorum
/// @dev MUST be triggered on a successful call to `newQuorum`.
event QuorumCreated(address[] _quorumValidators, Quorum quorum);

// Permissionless functions

/// @notice Deploy a new quorum.
/// @param _quorumValidators The initial set of quorum validators
/// @return The quorum
/// @dev On success, MUST emit an `QuorumCreated` event.
function newQuorum(
address[] calldata _quorumValidators,
uint256[] calldata _shares,
IHistory _history
) external returns (Quorum);

/// @notice Deploy a new quorum deterministically.
/// @param _quorumValidators The initial set of quorum validators
/// @param _salt The salt used to deterministically generate the quorum address
/// @return The quorum
/// @dev On success, MUST emit an `QuorumCreated` event.
function newQuorum(
address[] calldata _quorumValidators,
uint256[] calldata _shares,
IHistory _history,
bytes32 _salt
) external returns (Quorum);

/// @notice Calculate the address of an quorum to be deployed deterministically.
/// @param _quorumValidators The initial set of quorum validators
/// @param _salt The salt used to deterministically generate the quorum address
/// @return The deterministic quorum address
/// @dev Beware that only the `newQuorum` function with the `_salt` parameter
/// is able to deterministically deploy an quorum.
function calculateQuorumAddress(
address[] calldata _quorumValidators,
uint256[] calldata _shares,
IHistory _history,
bytes32 _salt
) external view returns (address);
}
57 changes: 57 additions & 0 deletions onchain/rollups/contracts/consensus/quorum/QuorumFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

import {IQuorumFactory} from "./IQuorumFactory.sol";
import {Quorum} from "./Quorum.sol";
import {IHistory} from "../../history/IHistory.sol";

/// @title Quorum factory
/// @notice Allows anyone to reliably deploy a new `Quorum` contract.
contract QuorumFactory is IQuorumFactory {
function newQuorum(
address[] calldata _quorumValidators,
uint256[] calldata _shares,
IHistory _history
) external override returns (Quorum) {
Quorum quorum = new Quorum(_quorumValidators,_shares,_history);

emit QuorumCreated(_quorumValidators, quorum);

return quorum;
}

function newQuorum(
address[] calldata _quorumValidators,
uint256[] calldata _shares,
IHistory _history,
bytes32 _salt
) external override returns (Quorum) {
Quorum quorum = new Quorum{salt: _salt}(_quorumValidators, _shares, _history);

emit QuorumCreated(_quorumValidators, quorum);

return quorum;
}

function calculateQuorumAddress(
address[] calldata _quorumValidators,
uint256[] calldata _shares,
IHistory _history,
bytes32 _salt
) external view override returns (address) {
return
Create2.computeAddress(
_salt,
keccak256(
abi.encodePacked(
type(Quorum).creationCode,
abi.encode(_quorumValidators, _shares, _history)
)
)
);
}
}
1 change: 1 addition & 0 deletions onchain/rollups/deploy/02_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {

const { Bitmask, MerkleV2 } = await deployments.all();

await deployments.deploy("QuorumFactory", opts);
await deployments.deploy("CartesiDAppFactory", {
...opts,
libraries: {
Expand Down
124 changes: 124 additions & 0 deletions onchain/rollups/test/foundry/consensus/quorum/QuorumFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// (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 {QuorumFactory} from "contracts/consensus/quorum/QuorumFactory.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 QuorumFactoryTest is TestBase {

QuorumFactory factory;

event QuorumCreated(address[] quorumValidators, Quorum quorum);

function setUp() public {
factory = new QuorumFactory();
}

function testNewQuorum(
uint256 _numValidators
) public {
vm.assume(_numValidators>1);
vm.assume(_numValidators<50);

address[] memory quorumValidators = generateValidators(_numValidators);
uint256[] memory shares = generateShares(quorumValidators);

IHistory history = new History(msg.sender);

vm.recordLogs();

Quorum quorum = factory.newQuorum(quorumValidators, shares, history);

emit QuorumCreated(quorumValidators, quorum);

//assertEq(quorum.getHistory() == bytes32(history);
//decodeFactoryLogs(quorumValidators, quorum);
}

function testNewQuorumDeterministic(
uint256 _numValidators,
bytes32 _salt
) public {
vm.assume(_numValidators>1);
vm.assume(_numValidators<50);

address[] memory quorumValidators = generateValidators(_numValidators);
uint256[] memory shares = generateShares(quorumValidators);

IHistory history = new History(msg.sender);

address precalculatedAddress = factory.calculateQuorumAddress(quorumValidators, shares, history, _salt);

vm.recordLogs();

Quorum quorum = factory.newQuorum(quorumValidators, shares, history, _salt);

emit QuorumCreated(quorumValidators, quorum);

// Precalculated address must match actual address
assertEq(precalculatedAddress, address(quorum));
}

function testAlreadyDeployedNewQuorumDeterministic(
uint256 _numValidators,
bytes32 _salt
) public {
vm.assume(_numValidators>1);
vm.assume(_numValidators<50);

address[] memory quorumValidators = generateValidators(_numValidators);
uint256[] memory shares = generateShares(quorumValidators);

IHistory history = new History(msg.sender);

factory.newQuorum(quorumValidators, shares, history, _salt);

//Deploy already deployed quorum
vm.expectRevert();
factory.newQuorum(quorumValidators, shares, history, _salt);
}

// HELPER FUNCTIONS
function generateValidators(uint256 _numValidators) internal returns(address[] memory){
address[] memory validators = new address[](_numValidators);
for (uint256 i = 0; i < _numValidators; i++) {
validators[i] = vm.addr(i+1);
}
return validators;
}

function generateShares(address[] memory validators) internal returns(uint256[] memory){
//generate a random number of shares for each validator
uint256[] memory shares = new uint256[](validators.length);
for (uint256 i; i < shares.length; ++i) {
uint256 share = uint256(
keccak256(abi.encodePacked(i, validators[i]))) % 100;
shares[i] = (share > 0) ? share : validators.length;
}
return shares;
}

/*function decodeFactoryLogs(address[] memory _quorumValidators, Quorum quorum) internal {
Vm.Log[] memory entries = vm.getRecordedLogs();
address[] memory a;
address b;
(a, b) = abi.decode(entries[0].data, (address[], address));
assertEq(_quorumValidators, a); //entry.emitter == address(factory)
assertEq(address(quorum), b);
}*/
}


0 comments on commit 3f5cede

Please sign in to comment.