Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9fc3cf8
Add `IWithdrawer` interface
guidanoli Feb 21, 2026
2c2ef39
Add `WithdrawalConfig` structure
guidanoli Feb 22, 2026
78ee2ee
Add `AccountValidityProof` structure
guidanoli Feb 21, 2026
114e58f
Add `WithdrawalConfig` parameter to app deployment entrypoints
guidanoli Feb 22, 2026
a3ce618
Add `WithdrawalConfig` parameter to app deployment event
guidanoli Feb 22, 2026
cde2e4b
Add `WithdrawalConfig` getters to app interface
guidanoli Feb 22, 2026
943f21d
Improve code quality of app deployment tests
guidanoli Feb 22, 2026
44a1184
Downcast canonical machine constants
guidanoli Feb 22, 2026
d8cdbfc
Add `LibWithdrawalConfig` library and tests
guidanoli Feb 22, 2026
c3dbbd1
Validate withdrawal config on app deployment
guidanoli Feb 22, 2026
2d05bf0
Add `foreclose` and `isForeclosed` functions to app interface
guidanoli Feb 22, 2026
1631f10
Add `IApplicationForeclosure` interface
guidanoli Feb 23, 2026
4357ff6
Add `IApplicationChecker` interface
guidanoli Feb 23, 2026
e671be1
Add `ApplicationChecker` abstract contract
guidanoli Feb 23, 2026
974fbc7
Make `InputBox` check for app foreclosure
guidanoli Feb 23, 2026
b23fbff
Add `LibMath` and tests
guidanoli Feb 28, 2026
3f8bbef
Add `LibUint256Array` and tests
guidanoli Feb 28, 2026
ea1ed2e
Add `AddressGenerator` and use in app tests
guidanoli Feb 28, 2026
6a82ae9
Add functions to `LibAddressArray` and tests
guidanoli Feb 28, 2026
78c75cc
Add `ApplicationCheckerTestUtils`
guidanoli Feb 28, 2026
3ff5073
Refactor `InputBox` tests
guidanoli Feb 28, 2026
a744b78
Make `Authority` and `Quorum` check for app foreclosure
guidanoli Mar 1, 2026
6e6d0de
Add `cartesi/machine-solidity-step` as dependency
guidanoli Mar 1, 2026
424f362
Add proof parameter to `submitClaim` function
guidanoli Mar 1, 2026
39fda5e
Improve tests and coverage
guidanoli Mar 3, 2026
50b9626
Add `getLastFinalizedMachineMerkleRoot` function
guidanoli Mar 5, 2026
5892940
Move `guardian` up in `WithdrawalConfig`
guidanoli Mar 9, 2026
a1d4e43
Refactor withdrawer -> withdrawal output builder
guidanoli Mar 9, 2026
7254d21
Add `getNumberOfWithdrawals` view function
guidanoli Mar 9, 2026
f21255d
Add `wereAccountFundsWithdrawn` view function
guidanoli Mar 9, 2026
7fb13ad
Create interface for `SafeERC20Transfer`
guidanoli Mar 9, 2026
bfc76d9
Add `UsdWithdrawalOutputBuilder` contract and test
guidanoli Mar 9, 2026
ffa0d51
Add `LibMath`
guidanoli Mar 9, 2026
1c3ab36
Add `LibKeccak256`
guidanoli Mar 9, 2026
26e6e49
Add `LibBinaryMerkleTree`
guidanoli Mar 9, 2026
90fef40
Remove `LibMerkle32` and `LibMath` (from tests)
guidanoli Mar 9, 2026
c256e06
Simplify LibBinaryMerkleTree test
guidanoli Mar 9, 2026
9e95583
Add `validateAccountMerkleRoot`
guidanoli Mar 10, 2026
287b805
Move errors to new `BinaryMerkleTreeErrors` iface
guidanoli Mar 10, 2026
a410ea4
Add `validateAccount`
guidanoli Mar 10, 2026
a818672
Add `LibBytes`
guidanoli Mar 10, 2026
2b3be10
Add `withdraw`
guidanoli Mar 10, 2026
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: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ optimizer = true
evm_version = "prague"
remappings = [
"@openzeppelin-contracts-5.2.0/=dependencies/@openzeppelin-contracts-5.2.0/",
"cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-machine-solidity-step-0.13.0/",
"forge-std-1.9.6/=dependencies/forge-std-1.9.6/",
]
fs_permissions = [{ access = "read-write", path = "deployments" }]
Expand All @@ -29,5 +30,6 @@ remappings_location = "config"
[dependencies]
"@openzeppelin-contracts" = "5.2.0"
forge-std = "1.9.6"
cartesi-machine-solidity-step = { version = "0.13.0", git = "https://github.com/cartesi/machine-solidity-step.git", tag = "v0.13.0" }

