Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ runs:
- name: Set Foundry version
id: set-foundry-version
shell: bash
run: echo "foundry-version=v1.4.3" >> $GITHUB_OUTPUT
run: echo "foundry-version=v1.5.1" >> $GITHUB_OUTPUT

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # v1.3.1
uses: foundry-rs/foundry-toolchain@50d5a8956f2e319df19e6b57539d7e2acb9f8c1e # v1.5.0
with:
version: ${{ steps.set-foundry-version.outputs.foundry-version }}
36 changes: 30 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.15.0-test1] - 2026-07-23
## [0.15.0] - 2026-08-04
### Added
- Added `revertRootHash` argument to `sendCmioResponse`
- Added `imcyclemax` deadline set by `sendCmioResponse` on advance-state responses
- Added revert on rejected input to `UArchReset.reset()`
- Added `ROLLUP_LOG2_MAX_*` limits and HTIF device/command/reason constants
- Added `revertState`, `readMcycle`, `writeImcyclemax`, `readHtifTohost` and `isYieldedManualWith` to `EmulatorCompat`
- Added tests for uarch reset, no-op response and step fixed points

### Changed
- Updated machine-emulator version to v0.21.0-test7
- Changed `UArchStep.step` to report overflow and halt on the step that reaches them
- Changed `UARCH_CYCLE_MAX` from `0x100000` to `0xfffff`
- Changed `UArchReset.reset` to also read `iflags.Y` and `htif.tohost`
- Changed `sendCmioResponse` failures into no-ops
- Changed `MetaStep` uarch reset period from `2^10` to `2^20` meta-steps
- Renamed `UArchStepStatus.CycleOverflow` to `UArchCycleOverflow`
- Renamed `getRevertRootHash`/`setRevertRootHash` to `readRevertRootHash`/`writeRevertRootHash`
- Renamed `readHaltFlag`/`writeHaltFlag` to `readHalt`/`writeHalt`
- Renamed `CMIO_YIELD_*` constants to `HTIF_YIELD_*`, and `UARCH_HALT_FLAG_ADDRESS` to `UARCH_HALT_ADDRESS`
- Updated `UARCH_PRISTINE_STATE_HASH` and shadow register addresses
- Updated test artifact parsing for `0x`-prefixed hexadecimal values
- Changed microarchitecture cycle overflow into a state-preserving fixed point derived from `uarch.cycle`
- Changed microarchitecture cycle overflow to take precedence over halt
- Updated machine-emulator version to v0.21.0
- Bumped Foundry to 1.5.1

### Removed
- Removed `AdvanceStatus` library
- Removed `mark_dirty_page` ECALL and `markDirtyPageECALL`
- Removed `LOG2_CYCLES_TO_RESET`

### Fixed
- Fixed various Solidity and forge lint warnings

## [0.14.0] - 2026-04-13
### Added
Expand Down Expand Up @@ -127,8 +151,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [0.2.0]
- [0.1.0]

[Unreleased]: https://github.com/cartesi/machine-solidity-step/compare/v0.15.0-test1...HEAD
[0.15.0-test1]: https://github.com/cartesi/machine-solidity-step/releases/tag/v0.15.0-test1
[Unreleased]: https://github.com/cartesi/machine-solidity-step/compare/v0.15.0...HEAD
[0.15.0]: https://github.com/cartesi/machine-solidity-step/releases/tag/v0.15.0
[0.14.0]: https://github.com/cartesi/machine-solidity-step/releases/tag/v0.14.0
[0.13.0]: https://github.com/cartesi/machine-solidity-step/releases/tag/v0.13.0
[0.12.1]: https://github.com/cartesi/machine-solidity-step/releases/tag/v0.12.1
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ DOWNLOADDIR := downloads
SRC_DIR := src

EMULATOR_VERSION ?= v0.21.0
EMULATOR_TAG ?= -test7
EMULATOR_TAG ?=

