Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup-tools/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ inputs:
foundry-version:
description: 'Foundry version to install'
required: false
default: 'v1.4.3'
default: 'v1.5.1'
setup-qemu:
description: 'Whether to setup QEMU for riscv support'
required: false
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions cartesi-rollups/contracts/script/Deployment.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ contract DeploymentScript is BaseDeploymentScript {

address inputBox = _loadDeployment(".", "InputBox");
address appFactory = _loadDeployment(".", "ApplicationFactory");
address tournamentFactory = _loadDeployment(".", "MultiLevelTournamentFactory");

// The factory's bound proof system; apps that want a safety gate
// deploy one at app-creation time via `newGatedDaveApp` (see
// prt/docs/safety-gate.md).
address taskSpawner = _loadDeployment(".", "MultiLevelTournamentFactory");

vmSafe.startBroadcast();

_storeDeployment(
type(DaveAppFactory).name,
_create2(type(DaveAppFactory).creationCode, abi.encode(inputBox, appFactory, tournamentFactory))
_create2(type(DaveAppFactory).creationCode, abi.encode(inputBox, appFactory, taskSpawner))
);

vmSafe.stopBroadcast();
Expand Down
105 changes: 85 additions & 20 deletions cartesi-rollups/contracts/src/DaveAppFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicatio
import {IApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactory.sol";
import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol";

import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol";
import {ITaskSpawner} from "prt-contracts/ITaskSpawner.sol";
import {SafetyGateTaskSpawner} from "prt-contracts/safety-gate-task/SafetyGateTaskSpawner.sol";
import {Time} from "prt-contracts/tournament/libs/Time.sol";
import {Machine} from "prt-contracts/types/Machine.sol";

import {DaveConsensus} from "./DaveConsensus.sol";
Expand All @@ -24,14 +26,14 @@ import {IDaveConsensus} from "./IDaveConsensus.sol";
contract DaveAppFactory is IDaveAppFactory {
IInputBox immutable INPUT_BOX;
IApplicationFactory immutable APP_FACTORY;
ITournamentFactory immutable TOURNAMENT_FACTORY;
ITaskSpawner immutable TASK_SPAWNER;

IOutputsMerkleRootValidator constant NO_VALIDATOR = IOutputsMerkleRootValidator(address(0));

constructor(IInputBox inputBox, IApplicationFactory appFactory, ITournamentFactory tournamentFactory) {
constructor(IInputBox inputBox, IApplicationFactory appFactory, ITaskSpawner taskSpawner) {
INPUT_BOX = inputBox;
APP_FACTORY = appFactory;
TOURNAMENT_FACTORY = tournamentFactory;
TASK_SPAWNER = taskSpawner;
}

function newDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt)
Expand All @@ -40,9 +42,8 @@ contract DaveAppFactory is IDaveAppFactory {
returns (IApplication appContract, IDaveConsensus daveConsensus)
{
appContract = _newApplication(templateHash, withdrawalConfig, salt);
daveConsensus = _newDaveConsensus(address(appContract), templateHash, salt);
appContract.migrateToOutputsMerkleRootValidator(daveConsensus);
appContract.renounceOwnership();
daveConsensus = _newDaveConsensus(address(appContract), templateHash, TASK_SPAWNER, salt);
_wireApp(appContract, daveConsensus);
emit DaveAppCreated(appContract, daveConsensus);
}

Expand All @@ -53,14 +54,59 @@ contract DaveAppFactory is IDaveAppFactory {
returns (address appContractAddress, address daveConsensusAddress)
{
appContractAddress = _calculateApplicationAddress(templateHash, withdrawalConfig, salt);
daveConsensusAddress = _calculateDaveConsensusAddress(appContractAddress, templateHash, salt);
daveConsensusAddress = _calculateDaveConsensusAddress(appContractAddress, templateHash, TASK_SPAWNER, salt);
}

function newGatedDaveApp(
bytes32 templateHash,
WithdrawalConfig calldata withdrawalConfig,
address sentryManager,
Time.Duration disagreementWindow,
address[] calldata sentries,
bytes32 salt
)
external
override
returns (IApplication appContract, IDaveConsensus daveConsensus, SafetyGateTaskSpawner gateSpawner)
{
appContract = _newApplication(templateHash, withdrawalConfig, salt);
gateSpawner = new SafetyGateTaskSpawner{salt: salt}(sentryManager, TASK_SPAWNER, disagreementWindow, sentries);
daveConsensus = _newDaveConsensus(address(appContract), templateHash, gateSpawner, salt);
_wireApp(appContract, daveConsensus);
emit GatedDaveAppCreated(appContract, daveConsensus, gateSpawner);
}

function calculateGatedDaveAppAddress(
bytes32 templateHash,
WithdrawalConfig calldata withdrawalConfig,
address sentryManager,
Time.Duration disagreementWindow,
address[] calldata sentries,
bytes32 salt
)
external
view
override
returns (address appContractAddress, address daveConsensusAddress, address gateSpawnerAddress)
{
appContractAddress = _calculateApplicationAddress(templateHash, withdrawalConfig, salt);
gateSpawnerAddress = _calculateGateSpawnerAddress(sentryManager, disagreementWindow, sentries, salt);
daveConsensusAddress =
_calculateDaveConsensusAddress(appContractAddress, templateHash, ITaskSpawner(gateSpawnerAddress), salt);
}

/// @notice Encode the data availability blob for applications that only use the input box as DA.
function _encodeInputBoxDataAvailability() internal view returns (bytes memory) {
return abi.encodeCall(DataAvailability.InputBox, (INPUT_BOX));
}

/// @notice Hand the application over to its consensus: set the outputs
/// Merkle root validator and renounce the factory's temporary ownership.
function _wireApp(IApplication appContract, IDaveConsensus daveConsensus) internal {
appContract.migrateToOutputsMerkleRootValidator(daveConsensus);
appContract.renounceOwnership();
}

/// @notice Instantiate a new application contract owned by the current contract,
/// with no outputs Merkle root validator (the zero address), and with the input box
/// as the only data availability source.
Expand All @@ -76,12 +122,12 @@ contract DaveAppFactory is IDaveAppFactory {
}

/// @notice Instantiate a new `DaveConsensus` contract.
function _newDaveConsensus(address appContract, bytes32 templateHash, bytes32 salt)
function _newDaveConsensus(address appContract, bytes32 templateHash, ITaskSpawner taskSpawner, bytes32 salt)
internal
returns (DaveConsensus)
{
Machine.Hash initialMachineStateHash = Machine.Hash.wrap(templateHash);
return new DaveConsensus{salt: salt}(INPUT_BOX, appContract, TOURNAMENT_FACTORY, initialMachineStateHash);
return new DaveConsensus{salt: salt}(INPUT_BOX, appContract, taskSpawner, initialMachineStateHash);
}

/// @notice Calculates the address of an application contract.
Expand All @@ -97,19 +143,38 @@ contract DaveAppFactory is IDaveAppFactory {
}

/// @notice Calculates the address of a `DaveConsensus` contract.
function _calculateDaveConsensusAddress(address appContract, bytes32 templateHash, bytes32 salt)
function _calculateDaveConsensusAddress(
address appContract,
bytes32 templateHash,
ITaskSpawner taskSpawner,
bytes32 salt
) internal view returns (address) {
return _calculateCreate2Address(
type(DaveConsensus).creationCode, abi.encode(INPUT_BOX, appContract, taskSpawner, templateHash), salt
);
}

/// @notice Calculates the address of a `SafetyGateTaskSpawner` contract.
function _calculateGateSpawnerAddress(
address sentryManager,
Time.Duration disagreementWindow,
address[] calldata sentries,
bytes32 salt
) internal view returns (address) {
return _calculateCreate2Address(
type(SafetyGateTaskSpawner).creationCode,
abi.encode(sentryManager, TASK_SPAWNER, disagreementWindow, sentries),
salt
);
}

/// @notice Address of a contract this factory would CREATE2-deploy from
/// the given creation code and constructor arguments under `salt`.
function _calculateCreate2Address(bytes memory creationCode, bytes memory args, bytes32 salt)
internal
view
returns (address)
{
return Create2.computeAddress(
salt,
keccak256(
abi.encodePacked(
type(DaveConsensus).creationCode,
abi.encode(INPUT_BOX, appContract, TOURNAMENT_FACTORY, templateHash)
)
)
);
return Create2.computeAddress(salt, keccak256(abi.encodePacked(creationCode, args)));
}
}
73 changes: 34 additions & 39 deletions cartesi-rollups/contracts/src/DaveConsensus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,17 @@ import {LibKeccak256} from "cartesi-rollups-contracts-3.0.0/src/library/LibKecca
import {LibMath} from "cartesi-rollups-contracts-3.0.0/src/library/LibMath.sol";

import {IDataProvider} from "prt-contracts/IDataProvider.sol";
import {ITournament} from "prt-contracts/ITournament.sol";
import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol";
import {ITask} from "prt-contracts/ITask.sol";
import {ITaskSpawner} from "prt-contracts/ITaskSpawner.sol";

import {Machine} from "prt-contracts/types/Machine.sol";
import {Tree} from "prt-contracts/types/Tree.sol";

import {EmulatorConstants} from "step/src/EmulatorConstants.sol";
import {Memory} from "step/src/Memory.sol";

import {IDaveConsensus} from "./IDaveConsensus.sol";

/// @notice Consensus contract with Dave tournaments.
/// @notice Consensus contract with Dave tasks.
///
/// @notice This contract validates only one application,
/// which read inputs from the InputBox contract.
Expand Down Expand Up @@ -60,8 +59,8 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
/// @notice The application contract
address immutable _APP_CONTRACT;

/// @notice The contract used to instantiate tournaments
ITournamentFactory immutable _TOURNAMENT_FACTORY;
/// @notice The contract used to instantiate tasks
ITaskSpawner immutable _TASK_SPAWNER;

/// @notice Deployment block number
uint256 immutable _DEPLOYMENT_BLOCK_NUMBER = block.number;
Expand All @@ -75,8 +74,8 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
/// @notice Input index (exclusive) upper bound of the current sealed epoch
uint256 _inputIndexUpperBound;

/// @notice Current sealed epoch tournament
ITournament _tournament;
/// @notice Current sealed epoch task
ITask _task;

/// @notice Settled output trees' merkle root hash
mapping(bytes32 => bool) _outputsMerkleRoots;
Expand All @@ -87,30 +86,30 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
constructor(
IInputBox inputBox,
address appContract,
ITournamentFactory tournamentFactory,
ITaskSpawner taskSpawner,
Machine.Hash initialMachineStateHash
) {
// Initialize immutable variables
_INPUT_BOX = inputBox;
_APP_CONTRACT = appContract;
_TOURNAMENT_FACTORY = tournamentFactory;
emit ConsensusCreation(inputBox, appContract, tournamentFactory);
_TASK_SPAWNER = taskSpawner;
emit ConsensusCreation(inputBox, appContract, taskSpawner);

// Initialize first sealed epoch
uint256 inputIndexUpperBound = inputBox.getNumberOfInputs(appContract);
_inputIndexUpperBound = inputIndexUpperBound;
ITournament tournament = tournamentFactory.instantiate(initialMachineStateHash, this);
_tournament = tournament;
emit EpochSealed(0, 0, inputIndexUpperBound, initialMachineStateHash, bytes32(0), tournament);
ITask task = taskSpawner.spawn(initialMachineStateHash, this);
_task = task;
emit EpochSealed(0, 0, inputIndexUpperBound, initialMachineStateHash, bytes32(0), task);
}

function canSettle()
external
view
override
returns (bool isFinished, uint256 epochNumber, Tree.Node winnerCommitment)
returns (bool isFinished, uint256 epochNumber, Machine.Hash finalState)
{
(isFinished, winnerCommitment,) = _tournament.arbitrationResult();
(isFinished, finalState) = _task.result();
epochNumber = _epochNumber;
}

Expand All @@ -119,14 +118,14 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
override
notForeclosed(_APP_CONTRACT)
{
// Check tournament settlement
// Check task settlement
require(epochNumber == _epochNumber, IncorrectEpochNumber(epochNumber, _epochNumber));

// Check tournament finished
(bool isFinished,, Machine.Hash finalMachineStateHash) = _tournament.arbitrationResult();
// Check task finished
(bool isFinished, Machine.Hash finalMachineStateHash) = _task.result();
require(isFinished, TournamentNotFinishedYet());
ITournament oldTournament = _tournament;
_tournament = ITournament(address(0));
ITask oldTask = _task;
_task = ITask(address(0));

// Check outputs Merkle root
_validateOutputTree(finalMachineStateHash, outputsMerkleRoot, proof);
Expand All @@ -138,36 +137,26 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
_outputsMerkleRoots[outputsMerkleRoot] = true;
_lastFinalizedMachineStateHash = finalMachineStateHash;

// Start new tournament
_tournament = _TOURNAMENT_FACTORY.instantiate(finalMachineStateHash, this);
// Start new task
_task = _TASK_SPAWNER.spawn(finalMachineStateHash, this);

emit EpochSealed(
_epochNumber,
_inputIndexLowerBound,
_inputIndexUpperBound,
finalMachineStateHash,
outputsMerkleRoot,
_tournament
_epochNumber, _inputIndexLowerBound, _inputIndexUpperBound, finalMachineStateHash, outputsMerkleRoot, _task
);

oldTournament.tryRecoveringBond();
_tryCleanup(oldTask);
}

function getCurrentSealedEpoch()
external
view
override
returns (
uint256 epochNumber,
uint256 inputIndexLowerBound,
uint256 inputIndexUpperBound,
ITournament tournament
)
returns (uint256 epochNumber, uint256 inputIndexLowerBound, uint256 inputIndexUpperBound, ITask task)
{
epochNumber = _epochNumber;
inputIndexLowerBound = _inputIndexLowerBound;
inputIndexUpperBound = _inputIndexUpperBound;
tournament = _tournament;
task = _task;
}

function getInputBox() external view override returns (IInputBox) {
Expand All @@ -178,8 +167,8 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
return _APP_CONTRACT;
}

function getTournamentFactory() external view override returns (ITournamentFactory) {
return _TOURNAMENT_FACTORY;
function getTaskSpawner() external view override returns (ITaskSpawner) {
return _TASK_SPAWNER;
}

function provideMerkleRootOfInput(uint256 inputIndexWithinEpoch, bytes calldata input)
Expand Down Expand Up @@ -250,6 +239,12 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
require(machineStateHash == allegedStateHash, InvalidOutputsMerkleRootProof(finalMachineStateHash));
}

/// @dev Best-effort: settlement must never be blocked by a task whose
/// cleanup reverts.
function _tryCleanup(ITask task) internal {
try task.cleanup() returns (bool) {} catch {}
}

modifier onlyValidAppContract(address appContract) {
_ensureAppContractIsValid(appContract);
_;
Expand Down
Loading
Loading