Skip to content

Commit

Permalink
feat: add quorum factory and factory tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ZzzzHui committed Jan 17, 2024
1 parent c67c7fb commit 1247e9f
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 0 deletions.
5 changes: 5 additions & 0 deletions onchain/rollups/.changeset/stupid-doors-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/rollups": minor
---

Added `QuorumFactory` contract and `IQuorumFactory` interface
45 changes: 45 additions & 0 deletions onchain/rollups/contracts/consensus/quorum/IQuorumFactory.sol
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 {Quorum} from "./Quorum.sol";

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

/// @notice A new quorum was deployed.
/// @param quorum The quorum
/// @dev MUST be triggered on a successful call to `newQuorum`.
event QuorumCreated(Quorum quorum);

// Permissionless functions

/// @notice Deploy a new quorum.
/// @param validators the list of validators
/// @return The quorum
/// @dev On success, MUST emit a `QuorumCreated` event.
function newQuorum(address[] calldata validators) external returns (Quorum);

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

/// @notice Calculate the address of a quorum to be deployed deterministically.
/// @param validators the list of 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 a quorum.
function calculateQuorumAddress(
address[] calldata validators,
bytes32 salt
) external view returns (address);
}
50 changes: 50 additions & 0 deletions onchain/rollups/contracts/consensus/quorum/QuorumFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// (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";

/// @title Quorum Factory
/// @notice Allows anyone to reliably deploy a new `Quorum` contract.
contract QuorumFactory is IQuorumFactory {
function newQuorum(
address[] calldata validators
) external override returns (Quorum) {
Quorum quorum = new Quorum(validators);

emit QuorumCreated(quorum);

return quorum;
}

function newQuorum(
address[] calldata validators,
bytes32 salt
) external override returns (Quorum) {
Quorum quorum = new Quorum{salt: salt}(validators);

emit QuorumCreated(quorum);

return quorum;
}

function calculateQuorumAddress(
address[] calldata validators,
bytes32 salt
) external view override returns (address) {
return
Create2.computeAddress(
salt,
keccak256(
abi.encodePacked(
type(Quorum).creationCode,
abi.encode(validators)
)
)
);
}
}
89 changes: 89 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,89 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

/// @title Quorum Factory Test
pragma solidity ^0.8.8;

import {QuorumFactory} from "contracts/consensus/quorum/QuorumFactory.sol";
import {Quorum} from "contracts/consensus/quorum/Quorum.sol";
import {Vm} from "forge-std/Vm.sol";

import {TestBase} from "../../util/TestBase.sol";

contract QuorumFactoryTest is TestBase {
QuorumFactory factory;
uint256 internal constant QUORUM_MAX_SIZE = 50;

event QuorumCreated(Quorum quorum);

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

function testNewQuorum(uint256 seed) public {
uint256 numOfValidators = bound(seed, 1, QUORUM_MAX_SIZE);
address[] memory validators = generateAddresses(numOfValidators);

vm.recordLogs();

Quorum quorum = factory.newQuorum(validators);

testNewQuorumAux(validators, quorum);
}

function testNewQuorumAux(
address[] memory validators,
Quorum quorum
) internal {
Vm.Log[] memory entries = vm.getRecordedLogs();

uint256 numQuorumCreated;
for (uint256 i; i < entries.length; ++i) {
Vm.Log memory entry = entries[i];

if (
entry.emitter == address(factory) &&
entry.topics[0] == QuorumCreated.selector
) {
++numQuorumCreated;
Quorum eventQuorum = abi.decode(entry.data, (Quorum));
assertEq(address(eventQuorum), address(quorum));
}
}
assertEq(numQuorumCreated, 1);

uint256 numOfValidators = validators.length;
assertEq(numOfValidators, quorum.numOfValidators());
for (uint256 i; i < numOfValidators; ++i) {
assertEq(validators[i], quorum.validatorById(i + 1));
}
}

function testNewQuorumDeterministic(uint256 seed, bytes32 salt) public {
uint256 numOfValidators = bound(seed, 1, QUORUM_MAX_SIZE);
address[] memory validators = generateAddresses(numOfValidators);

address precalculatedAddress = factory.calculateQuorumAddress(
validators,
salt
);

vm.recordLogs();

Quorum quorum = factory.newQuorum(validators, salt);

testNewQuorumAux(validators, quorum);

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

precalculatedAddress = factory.calculateQuorumAddress(validators, salt);

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

// Cannot deploy a quorum with the same salt twice
vm.expectRevert();
factory.newQuorum(validators, salt);
}
}

0 comments on commit 1247e9f

Please sign in to comment.