SOLIDITY_VERSION ?= 0.8.30

Expand Down
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ For Cartesi's design to work, this implementation must have the exact transition

Since the cost of storing a full Cartesi Machine state within the blockchain is prohibitive, all machine states are represented in the blockchain as cryptographic hashes. The contents of those states and memory represented by those hashes are only known off-chain.

Cartesi uses Merkle tree operations and properties to ensure that the blockchain has the ability to correctly verify a state transition without having full state-access. However, the RISC-V Solidity emulator abstracts these operations away and acts as if it knows the full contents of a machine state - it uses the Memory Manager interface to fetch or write any necessary words to memory.
Cartesi uses Merkle tree operations and properties to ensure that the blockchain has the ability to correctly verify a state transition without having full state-access. However, the RISC-V Solidity emulator abstracts these operations away and acts as if it knows the full contents of a machine state - it uses the `AccessLogs` interface to fetch or write any necessary words to memory.

## AccessLogs

Expand All @@ -18,17 +18,25 @@ It also makes sure that all accesses performed by the `step` function match the

## Step function

`MetaStep.step` is the top-level state transition function, it is meant to take the machine from state s[i] to state[i + 1], using the `AccessLogs` as an assistant. It receives a `counter` and an `AccessLogs.Context` - the latter populated with the access log generated by the emulator off-chain - and returns a `UArchStepStatus` signaling the result of its execution. Internally it runs one micro-step via `UArchStep.step` and, every `1 << LOG2_CYCLES_TO_RESET` cycles, also runs `UArchReset.reset` to reset the micro-architecture state.
`MetaStep.step` is the top-level state transition function, it is meant to take the machine from state s[i] to state[i + 1], using the `AccessLogs` as an assistant. It receives a `counter` and an `AccessLogs.Context` - the latter populated with the access log generated by the emulator off-chain - and returns a `UArchStepStatus` signaling the result of its execution. Internally it runs one micro-step via `UArchStep.step` and, every `1 << ROLLUP_LOG2_MAX_UARCH_CYCLES_PER_MCYCLE` cycles, also runs `UArchReset.reset` to reset the micro-architecture state.

During a `MetaStep.step` execution, every necessary read or write (be it to memory, registers etc) is processed and verified by the `AccessLogs`.

## Execute Instruction

The `UArchStep` library implements a single micro-architecture instruction step (`UArchStep.step`), and is invoked by `MetaStep.step`. It consists of the machine instruction logic, such as decoding, executing, opcode matching and etc. The Solidity implementation is converted from the Cpp implementation directly through a translator script. This is to assure that the implementations in two languages are identical. Yet the low level differences in two languages are wrapped in the Compatibility Layer.
The `UArchStep` library implements a single micro-architecture instruction step (`UArchStep.step`), and is invoked by `MetaStep.step`. It consists of the machine instruction logic, such as decoding, executing, opcode matching and etc. The Solidity implementation is converted from the C++ implementation directly through a translator script. This is to assure that the implementations in two languages are identical. Yet the low level differences in two languages are wrapped in the Compatibility Layer.

## Reset Microarchitecture

The `UArchReset` library implements `UArchReset.reset`, which restores the micro-architecture to its pristine state, and is invoked by `MetaStep.step` at the end of every micro-architecture span. When the machine has rejected an input, it instead reverts the machine to the state recorded in the revert root hash. Like `UArchStep`, it is converted from the C++ implementation through a translator script.

## Send CMIO Response

The `SendCmioResponse` library implements `SendCmioResponse.sendCmioResponse`, which delivers a response to a machine waiting on a manual yield, and takes the revert root hash to restore in case the response is eventually rejected. It cannot fail, when the machine is not in a state that accepts the response, the operation is a no-op, so the resulting state transition can always be proven. It is also converted from the C++ implementation through a translator script.

## Compatibility Layer