# See more config options https://book.getfoundry.sh/reference/config/
6 changes: 6 additions & 0 deletions soldeer.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/5_2_0_
checksum = "6dbd0440446b2ed16ca25e9f1af08fc0c5c1e73e71fee86ae8a00daa774e3817"
integrity = "4cb7f3777f67fdf4b7d0e2f94d2f93f198b2e5dce718b7062ac7c2c83e1183bd"

[[dependencies]]
name = "cartesi-machine-solidity-step"
version = "0.13.0"
git = "https://github.com/cartesi/machine-solidity-step.git"
rev = "97fbc294dc5f816f0349fc1291456c9a06091669"

[[dependencies]]
name = "forge-std"
version = "1.9.6"
Expand Down
14 changes: 14 additions & 0 deletions src/common/AccountValidityProof.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

/// @notice Proof of inclusion of an account in the accounts drive.
/// @param accountIndex Index of account in the accounts drive
/// @param accountRootSiblings Siblings of the account root in the accounts drive
/// @dev From the index and siblings, one can calculate the accounts drive root.
/// @dev The siblings array should have size equal to the log2 of the maximum number of accounts.
struct AccountValidityProof {
uint64 accountIndex;
bytes32[] accountRootSiblings;
}
37 changes: 37 additions & 0 deletions src/common/BinaryMerkleTreeErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.27;

interface BinaryMerkleTreeErrors {
/// @notice The provided node index is invalid.
/// @param nodeIndex The node index in its level
/// @param height The binary Merkle tree height
/// @dev The node index should be less than `2^{height}`.
error InvalidNodeIndex(uint256 nodeIndex, uint256 height);

/// @notice A drive size too large was provided.
/// @param log2DriveSize The log2 size of the drive
/// @param maxLog2DriveSize The maximum log2 size of a drive
error DriveTooLarge(uint256 log2DriveSize, uint256 maxLog2DriveSize);

/// @notice A data block size too large was provided.
/// @param log2DataBlockSize The log2 size of the data block
/// @param maxLog2DataBlockSize The maximum log2 size of a data block
error DataBlockTooLarge(uint256 log2DataBlockSize, uint256 maxLog2DataBlockSize);

/// @notice A drive size smaller than the data block size was provided.
/// @param log2DriveSize The log2 size of the drive
/// @param log2DataBlockSize The log2 size of the data block
error DriveSmallerThanDataBlock(uint256 log2DriveSize, uint256 log2DataBlockSize);

/// @notice A drive too small to fit the data was provided.
/// @param driveSize The size of the drive
/// @param dataSize The size of the data
error DriveSmallerThanData(uint256 driveSize, uint256 dataSize);

/// @notice An unexpected stack error occurred.
/// @param stackDepth The final stack depth
/// @dev Expected final stack depth to be 1.
error UnexpectedFinalStackDepth(uint256 stackDepth);
}
10 changes: 8 additions & 2 deletions src/common/CanonicalMachine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ pragma solidity ^0.8.8;
/// of the RISC-V machine that runs Linux, also known as the "Cartesi Machine".
library CanonicalMachine {
/// @notice Maximum input size (64 kilobytes).
uint256 constant INPUT_MAX_SIZE = 1 << 16;
uint64 constant INPUT_MAX_SIZE = 1 << 16;

/// @notice Log2 of memory size.
uint8 constant LOG2_MEMORY_SIZE = 64;

/// @notice Log2 of maximum number of outputs.
uint256 constant LOG2_MAX_OUTPUTS = 63;
uint8 constant LOG2_MAX_OUTPUTS = 63;

/// @notice Log2 of data block size.
uint8 constant LOG2_DATA_BLOCK_SIZE = 5;
}
20 changes: 20 additions & 0 deletions src/common/WithdrawalConfig.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {IWithdrawalOutputBuilder} from "../withdrawal/IWithdrawalOutputBuilder.sol";

