-
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.
feat: add quorum factory and factory tests
- Loading branch information
Showing
4 changed files
with
189 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
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
45
onchain/rollups/contracts/consensus/quorum/IQuorumFactory.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 {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
50
onchain/rollups/contracts/consensus/quorum/QuorumFactory.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,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
89
onchain/rollups/test/foundry/consensus/quorum/QuorumFactory.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,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); | ||
} | ||
} |