The `EmulatorCompat` contract is taking care of all the differences in the two implementations. Ranging from programming languages (Cpp versus Solidity) to architectural differences (RISC-V versus EVM).
The `EmulatorCompat` library is taking care of all the differences in the two implementations. Ranging from programming languages (C++ versus Solidity) to architectural differences (RISC-V versus EVM).

## Getting Started

Expand Down Expand Up @@ -66,7 +74,7 @@ Coverage targets:

### Requirements

- Foundry 1.4.3
- Foundry >= 1.5.1
- GNU Make >= 3.81
- GNU sed >= 4.9
- GPP >= 2.27
Expand Down
5 changes: 3 additions & 2 deletions helper_scripts/generate_SendCmioResponse.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ pattern="namespace cartesi \{(.*)\}"
cpp_src=`echo "${BASH_REMATCH[1]}" \
| $SED "/Explicit instantiatio/d" \
| $SED "/template/d" \
| $SED "/uint32 length);/d" \
| $SED "/bytes32 revertRootHash);/d" \
| $SED "/uint32 length, bytes32 revertRootHash);/d" \
| $SED "s/machine_merkle_tree::get_log2_word_size()/TREE_LOG2_WORD_SIZE/g" \
| $SED -E "s/($COMPAT_FNS)/EmulatorCompat.\1/g" \
| $SED "s/writeMemoryWithPadding(a, AR_CMIO_RX_BUFFER_START, data, dataLength, writeLengthLog2Size);/a.writeRegion(Memory.regionFromPhysicalAddress(AR_CMIO_RX_BUFFER_START.toPhysicalAddress(),Memory.alignedSizeFromLog2(uint8(writeLengthLog2Size - HASH_TREE_LOG2_WORD_SIZE))),dataHash);"/g \
| $SED -E "s/($CONSTANTS)([^a-zA-Z])/EmulatorConstants.\1\2/g" \
| $SED "s/void send_cmio_response(STATE_ACCESS a, bytes32 revertRootHash, uint16 reason, bytes data, uint32 dataLength) {/function sendCmioResponse(AccessLogs.Context memory a, bytes32 revertRootHash, uint16 reason, bytes32 dataHash, uint32 dataLength) internal pure {/" \
| $SED "s/void send_cmio_response(STATE_ACCESS a, uint16 reason, bytes data, uint32 dataLength, bytes32 revertRootHash) {/function sendCmioResponse(AccessLogs.Context memory a, uint16 reason, bytes32 dataHash, uint32 dataLength, bytes32 revertRootHash) internal pure {/" \
| $SED "s/const uint64/uint64/g" \
| $SED "s/const uint32/uint32/g" \
| $SED "/^$/N;/^\n$/D"
Expand Down
2 changes: 2 additions & 0 deletions helper_scripts/generate_UArchStep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ cpp_src=`echo "${BASH_REMATCH[1]}" \
| $SED -E "s/UArchStepStatus uarch_step/static inline UArchStepStatus step/g" \
| $SED -E "s/static inline (\w+) ($INTERNAL_FN)\(([^\n]*)\) \{/function \2\(\3\) internal pure returns \(\1\)\{/g" \
| $SED -E "s/static inline (\w+) (\w+)\(([^\n]*)\) \{/function \2\(\3\) private pure returns \(\1\)\{/g" \
| $SED -E "s/function (executeFENCE|executeECALL)\(AccessLogs.Context memory a, uint32 insn,/function \1(AccessLogs.Context memory a, uint32,/g" \
| $SED -E "s/function executeEBREAK\(AccessLogs.Context memory a, uint32 insn, uint64 pc\)/function executeEBREAK(AccessLogs.Context memory a, uint32, uint64)/g" \
| $SED -E "s/($COMPAT_FNS)/EmulatorCompat.\1/g" \
| $SED -E "s/([^a-zA-Z])($CONSTANTS)([^a-zA-Z])/\1EmulatorConstants.\2\3/g" \
| $SED "s/ returns (void)//g"`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cartesi/machine-solidity-step",
"version": "0.15.0-test1",
"version": "0.15.0",
"description": "Machine Solidity UArch Interpret",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions shasum-download
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
12c934f783c6e5e706befe4986fa66f2329c97e2486dab4a5ff817bf412e1a5e downloads/machine-emulator-tests-data.deb
b0860e8e87da7b7226ece35f3baa5ccb6e543654919af4a390df72a59ec3c866 downloads/uarch-riscv-tests-json-logs.tar.gz
71348a14e065513cf615a3894c7a928496b2a9040fb776b4da0a0a10c51eee8c downloads/machine-emulator-tests-data.deb
dcd588e1205a3a260605335a9ea93adb9fe743f679b8e72251cc9866e0977eee downloads/uarch-riscv-tests-json-logs.tar.gz
2 changes: 1 addition & 1 deletion src/EmulatorConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ library EmulatorConstants {
// START OF AUTO-GENERATED CODE

bytes32 constant UARCH_PRISTINE_STATE_HASH =
0xfecd1447b18725c91ba909a13b3d059d3628a625f668ce991e6ab7c901a65d2c;
0x1187b59e3fe94897c23582998adb33d3df9e8c962f6d1696dfd91adcfabb3a23;
uint64 constant UARCH_CYCLE_ADDRESS = 0x400008;
uint64 constant UARCH_CYCLE_MAX = 0xfffff;
uint64 constant ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE = 48;
Expand Down
27 changes: 13 additions & 14 deletions src/SendCmioResponse.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ library SendCmioResponse {

function sendCmioResponse(
AccessLogs.Context memory a,
bytes32 revertRootHash,
uint16 reason,
bytes32 dataHash,
uint32 dataLength
uint32 dataLength,
bytes32 revertRootHash
) internal pure {
// This function cannot fail. When a failure is detected, the operation is a no-op instead,
// so the honest party can always log and prove the resulting state transition.
Expand Down Expand Up @@ -80,21 +80,20 @@ library SendCmioResponse {
}
if (reason == EmulatorConstants.HTIF_YIELD_REASON_ADVANCE_STATE) {
uint64 mcycle = EmulatorCompat.readMcycle(a);
uint64 maxMcycles =
EmulatorCompat.uint64ShiftLeft(
1,
uint32(
EmulatorConstants.ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE
)
) - 1;
uint64 maxMcycles = EmulatorCompat.uint64ShiftLeft(
1,
uint32(
EmulatorConstants.ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE
)
) - 1;
uint64 maxUint64 = ~uint64(0);
uint64 imcyclemax = mcycle > maxUint64 - maxMcycles
? maxUint64
: mcycle + maxMcycles;
EmulatorCompat.writeImcyclemax(a, imcyclemax);
// Record the machine root hash to revert to in case the response is eventually rejected
EmulatorCompat.writeRevertRootHash(a, revertRootHash);
}
// Record the machine root hash to revert to in case the response is eventually rejected
EmulatorCompat.writeRevertRootHash(a, revertRootHash);
if (dataLength > 0) {
a.writeRegion(
Memory.regionFromPhysicalAddress(
Expand All @@ -113,9 +112,9 @@ library SendCmioResponse {
// Write data length and reason to fromhost
uint64 mask16 = EmulatorCompat.uint64ShiftLeft(1, 16) - 1;
uint64 mask32 = EmulatorCompat.uint64ShiftLeft(1, 32) - 1;
uint64 yieldData =
EmulatorCompat.uint64ShiftLeft((uint64(reason) & mask16), 32)
| (uint64(dataLength) & mask32);
uint64 yieldData = EmulatorCompat.uint64ShiftLeft(
(uint64(reason) & mask16), 32
) | (uint64(dataLength) & mask32);
EmulatorCompat.writeHtifFromhost(a, yieldData);
// Reset iflags.Y
EmulatorCompat.writeIflagsY(a, 0);
Expand Down
6 changes: 3 additions & 3 deletions src/UArchStep.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1011,7 +1011,7 @@ library UArchStep {
return advancePc(a, pc);
}

function executeFENCE(AccessLogs.Context memory a, uint32 insn, uint64 pc)
function executeFENCE(AccessLogs.Context memory a, uint32, uint64 pc)
private
pure
{
Expand Down Expand Up @@ -1061,7 +1061,7 @@ library UArchStep {
return advancePc(a, pc);
}

function executeECALL(AccessLogs.Context memory a, uint32 insn, uint64 pc)
function executeECALL(AccessLogs.Context memory a, uint32, uint64 pc)
private
pure
{
Expand Down Expand Up @@ -1093,7 +1093,7 @@ library UArchStep {
EmulatorCompat.throwRuntimeError(a, "unsupported ecall function");
}

function executeEBREAK(AccessLogs.Context memory a, uint32 insn, uint64 pc)
function executeEBREAK(AccessLogs.Context memory a, uint32, uint64)
private
pure
{
Expand Down
20 changes: 11 additions & 9 deletions templates/UArchReplay.t.sol.template
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
//
pragma solidity ^0.8.30;

import "forge-std/console.sol";
import "forge-std/Test.sol";
import {console} from "forge-std/console.sol";

import "src/Buffer.sol";
import "src/UArchStep.sol";
import "./AccessLogJsonParse.sol";
import "./BufferAux.sol";
import {AccessLogs} from "src/AccessLogs.sol";
import {Buffer} from "src/Buffer.sol";
import {UArchStep} from "src/UArchStep.sol";
import {AccessLogJsonParse} from "./AccessLogJsonParse.sol";
import {BufferAux} from "./BufferAux.sol";

contract UArchReplay_@X@_Test is AccessLogJsonParse {
using Buffer for Buffer.Context;
Expand All @@ -33,7 +33,7 @@ contract UArchReplay_@X@_Test is AccessLogJsonParse {
string constant JSON_PATH = "./test/uarch-log/";
string constant CATALOG_PATH = "catalog.json";

uint256 constant siblingsLength = 59;
uint256 constant SIBLINGS_LENGTH = 59;

struct Entry {
string binaryFilename;
Expand All @@ -43,7 +43,7 @@ contract UArchReplay_@X@_Test is AccessLogJsonParse {
uint256 steps;
}

string constant ENTRY_TYPE_DESCRIPTION = "Entry(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)";
string constant ENTRY_TYPE_DESCRIPTION = "CatalogEntryJson(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)";

function testReplay_@X@() public {
Entry[] memory catalog =
Expand All @@ -53,7 +53,7 @@ contract UArchReplay_@X@_Test is AccessLogJsonParse {
// also raise memory_limit in foundry.toml per https://github.com/foundry-rs/foundry/issues/3971
vm.pauseGasMetering();
// create a large buffer and reuse it
bytes memory buffer = new bytes(100 * (siblingsLength + 1) * 32);
bytes memory buffer = new bytes(100 * (SIBLINGS_LENGTH + 1) * 32);

for (uint256 i = 0; i < catalog.length; i++) {
if (
Expand Down Expand Up @@ -93,6 +93,7 @@ contract UArchReplay_@X@_Test is AccessLogJsonParse {
view
returns (Entry[] memory)
{
// forge-lint: disable-next-line(unsafe-cheatcode)
string memory json = vm.readFile(path);
bytes memory raw = vm.parseJsonTypeArray(json, ".", ENTRY_TYPE_DESCRIPTION);
Entry[] memory catalog = abi.decode(raw, (Entry[]));
Expand All @@ -105,6 +106,7 @@ contract UArchReplay_@X@_Test is AccessLogJsonParse {
view
returns (string memory)
{
// forge-lint: disable-next-line(unsafe-cheatcode)
return vm.readFile(path);
}

Expand Down
Loading
Loading