// @notice Withdrawal configuration parameters.
// @param guardian The address of the account with guardian priviledges
// @param log2LeavesPerAccount The base-2 log of leaves per account
// @param log2MaxNumOfAccounts The base-2 log of max. num. of accounts
// @param accountsDriveStartIndex The offset of the accounts drive
// @param withdrawalOutputBuilder The address of the withdrawal output builder
struct WithdrawalConfig {
address guardian;
uint8 log2LeavesPerAccount;
uint8 log2MaxNumOfAccounts;
uint64 accountsDriveStartIndex;
IWithdrawalOutputBuilder withdrawalOutputBuilder;
}
100 changes: 89 additions & 11 deletions src/consensus/AbstractConsensus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,42 @@ pragma solidity ^0.8.26;
import {ERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/ERC165.sol";
import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165.sol";

import {
EmulatorConstants
} from "cartesi-machine-solidity-step-0.13.0/src/EmulatorConstants.sol";
import {Memory} from "cartesi-machine-solidity-step-0.13.0/src/Memory.sol";

import {ApplicationChecker} from "../dapp/ApplicationChecker.sol";
import {LibBinaryMerkleTree} from "../library/LibBinaryMerkleTree.sol";
import {LibKeccak256} from "../library/LibKeccak256.sol";
import {IConsensus} from "./IConsensus.sol";
import {IOutputsMerkleRootValidator} from "./IOutputsMerkleRootValidator.sol";

/// @notice Abstract implementation of IConsensus
abstract contract AbstractConsensus is IConsensus, ERC165 {
abstract contract AbstractConsensus is IConsensus, ERC165, ApplicationChecker {
using LibBinaryMerkleTree for bytes32[];

/// @notice The epoch length
uint256 immutable EPOCH_LENGTH;

/// @notice Indexes accepted claims by application contract address.
mapping(address => mapping(bytes32 => bool)) private _validOutputsMerkleRoots;

/// @notice Indexes number of the first unprocessed block
/// by application contract address.
mapping(address => uint256) private _firstUnprocessedBlockNumbers;

/// @notice Indexes machine merkle root of the most recently accepted claim
/// by application contract address.
mapping(address => bytes32) private _lastFinalizedMachineMerkleRoots;

/// @notice Number of claims accepted by the consensus.
/// @dev Must be monotonically non-decreasing in time
uint256 _numOfAcceptedClaims;
uint256 private _numOfAcceptedClaims;

/// @notice Number of claims submitted to the consensus.
/// @dev Must be monotonically non-decreasing in time
uint256 _numOfSubmittedClaims;
uint256 private _numOfSubmittedClaims;

/// @param epochLength The epoch length
/// @dev Reverts if the epoch length is zero.
Expand All @@ -40,6 +60,15 @@ abstract contract AbstractConsensus is IConsensus, ERC165 {
return _validOutputsMerkleRoots[appContract][outputsMerkleRoot];
}

function getLastFinalizedMachineMerkleRoot(address appContract)
public
view
override
returns (bytes32)
{
return _lastFinalizedMachineMerkleRoots[appContract];
}

/// @inheritdoc IConsensus
function getEpochLength() public view override returns (uint256) {
return EPOCH_LENGTH;
Expand Down Expand Up @@ -90,33 +119,82 @@ abstract contract AbstractConsensus is IConsensus, ERC165 {
/// @param submitter The submitter address
/// @param appContract The application contract address
/// @param lastProcessedBlockNumber The number of the last processed block
/// @param outputsMerkleRoot The output Merkle root hash
/// @param outputsMerkleRoot The output Merkle root
/// @param machineMerkleRoot The machine Merkle root
/// @dev Assumes outputs Merkle root is proven to be at the start of the machine TX buffer.
/// @dev Checks whether the app is foreclosed.
/// @dev Emits a `ClaimSubmitted` event.
function _submitClaim(
address submitter,
address appContract,
uint256 lastProcessedBlockNumber,
bytes32 outputsMerkleRoot
) internal {
bytes32 outputsMerkleRoot,
bytes32 machineMerkleRoot
) internal notForeclosed(appContract) {
emit ClaimSubmitted(
submitter, appContract, lastProcessedBlockNumber, outputsMerkleRoot
submitter,
appContract,
lastProcessedBlockNumber,
outputsMerkleRoot,
machineMerkleRoot
);
++_numOfSubmittedClaims;
}

/// @notice Accept a claim.
/// @param appContract The application contract address
/// @param lastProcessedBlockNumber The number of the last processed block
/// @param outputsMerkleRoot The output Merkle root hash
/// @param outputsMerkleRoot The output Merkle root
/// @param machineMerkleRoot The machine Merkle root
/// @dev Assumes outputs Merkle root is proven to be at the start of the machine TX buffer.
/// @dev Checks whether the app is foreclosed.
/// @dev Marks the outputsMerkleRoot as valid.
/// @dev Emits a `ClaimAccepted` event.
function _acceptClaim(
address appContract,
uint256 lastProcessedBlockNumber,
bytes32 outputsMerkleRoot
) internal {
bytes32 outputsMerkleRoot,
bytes32 machineMerkleRoot
) internal notForeclosed(appContract) {
_validOutputsMerkleRoots[appContract][outputsMerkleRoot] = true;
emit ClaimAccepted(appContract, lastProcessedBlockNumber, outputsMerkleRoot);
if (lastProcessedBlockNumber >= _firstUnprocessedBlockNumbers[appContract]) {
_lastFinalizedMachineMerkleRoots[appContract] = machineMerkleRoot;
_firstUnprocessedBlockNumbers[appContract] = lastProcessedBlockNumber + 1;
}
emit ClaimAccepted(
appContract, lastProcessedBlockNumber, outputsMerkleRoot, machineMerkleRoot
);
++_numOfAcceptedClaims;
}

/// @notice Compute the machine Merkle root given an outputs Merkle root and a proof.
/// @param outputsMerkleRoot The outputs Merkle root
/// @param proof The bottom-up Merkle proof of the outputs Merkle root at the start of the machine TX buffer
/// @return machineMerkleRoot The machine Merkle root
function _computeMachineMerkleRoot(
bytes32 outputsMerkleRoot,
bytes32[] calldata proof
) internal pure returns (bytes32 machineMerkleRoot) {
_checkProofSize(proof.length, Memory.LOG2_MAX_SIZE);
machineMerkleRoot = proof.merkleRootAfterReplacement(
EmulatorConstants.PMA_CMIO_TX_BUFFER_START
>> EmulatorConstants.TREE_LOG2_WORD_SIZE,
keccak256(abi.encode(outputsMerkleRoot)),
LibKeccak256.hashPair
);
}

/// @notice Check the size of a supplied proof against the expected proof size.
/// @param suppliedProofSize Supplied proof size
/// @param expectedProofSize Expected proof size
/// @dev Raises an `InvalidOutputsMerkleRootProofSize` error if sizes differ.
function _checkProofSize(uint256 suppliedProofSize, uint256 expectedProofSize)
internal
pure
{
require(
suppliedProofSize == expectedProofSize,
InvalidOutputsMerkleRootProofSize(suppliedProofSize, expectedProofSize)
);
}
}
22 changes: 18 additions & 4 deletions src/consensus/IConsensus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

pragma solidity ^0.8.8;

import {IApplicationChecker} from "../dapp/IApplicationChecker.sol";
import {IOutputsMerkleRootValidator} from "./IOutputsMerkleRootValidator.sol";

/// @notice Each application has its own stream of inputs.
Expand All @@ -21,29 +22,33 @@ import {IOutputsMerkleRootValidator} from "./IOutputsMerkleRootValidator.sol";
/// - submitted by the majority of a quorum or;
/// - submitted and not proven wrong after some period of time or;
/// - submitted and proven correct through an on-chain tournament.
interface IConsensus is IOutputsMerkleRootValidator {
interface IConsensus is IOutputsMerkleRootValidator, IApplicationChecker {
/// @notice MUST trigger when a claim is submitted.
/// @param submitter The submitter address
/// @param appContract The application contract address
/// @param lastProcessedBlockNumber The number of the last processed block
/// @param outputsMerkleRoot The outputs Merkle root
/// @param machineStateRoot The machine state root
event ClaimSubmitted(
address indexed submitter,
address indexed appContract,
uint256 lastProcessedBlockNumber,
bytes32 outputsMerkleRoot
bytes32 outputsMerkleRoot,
bytes32 machineStateRoot
);

/// @notice MUST trigger when a claim is accepted.
/// @param appContract The application contract address
/// @param lastProcessedBlockNumber The number of the last processed block
/// @param outputsMerkleRoot The outputs Merkle root
/// @param machineStateRoot The machine state root
/// @dev For each application and lastProcessedBlockNumber,
/// there can be at most one accepted claim.
event ClaimAccepted(
address indexed appContract,
uint256 lastProcessedBlockNumber,
bytes32 outputsMerkleRoot
bytes32 outputsMerkleRoot,
bytes32 machineStateRoot
);

/// @notice The claim contains the number of a block that is not
Expand All @@ -63,16 +68,25 @@ interface IConsensus is IOutputsMerkleRootValidator {
/// @param lastProcessedBlockNumber The number of the last processed block
error NotFirstClaim(address appContract, uint256 lastProcessedBlockNumber);

/// @notice Supplied output tree proof size is incorrect
/// @param suppliedProofSize Supplied proof size
/// @param expectedProofSize Expected proof size
error InvalidOutputsMerkleRootProofSize(
uint256 suppliedProofSize, uint256 expectedProofSize
);

/// @notice Submit a claim to the consensus.
/// @param appContract The application contract address
/// @param lastProcessedBlockNumber The number of the last processed block
/// @param outputsMerkleRoot The outputs Merkle root
/// @param proof The bottom-up Merkle proof of the outputs Merkle root at the start of the machine TX buffer
/// @dev MUST fire a `ClaimSubmitted` event.
/// @dev MAY fire a `ClaimAccepted` event, if the acceptance criteria is met.
function submitClaim(
address appContract,
uint256 lastProcessedBlockNumber,
bytes32 outputsMerkleRoot
bytes32 outputsMerkleRoot,
bytes32[] calldata proof
) external;

/// @notice Get the epoch length, in number of base layer blocks.
Expand Down
10 changes: 10 additions & 0 deletions src/consensus/IOutputsMerkleRootValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,14 @@ interface IOutputsMerkleRootValidator is IERC165 {
external
view
returns (bool);

/// @notice Get the last finalized machine Merkle root.
/// @param appContract The application contract address
/// @dev Returns zero if no machine merkle root has been finalized yet
/// for that particular application. This should not be a problem given
/// that the pre-image Keccak-256 hash of zero is unknown.
function getLastFinalizedMachineMerkleRoot(address appContract)
external
view
returns (bytes32);
}
17 changes: 14 additions & 3 deletions src/consensus/authority/Authority.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ contract Authority is IAuthority, AbstractConsensus, Ownable {
function submitClaim(
address appContract,
uint256 lastProcessedBlockNumber,
bytes32 outputsMerkleRoot
bytes32 outputsMerkleRoot,
bytes32[] calldata proof
) external override onlyOwner {
_validateLastProcessedBlockNumber(lastProcessedBlockNumber);

Expand All @@ -46,9 +47,19 @@ contract Authority is IAuthority, AbstractConsensus, Ownable {
!bitmap.get(epochNumber), NotFirstClaim(appContract, lastProcessedBlockNumber)
);

_submitClaim(msg.sender, appContract, lastProcessedBlockNumber, outputsMerkleRoot);
bytes32 machineMerkleRoot = _computeMachineMerkleRoot(outputsMerkleRoot, proof);

_acceptClaim(appContract, lastProcessedBlockNumber, outputsMerkleRoot);
_submitClaim(
msg.sender,
appContract,
lastProcessedBlockNumber,
outputsMerkleRoot,
machineMerkleRoot
);

_acceptClaim(
appContract, lastProcessedBlockNumber, outputsMerkleRoot, machineMerkleRoot
);

bitmap.set(epochNumber);
}
Expand Down
Loading
Loading