diff --git a/.changeset/popular-ghosts-fix.md b/.changeset/popular-ghosts-fix.md new file mode 100644 index 00000000..0b0e6bd0 --- /dev/null +++ b/.changeset/popular-ghosts-fix.md @@ -0,0 +1,9 @@ +--- +"@cartesi/rollups": major +--- + +Added an `epochLength` parameter to functions of: + +- `IAuthorityFactory` +- `ISelfHostedApplicationFactory` +- `IQuorumFactory` diff --git a/.changeset/six-schools-live.md b/.changeset/six-schools-live.md new file mode 100644 index 00000000..1d452e53 --- /dev/null +++ b/.changeset/six-schools-live.md @@ -0,0 +1,5 @@ +--- +"@cartesi/rollups": minor +--- + +Added a `getEpochLength` function to `IConsensus` interface. diff --git a/contracts/consensus/AbstractConsensus.sol b/contracts/consensus/AbstractConsensus.sol index a4ead2fc..b854af4d 100644 --- a/contracts/consensus/AbstractConsensus.sol +++ b/contracts/consensus/AbstractConsensus.sol @@ -9,9 +9,19 @@ import {IConsensus} from "./IConsensus.sol"; /// @dev This contract was designed to be inherited by implementations of the `IConsensus` interface /// that only need a simple mechanism of storage and retrieval of accepted claims. abstract contract AbstractConsensus is IConsensus { + /// @notice The epoch length + uint256 private immutable _epochLength; + /// @notice Indexes accepted claims by application contract address. mapping(address => mapping(bytes32 => bool)) private _acceptedClaims; + /// @param epochLength The epoch length + /// @dev Reverts if the epoch length is zero. + constructor(uint256 epochLength) { + require(epochLength > 0, "epoch length must not be zero"); + _epochLength = epochLength; + } + /// @inheritdoc IConsensus function wasClaimAccepted( address appContract, @@ -20,6 +30,11 @@ abstract contract AbstractConsensus is IConsensus { return _acceptedClaims[appContract][claim]; } + /// @inheritdoc IConsensus + function getEpochLength() public view override returns (uint256) { + return _epochLength; + } + /// @notice Accept a claim. /// @param appContract The application contract address /// @param lastProcessedBlockNumber The number of the last processed block diff --git a/contracts/consensus/IConsensus.sol b/contracts/consensus/IConsensus.sol index d7fa5c9e..6313f2dd 100644 --- a/contracts/consensus/IConsensus.sol +++ b/contracts/consensus/IConsensus.sol @@ -62,4 +62,9 @@ interface IConsensus { address appContract, bytes32 claim ) external view returns (bool); + + /// @notice Get the epoch length, in number of base layer blocks. + /// @dev The epoch number of a block is defined as + /// the integer division of the block number by the epoch length. + function getEpochLength() external view returns (uint256); } diff --git a/contracts/consensus/authority/Authority.sol b/contracts/consensus/authority/Authority.sol index 2aa25afe..ab8fccb0 100644 --- a/contracts/consensus/authority/Authority.sol +++ b/contracts/consensus/authority/Authority.sol @@ -13,7 +13,12 @@ import {AbstractConsensus} from "../AbstractConsensus.sol"; /// For more information on `Ownable`, please consult OpenZeppelin's official documentation. contract Authority is AbstractConsensus, Ownable { /// @param initialOwner The initial contract owner - constructor(address initialOwner) Ownable(initialOwner) {} + /// @param epochLength The epoch length + /// @dev Reverts if the epoch length is zero. + constructor( + address initialOwner, + uint256 epochLength + ) AbstractConsensus(epochLength) Ownable(initialOwner) {} /// @notice Submit a claim. /// @param appContract The application contract address diff --git a/contracts/consensus/authority/AuthorityFactory.sol b/contracts/consensus/authority/AuthorityFactory.sol index 74de4e26..321d55f5 100644 --- a/contracts/consensus/authority/AuthorityFactory.sol +++ b/contracts/consensus/authority/AuthorityFactory.sol @@ -12,9 +12,10 @@ import {Authority} from "./Authority.sol"; /// @notice Allows anyone to reliably deploy a new `Authority` contract. contract AuthorityFactory is IAuthorityFactory { function newAuthority( - address authorityOwner + address authorityOwner, + uint256 epochLength ) external override returns (Authority) { - Authority authority = new Authority(authorityOwner); + Authority authority = new Authority(authorityOwner, epochLength); emit AuthorityCreated(authority); @@ -23,9 +24,13 @@ contract AuthorityFactory is IAuthorityFactory { function newAuthority( address authorityOwner, + uint256 epochLength, bytes32 salt ) external override returns (Authority) { - Authority authority = new Authority{salt: salt}(authorityOwner); + Authority authority = new Authority{salt: salt}( + authorityOwner, + epochLength + ); emit AuthorityCreated(authority); @@ -34,6 +39,7 @@ contract AuthorityFactory is IAuthorityFactory { function calculateAuthorityAddress( address authorityOwner, + uint256 epochLength, bytes32 salt ) external view override returns (address) { return @@ -42,7 +48,7 @@ contract AuthorityFactory is IAuthorityFactory { keccak256( abi.encodePacked( type(Authority).creationCode, - abi.encode(authorityOwner) + abi.encode(authorityOwner, epochLength) ) ) ); diff --git a/contracts/consensus/authority/IAuthorityFactory.sol b/contracts/consensus/authority/IAuthorityFactory.sol index 7cf3d719..e76baf9f 100644 --- a/contracts/consensus/authority/IAuthorityFactory.sol +++ b/contracts/consensus/authority/IAuthorityFactory.sol @@ -18,30 +18,40 @@ interface IAuthorityFactory { /// @notice Deploy a new authority. /// @param authorityOwner The initial authority owner + /// @param epochLength The epoch length /// @return The authority /// @dev On success, MUST emit an `AuthorityCreated` event. /// @dev Reverts if the authority owner address is zero. - function newAuthority(address authorityOwner) external returns (Authority); + /// @dev Reverts if the epoch length is zero. + function newAuthority( + address authorityOwner, + uint256 epochLength + ) external returns (Authority); /// @notice Deploy a new authority deterministically. /// @param authorityOwner The initial authority owner + /// @param epochLength The epoch length /// @param salt The salt used to deterministically generate the authority address /// @return The authority /// @dev On success, MUST emit an `AuthorityCreated` event. /// @dev Reverts if the authority owner address is zero. + /// @dev Reverts if the epoch length is zero. function newAuthority( address authorityOwner, + uint256 epochLength, bytes32 salt ) external returns (Authority); /// @notice Calculate the address of an authority to be deployed deterministically. /// @param authorityOwner The initial authority owner + /// @param epochLength The epoch length /// @param salt The salt used to deterministically generate the authority address /// @return The deterministic authority address /// @dev Beware that only the `newAuthority` function with the `salt` parameter /// is able to deterministically deploy an authority. function calculateAuthorityAddress( address authorityOwner, + uint256 epochLength, bytes32 salt ) external view returns (address); } diff --git a/contracts/consensus/quorum/IQuorumFactory.sol b/contracts/consensus/quorum/IQuorumFactory.sol index c1f4ce47..52dd9890 100644 --- a/contracts/consensus/quorum/IQuorumFactory.sol +++ b/contracts/consensus/quorum/IQuorumFactory.sol @@ -18,30 +18,40 @@ interface IQuorumFactory { /// @notice Deploy a new quorum. /// @param validators the list of validators + /// @param epochLength The epoch length /// @return The quorum /// @dev On success, MUST emit a `QuorumCreated` event. /// @dev Duplicates in the `validators` array are ignored. - function newQuorum(address[] calldata validators) external returns (Quorum); + /// @dev Reverts if the epoch length is zero. + function newQuorum( + address[] calldata validators, + uint256 epochLength + ) external returns (Quorum); /// @notice Deploy a new quorum deterministically. /// @param validators the list of validators + /// @param epochLength The epoch length /// @param salt The salt used to deterministically generate the quorum address /// @return The quorum /// @dev On success, MUST emit a `QuorumCreated` event. /// @dev Duplicates in the `validators` array are ignored. + /// @dev Reverts if the epoch length is zero. function newQuorum( address[] calldata validators, + uint256 epochLength, bytes32 salt ) external returns (Quorum); /// @notice Calculate the address of a quorum to be deployed deterministically. /// @param validators the list of validators + /// @param epochLength The epoch length /// @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, + uint256 epochLength, bytes32 salt ) external view returns (address); } diff --git a/contracts/consensus/quorum/Quorum.sol b/contracts/consensus/quorum/Quorum.sol index b1626cf3..bed6011e 100644 --- a/contracts/consensus/quorum/Quorum.sol +++ b/contracts/consensus/quorum/Quorum.sol @@ -47,8 +47,13 @@ contract Quorum is AbstractConsensus { private _votes; /// @param validators The array of validator addresses + /// @param epochLength The epoch length /// @dev Duplicates in the `validators` array are ignored. - constructor(address[] memory validators) { + /// @dev Reverts if the epoch length is zero. + constructor( + address[] memory validators, + uint256 epochLength + ) AbstractConsensus(epochLength) { uint256 n; for (uint256 i; i < validators.length; ++i) { address validator = validators[i]; diff --git a/contracts/consensus/quorum/QuorumFactory.sol b/contracts/consensus/quorum/QuorumFactory.sol index 332d08b1..d8283111 100644 --- a/contracts/consensus/quorum/QuorumFactory.sol +++ b/contracts/consensus/quorum/QuorumFactory.sol @@ -12,9 +12,10 @@ import {Quorum} from "./Quorum.sol"; /// @notice Allows anyone to reliably deploy a new `Quorum` contract. contract QuorumFactory is IQuorumFactory { function newQuorum( - address[] calldata validators + address[] calldata validators, + uint256 epochLength ) external override returns (Quorum) { - Quorum quorum = new Quorum(validators); + Quorum quorum = new Quorum(validators, epochLength); emit QuorumCreated(quorum); @@ -23,9 +24,10 @@ contract QuorumFactory is IQuorumFactory { function newQuorum( address[] calldata validators, + uint256 epochLength, bytes32 salt ) external override returns (Quorum) { - Quorum quorum = new Quorum{salt: salt}(validators); + Quorum quorum = new Quorum{salt: salt}(validators, epochLength); emit QuorumCreated(quorum); @@ -34,6 +36,7 @@ contract QuorumFactory is IQuorumFactory { function calculateQuorumAddress( address[] calldata validators, + uint256 epochLength, bytes32 salt ) external view override returns (address) { return @@ -42,7 +45,7 @@ contract QuorumFactory is IQuorumFactory { keccak256( abi.encodePacked( type(Quorum).creationCode, - abi.encode(validators) + abi.encode(validators, epochLength) ) ) ); diff --git a/contracts/dapp/ISelfHostedApplicationFactory.sol b/contracts/dapp/ISelfHostedApplicationFactory.sol index cda7a055..ef3e81e1 100644 --- a/contracts/dapp/ISelfHostedApplicationFactory.sol +++ b/contracts/dapp/ISelfHostedApplicationFactory.sol @@ -23,6 +23,7 @@ interface ISelfHostedApplicationFactory { /// @notice Deploy new application and authority contracts deterministically. /// @param authorityOwner The initial authority owner + /// @param epochLength The epoch length /// @param appOwner The initial Application owner /// @param templateHash The initial machine state hash /// @param salt The salt used to deterministically generate the addresses @@ -30,8 +31,10 @@ interface ISelfHostedApplicationFactory { /// @return The authority contract /// @dev Reverts if the authority owner address is zero. /// @dev Reverts if the application owner address is zero. + /// @dev Reverts if the epoch length is zero. function deployContracts( address authorityOwner, + uint256 epochLength, address appOwner, bytes32 templateHash, bytes32 salt @@ -40,6 +43,7 @@ interface ISelfHostedApplicationFactory { /// @notice Calculate the addresses of the application and authority contracts /// to be deployed deterministically. /// @param authorityOwner The initial authority owner + /// @param epochLength The epoch length /// @param appOwner The initial Application owner /// @param templateHash The initial machine state hash /// @param salt The salt used to deterministically generate the addresses @@ -47,6 +51,7 @@ interface ISelfHostedApplicationFactory { /// @return The authority address function calculateAddresses( address authorityOwner, + uint256 epochLength, address appOwner, bytes32 templateHash, bytes32 salt diff --git a/contracts/dapp/SelfHostedApplicationFactory.sol b/contracts/dapp/SelfHostedApplicationFactory.sol index bef712ea..874f3c2a 100644 --- a/contracts/dapp/SelfHostedApplicationFactory.sol +++ b/contracts/dapp/SelfHostedApplicationFactory.sol @@ -47,11 +47,16 @@ contract SelfHostedApplicationFactory is ISelfHostedApplicationFactory { function deployContracts( address authorityOwner, + uint256 epochLength, address appOwner, bytes32 templateHash, bytes32 salt ) external returns (Application application, Authority authority) { - authority = _authorityFactory.newAuthority(authorityOwner, salt); + authority = _authorityFactory.newAuthority( + authorityOwner, + epochLength, + salt + ); application = _applicationFactory.newApplication( authority, @@ -63,12 +68,14 @@ contract SelfHostedApplicationFactory is ISelfHostedApplicationFactory { function calculateAddresses( address authorityOwner, + uint256 epochLength, address appOwner, bytes32 templateHash, bytes32 salt ) external view returns (address application, address authority) { authority = _authorityFactory.calculateAuthorityAddress( authorityOwner, + epochLength, salt ); diff --git a/test/foundry/consensus/authority/Authority.t.sol b/test/foundry/consensus/authority/Authority.t.sol index 97ba6107..16aec45c 100644 --- a/test/foundry/consensus/authority/Authority.t.sol +++ b/test/foundry/consensus/authority/Authority.t.sol @@ -16,12 +16,13 @@ import {LibTopic} from "../../util/LibTopic.sol"; contract AuthorityTest is TestBase { using LibTopic for address; - function testConstructor(address owner) public { + function testConstructor(address owner, uint256 epochLength) public { vm.assume(owner != address(0)); + vm.assume(epochLength > 0); vm.recordLogs(); - Authority authority = new Authority(owner); + Authority authority = new Authority(owner, epochLength); Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -45,29 +46,41 @@ contract AuthorityTest is TestBase { assertEq(numOfOwnershipTransferred, 1); assertEq(authority.owner(), owner); + assertEq(authority.getEpochLength(), epochLength); } - function testRevertsOwnerAddressZero() public { + function testRevertsOwnerAddressZero(uint256 epochLength) public { + vm.assume(epochLength > 0); + vm.expectRevert( abi.encodeWithSelector( Ownable.OwnableInvalidOwner.selector, address(0) ) ); - new Authority(address(0)); + new Authority(address(0), epochLength); + } + + function testRevertsEpochLengthZero(address owner) public { + vm.assume(owner != address(0)); + + vm.expectRevert("epoch length must not be zero"); + new Authority(owner, 0); } function testSubmitClaimRevertsCallerNotOwner( address owner, address notOwner, + uint256 epochLength, address appContract, uint256 lastProcessedBlockNumber, bytes32 claim ) public { vm.assume(owner != address(0)); vm.assume(owner != notOwner); + vm.assume(epochLength > 0); - Authority authority = new Authority(owner); + Authority authority = new Authority(owner, epochLength); vm.expectRevert( abi.encodeWithSelector( @@ -82,13 +95,15 @@ contract AuthorityTest is TestBase { function testSubmitClaim( address owner, + uint256 epochLength, address appContract, uint256 lastProcessedBlockNumber, bytes32 claim ) public { vm.assume(owner != address(0)); + vm.assume(epochLength > 0); - Authority authority = new Authority(owner); + Authority authority = new Authority(owner, epochLength); _expectClaimEvents( authority, @@ -106,12 +121,14 @@ contract AuthorityTest is TestBase { function testWasClaimAccepted( address owner, + uint256 epochLength, address appContract, bytes32 claim ) public { vm.assume(owner != address(0)); + vm.assume(epochLength > 0); - Authority authority = new Authority(owner); + Authority authority = new Authority(owner, epochLength); assertFalse(authority.wasClaimAccepted(appContract, claim)); } diff --git a/test/foundry/consensus/authority/AuthorityFactory.t.sol b/test/foundry/consensus/authority/AuthorityFactory.t.sol index 5ee64369..b351e554 100644 --- a/test/foundry/consensus/authority/AuthorityFactory.t.sol +++ b/test/foundry/consensus/authority/AuthorityFactory.t.sol @@ -4,55 +4,103 @@ /// @title Authority Factory Test pragma solidity ^0.8.22; +import {Vm} from "forge-std/Vm.sol"; import {Test} from "forge-std/Test.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + import {AuthorityFactory, IAuthorityFactory} from "contracts/consensus/authority/AuthorityFactory.sol"; import {Authority} from "contracts/consensus/authority/Authority.sol"; -import {Vm} from "forge-std/Vm.sol"; contract AuthorityFactoryTest is Test { AuthorityFactory _factory; - struct AuthorityCreatedEventData { - address authorityOwner; - Authority authority; - } - function setUp() public { _factory = new AuthorityFactory(); } - function testNewAuthority(address authorityOwner) public { + function testRevertsOwnerAddressZero( + uint256 epochLength, + bytes32 salt + ) public { + vm.assume(epochLength > 0); + + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableInvalidOwner.selector, + address(0) + ) + ); + _factory.newAuthority(address(0), epochLength); + + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableInvalidOwner.selector, + address(0) + ) + ); + _factory.newAuthority(address(0), epochLength, salt); + } + + function testRevertsEpochLengthZero( + address authorityOwner, + bytes32 salt + ) public { + vm.assume(authorityOwner != address(0)); + + vm.expectRevert("epoch length must not be zero"); + _factory.newAuthority(authorityOwner, 0); + + vm.expectRevert("epoch length must not be zero"); + _factory.newAuthority(authorityOwner, 0, salt); + } + + function testNewAuthority( + address authorityOwner, + uint256 epochLength + ) public { vm.assume(authorityOwner != address(0)); + vm.assume(epochLength > 0); vm.recordLogs(); - Authority authority = _factory.newAuthority(authorityOwner); + Authority authority = _factory.newAuthority( + authorityOwner, + epochLength + ); - _testNewAuthorityAux(authorityOwner, authority); + _testNewAuthorityAux(authorityOwner, epochLength, authority); } function testNewAuthorityDeterministic( address authorityOwner, + uint256 epochLength, bytes32 salt ) public { vm.assume(authorityOwner != address(0)); + vm.assume(epochLength > 0); address precalculatedAddress = _factory.calculateAuthorityAddress( authorityOwner, + epochLength, salt ); vm.recordLogs(); - Authority authority = _factory.newAuthority(authorityOwner, salt); + Authority authority = _factory.newAuthority( + authorityOwner, + epochLength, + salt + ); - _testNewAuthorityAux(authorityOwner, authority); + _testNewAuthorityAux(authorityOwner, epochLength, authority); // Precalculated address must match actual address assertEq(precalculatedAddress, address(authority)); precalculatedAddress = _factory.calculateAuthorityAddress( authorityOwner, + epochLength, salt ); @@ -61,11 +109,12 @@ contract AuthorityFactoryTest is Test { // Cannot deploy an authority with the same salt twice vm.expectRevert(); - _factory.newAuthority(authorityOwner, salt); + _factory.newAuthority(authorityOwner, epochLength, salt); } function _testNewAuthorityAux( address authorityOwner, + uint256 epochLength, Authority authority ) internal { Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -81,16 +130,14 @@ contract AuthorityFactoryTest is Test { ) { ++numOfAuthorityCreated; - AuthorityCreatedEventData memory eventData; - - eventData = abi.decode(entry.data, (AuthorityCreatedEventData)); + address authorityAddress = abi.decode(entry.data, (address)); - assertEq(authorityOwner, eventData.authorityOwner); - assertEq(address(authority), address(eventData.authority)); + assertEq(address(authority), authorityAddress); } } assertEq(numOfAuthorityCreated, 1); assertEq(authority.owner(), authorityOwner); + assertEq(authority.getEpochLength(), epochLength); } } diff --git a/test/foundry/consensus/quorum/Quorum.t.sol b/test/foundry/consensus/quorum/Quorum.t.sol index bd50ae2b..921a367b 100644 --- a/test/foundry/consensus/quorum/Quorum.t.sol +++ b/test/foundry/consensus/quorum/Quorum.t.sol @@ -68,12 +68,18 @@ contract QuorumTest is TestBase { using LibQuorum for Quorum; using LibTopic for address; - function testConstructor(uint8 numOfValidators) external { + function testConstructor( + uint8 numOfValidators, + uint256 epochLength + ) external { + vm.assume(epochLength > 0); + address[] memory validators = _generateAddresses(numOfValidators); - Quorum quorum = new Quorum(validators); + Quorum quorum = new Quorum(validators, epochLength); assertEq(quorum.numOfValidators(), numOfValidators); + assertEq(quorum.getEpochLength(), epochLength); for (uint256 i; i < numOfValidators; ++i) { address validator = validators[i]; @@ -83,7 +89,14 @@ contract QuorumTest is TestBase { } } - function testConstructorIgnoresDuplicates() external { + function testRevertsEpochLengthZero(uint8 numOfValidators) external { + vm.expectRevert("epoch length must not be zero"); + new Quorum(_generateAddresses(numOfValidators), 0); + } + + function testConstructorIgnoresDuplicates(uint256 epochLength) external { + vm.assume(epochLength > 0); + address[] memory validators = new address[](7); validators[0] = vm.addr(1); @@ -94,7 +107,7 @@ contract QuorumTest is TestBase { validators[5] = vm.addr(1); validators[6] = vm.addr(3); - Quorum quorum = new Quorum(validators); + Quorum quorum = new Quorum(validators, epochLength); assertEq(quorum.numOfValidators(), 3); @@ -104,10 +117,16 @@ contract QuorumTest is TestBase { } } - function testValidatorId(uint8 numOfValidators, address addr) external { + function testValidatorId( + uint8 numOfValidators, + address addr, + uint256 epochLength + ) external { + vm.assume(epochLength > 0); + address[] memory validators = _generateAddresses(numOfValidators); - Quorum quorum = new Quorum(validators); + Quorum quorum = new Quorum(validators, epochLength); uint256 id = quorum.validatorId(addr); @@ -119,39 +138,47 @@ contract QuorumTest is TestBase { } } - function testValidatorByIdZero(uint8 numOfValidators) external { - Quorum quorum = _deployQuorum(numOfValidators); + function testValidatorByIdZero( + uint8 numOfValidators, + uint256 epochLength + ) external { + Quorum quorum = _deployQuorum(numOfValidators, epochLength); assertEq(quorum.validatorById(0), address(0)); } function testValidatorByIdValid( uint8 numOfValidators, - uint256 id + uint256 id, + uint256 epochLength ) external { numOfValidators = uint8(bound(numOfValidators, 1, type(uint8).max)); id = bound(id, 1, numOfValidators); - Quorum quorum = _deployQuorum(numOfValidators); + Quorum quorum = _deployQuorum(numOfValidators, epochLength); address validator = quorum.validatorById(id); assertEq(quorum.validatorId(validator), id); } function testValidatorByIdTooLarge( uint8 numOfValidators, - uint256 id + uint256 id, + uint256 epochLength ) external { id = bound(id, uint256(numOfValidators) + 1, type(uint256).max); - Quorum quorum = _deployQuorum(numOfValidators); + Quorum quorum = _deployQuorum(numOfValidators, epochLength); assertEq(quorum.validatorById(id), address(0)); } function testSubmitClaimRevertsNotValidator( uint8 numOfValidators, + uint256 epochLength, address caller, Claim calldata claim ) external { + vm.assume(epochLength > 0); + address[] memory validators = _generateAddresses(numOfValidators); - Quorum quorum = new Quorum(validators); + Quorum quorum = new Quorum(validators, epochLength); vm.assume(!_contains(validators, caller)); @@ -163,27 +190,30 @@ contract QuorumTest is TestBase { function testNumOfValidatorsInFavorOf( uint8 numOfValidators, + uint256 epochLength, Claim calldata claim ) external { - Quorum quorum = _deployQuorum(numOfValidators); + Quorum quorum = _deployQuorum(numOfValidators, epochLength); assertEq(quorum.numOfValidatorsInFavorOf(claim), 0); } function testIsValidatorInFavorOf( uint8 numOfValidators, + uint256 epochLength, Claim calldata claim, uint256 id ) external { - Quorum quorum = _deployQuorum(numOfValidators); + Quorum quorum = _deployQuorum(numOfValidators, epochLength); assertFalse(quorum.isValidatorInFavorOf(claim, id)); } function testSubmitClaim( uint8 numOfValidators, + uint256 epochLength, Claim calldata claim ) external { numOfValidators = uint8(bound(numOfValidators, 1, 7)); - Quorum quorum = _deployQuorum(numOfValidators); + Quorum quorum = _deployQuorum(numOfValidators, epochLength); bool[] memory inFavorOf = new bool[](numOfValidators + 1); for (uint256 id = 1; id <= numOfValidators; ++id) { _submitClaimAs(quorum, claim, id); @@ -196,10 +226,13 @@ contract QuorumTest is TestBase { /// @dev Each slot has 256 bits, one for each validator ID. /// The first bit is skipped because validator IDs start from 1. /// Therefore, validator ID 256 is the first to use a new slot. - function testSubmitClaim256(Claim calldata claim) external { + function testSubmitClaim256( + Claim calldata claim, + uint256 epochLength + ) external { uint256 numOfValidators = 256; - Quorum quorum = _deployQuorum(numOfValidators); + Quorum quorum = _deployQuorum(numOfValidators, epochLength); uint256 id = numOfValidators; @@ -212,8 +245,12 @@ contract QuorumTest is TestBase { // Internal functions // ------------------ - function _deployQuorum(uint256 numOfValidators) internal returns (Quorum) { - return new Quorum(_generateAddresses(numOfValidators)); + function _deployQuorum( + uint256 numOfValidators, + uint256 epochLength + ) internal returns (Quorum) { + vm.assume(epochLength > 0); + return new Quorum(_generateAddresses(numOfValidators), epochLength); } function _checkSubmitted( diff --git a/test/foundry/consensus/quorum/QuorumFactory.t.sol b/test/foundry/consensus/quorum/QuorumFactory.t.sol index e4217427..d967ae03 100644 --- a/test/foundry/consensus/quorum/QuorumFactory.t.sol +++ b/test/foundry/consensus/quorum/QuorumFactory.t.sol @@ -18,37 +18,58 @@ contract QuorumFactoryTest is TestBase { _factory = new QuorumFactory(); } - function testNewQuorum(uint256 seed) public { + function testRevertsEpochLengthZero(uint256 seed, bytes32 salt) public { + uint256 numOfValidators = bound(seed, 1, _QUORUM_MAX_SIZE); + address[] memory validators = _generateAddresses(numOfValidators); + + vm.expectRevert("epoch length must not be zero"); + _factory.newQuorum(validators, 0); + + vm.expectRevert("epoch length must not be zero"); + _factory.newQuorum(validators, 0, salt); + } + + function testNewQuorum(uint256 seed, uint256 epochLength) public { + vm.assume(epochLength > 0); + uint256 numOfValidators = bound(seed, 1, _QUORUM_MAX_SIZE); address[] memory validators = _generateAddresses(numOfValidators); vm.recordLogs(); - Quorum quorum = _factory.newQuorum(validators); + Quorum quorum = _factory.newQuorum(validators, epochLength); - _testNewQuorumAux(validators, quorum); + _testNewQuorumAux(validators, epochLength, quorum); } - function testNewQuorumDeterministic(uint256 seed, bytes32 salt) public { + function testNewQuorumDeterministic( + uint256 seed, + uint256 epochLength, + bytes32 salt + ) public { + vm.assume(epochLength > 0); + uint256 numOfValidators = bound(seed, 1, _QUORUM_MAX_SIZE); address[] memory validators = _generateAddresses(numOfValidators); address precalculatedAddress = _factory.calculateQuorumAddress( validators, + epochLength, salt ); vm.recordLogs(); - Quorum quorum = _factory.newQuorum(validators, salt); + Quorum quorum = _factory.newQuorum(validators, epochLength, salt); - _testNewQuorumAux(validators, quorum); + _testNewQuorumAux(validators, epochLength, quorum); // Precalculated address must match actual address assertEq(precalculatedAddress, address(quorum)); precalculatedAddress = _factory.calculateQuorumAddress( validators, + epochLength, salt ); @@ -57,11 +78,12 @@ contract QuorumFactoryTest is TestBase { // Cannot deploy a quorum with the same salt twice vm.expectRevert(); - _factory.newQuorum(validators, salt); + _factory.newQuorum(validators, epochLength, salt); } function _testNewQuorumAux( address[] memory validators, + uint256 epochLength, Quorum quorum ) internal { Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -86,5 +108,7 @@ contract QuorumFactoryTest is TestBase { for (uint256 i; i < numOfValidators; ++i) { assertEq(validators[i], quorum.validatorById(i + 1)); } + + assertEq(epochLength, quorum.getEpochLength()); } } diff --git a/test/foundry/dapp/Application.t.sol b/test/foundry/dapp/Application.t.sol index ff3bda09..26095d11 100644 --- a/test/foundry/dapp/Application.t.sol +++ b/test/foundry/dapp/Application.t.sol @@ -54,6 +54,7 @@ contract ApplicationTest is TestBase { uint256[] _transferAmounts; mapping(string => LibEmulator.OutputIndex) _outputIndexByName; + uint256 constant _epochLength = 1; bytes32 constant _templateHash = keccak256("templateHash"); uint256 constant _initialSupply = 1000000000000000000000000000000000000; uint256 constant _tokenId = 88888888; @@ -325,7 +326,7 @@ contract ApplicationTest is TestBase { _tokenIds, _initialSupplies ); - _consensus = new Authority(_authorityOwner); + _consensus = new Authority(_authorityOwner, _epochLength); _appContract = new Application(_consensus, _appOwner, _templateHash); _safeERC20Transfer = new SafeERC20Transfer(); } diff --git a/test/foundry/dapp/SelfHostedApplicationFactory.t.sol b/test/foundry/dapp/SelfHostedApplicationFactory.t.sol index 44d0c928..0c4e8e49 100644 --- a/test/foundry/dapp/SelfHostedApplicationFactory.t.sol +++ b/test/foundry/dapp/SelfHostedApplicationFactory.t.sol @@ -4,6 +4,8 @@ /// @title Self-hosted Application Factory Test pragma solidity ^0.8.22; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + import {IAuthorityFactory} from "contracts/consensus/authority/IAuthorityFactory.sol"; import {AuthorityFactory} from "contracts/consensus/authority/AuthorityFactory.sol"; import {Authority} from "contracts/consensus/authority/Authority.sol"; @@ -12,6 +14,7 @@ import {ApplicationFactory} from "contracts/dapp/ApplicationFactory.sol"; import {Application} from "contracts/dapp/Application.sol"; import {ISelfHostedApplicationFactory} from "contracts/dapp/ISelfHostedApplicationFactory.sol"; import {SelfHostedApplicationFactory} from "contracts/dapp/SelfHostedApplicationFactory.sol"; + import {TestBase} from "../util/TestBase.sol"; contract SelfHostedApplicationFactoryTest is TestBase { @@ -42,20 +45,90 @@ contract SelfHostedApplicationFactoryTest is TestBase { ); } + function testRevertsAuthorityOwnerAddressZero( + uint256 epochLength, + address appOwner, + bytes32 templateHash, + bytes32 salt + ) external { + vm.assume(appOwner != address(0)); + vm.assume(epochLength > 0); + + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableInvalidOwner.selector, + address(0) + ) + ); + factory.deployContracts( + address(0), + epochLength, + appOwner, + templateHash, + salt + ); + } + + function testRevertsEpochLengthZero( + address authorityOwner, + address appOwner, + bytes32 templateHash, + bytes32 salt + ) external { + vm.assume(appOwner != address(0)); + vm.assume(authorityOwner != address(0)); + + vm.expectRevert("epoch length must not be zero"); + factory.deployContracts( + authorityOwner, + 0, + appOwner, + templateHash, + salt + ); + } + + function testRevertsApplicationOwnerAddressZero( + address authorityOwner, + uint256 epochLength, + bytes32 templateHash, + bytes32 salt + ) external { + vm.assume(authorityOwner != address(0)); + vm.assume(epochLength > 0); + + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableInvalidOwner.selector, + address(0) + ) + ); + factory.deployContracts( + authorityOwner, + epochLength, + address(0), + templateHash, + salt + ); + } + function testDeployContracts( address authorityOwner, + uint256 epochLength, address appOwner, bytes32 templateHash, bytes32 salt ) external { vm.assume(appOwner != address(0)); vm.assume(authorityOwner != address(0)); + vm.assume(epochLength > 0); address appAddr; address authorityAddr; (appAddr, authorityAddr) = factory.calculateAddresses( authorityOwner, + epochLength, appOwner, templateHash, salt @@ -66,6 +139,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { (application, authority) = factory.deployContracts( authorityOwner, + epochLength, appOwner, templateHash, salt @@ -75,6 +149,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { assertEq(authorityAddr, address(authority)); assertEq(authority.owner(), authorityOwner); + assertEq(authority.getEpochLength(), epochLength); assertEq(address(application.getConsensus()), authorityAddr); assertEq(application.owner(), appOwner);