diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index a23dbe4..7179563 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -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 }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9daa172..6bcbd54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/Makefile b/Makefile index 324ff07..788d1d7 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ DOWNLOADDIR := downloads SRC_DIR := src EMULATOR_VERSION ?= v0.21.0 -EMULATOR_TAG ?= -test7 +EMULATOR_TAG ?= SOLIDITY_VERSION ?= 0.8.30 diff --git a/README.md b/README.md index 27becbb..2e9be71 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 diff --git a/helper_scripts/generate_SendCmioResponse.sh b/helper_scripts/generate_SendCmioResponse.sh index 4213d67..88ded7b 100755 --- a/helper_scripts/generate_SendCmioResponse.sh +++ b/helper_scripts/generate_SendCmioResponse.sh @@ -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" diff --git a/helper_scripts/generate_UArchStep.sh b/helper_scripts/generate_UArchStep.sh index ba23738..71fd858 100755 --- a/helper_scripts/generate_UArchStep.sh +++ b/helper_scripts/generate_UArchStep.sh @@ -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"` diff --git a/package.json b/package.json index a055d6d..9ba0a73 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/shasum-download b/shasum-download index 107f7cd..6b31010 100644 --- a/shasum-download +++ b/shasum-download @@ -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 diff --git a/src/EmulatorConstants.sol b/src/EmulatorConstants.sol index 7567708..74f4725 100644 --- a/src/EmulatorConstants.sol +++ b/src/EmulatorConstants.sol @@ -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; diff --git a/src/SendCmioResponse.sol b/src/SendCmioResponse.sol index ca5115b..80a7e25 100644 --- a/src/SendCmioResponse.sol +++ b/src/SendCmioResponse.sol @@ -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. @@ -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( @@ -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); diff --git a/src/UArchStep.sol b/src/UArchStep.sol index 6e7b9ce..2997e8e 100644 --- a/src/UArchStep.sol +++ b/src/UArchStep.sol @@ -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 { @@ -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 { @@ -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 { diff --git a/templates/UArchReplay.t.sol.template b/templates/UArchReplay.t.sol.template index 3c44374..645f6c8 100644 --- a/templates/UArchReplay.t.sol.template +++ b/templates/UArchReplay.t.sol.template @@ -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; @@ -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; @@ -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 = @@ -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 ( @@ -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[])); @@ -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); } diff --git a/test/AccessLogJsonParse.sol b/test/AccessLogJsonParse.sol index dc987d6..5e37d90 100644 --- a/test/AccessLogJsonParse.sol +++ b/test/AccessLogJsonParse.sol @@ -5,31 +5,32 @@ // Requires forge-std >= v1.9.2 (`vm.parseJsonTypeArray`). pragma solidity ^0.8.30; -import "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; -import "src/Buffer.sol"; +import {Buffer} from "src/Buffer.sol"; -import "./BufferAux.sol"; +import {BufferAux} from "./BufferAux.sol"; /// @dev Shared helpers for replay tests that load `accesses` arrays from JSON logs. abstract contract AccessLogJsonParse is Test { using BufferAux for Buffer.Context; - /// @dev JSON keys: address, log2_size, read_hash, read_value, sibling_hashes, type, - /// written_hash, written_value. + /// @dev Field order must match RAW_ACCESS_TYPE_DESCRIPTION below, which carries the + /// JSON key names: address, log2_size, read_hash, read_value, sibling_hashes, type, + /// written_hash, written_value. The decoding is positional, so the names may differ. struct RawAccess { uint256 accessAddress; - uint256 log2_size; - string read_hash; - string read_value; - string[] sibling_hashes; + uint256 log2Size; + string readHash; + string readValue; + string[] siblingHashes; string accessType; - string written_hash; - string written_value; + string writtenHash; + string writtenValue; } string internal constant RAW_ACCESS_TYPE_DESCRIPTION = - "RawAccess(uint256 address,uint256 log2_size,string read_hash,string read_value,string[] sibling_hashes,string type,string written_hash,string written_value)"; + "RawAccessJson(uint256 address,uint256 log2_size,string read_hash,string read_value,string[] sibling_hashes,string type,string written_hash,string written_value)"; function _fillBufferFromRawAccesses( RawAccess[] memory rawAccesses, @@ -38,21 +39,21 @@ abstract contract AccessLogJsonParse is Test { uint256 n = rawAccesses.length; for (uint256 i = 0; i < n; i++) { RawAccess memory a = rawAccesses[i]; - if (a.log2_size == 3) { - buffer.writeBytes32(_parseHex32FromLogString(a.read_value)); + if (a.log2Size == 3) { + buffer.writeBytes32(_parseHex32FromLogString(a.readValue)); } else if ( keccak256(bytes(a.accessType)) == keccak256(bytes("read")) ) { // a leaf read carries the read value followed by the leaf hash - buffer.writeBytes32(_parseHex32FromLogString(a.read_value)); - buffer.writeBytes32(_parseHex32FromLogString(a.read_hash)); + buffer.writeBytes32(_parseHex32FromLogString(a.readValue)); + buffer.writeBytes32(_parseHex32FromLogString(a.readHash)); } else { - buffer.writeBytes32(_parseHex32FromLogString(a.read_hash)); + buffer.writeBytes32(_parseHex32FromLogString(a.readHash)); } - uint256 siblingCount = a.sibling_hashes.length; + uint256 siblingCount = a.siblingHashes.length; for (uint256 j = 0; j < siblingCount; j++) { buffer.writeBytes32( - _parseHex32FromLogString(a.sibling_hashes[j]) + _parseHex32FromLogString(a.siblingHashes[j]) ); } } diff --git a/test/AccessLogs.t.sol b/test/AccessLogs.t.sol index 1889d12..5789742 100644 --- a/test/AccessLogs.t.sol +++ b/test/AccessLogs.t.sol @@ -13,11 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import "forge-std/console.sol"; -import "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; -import "src/AccessLogs.sol"; -import "./BufferAux.sol"; +import {AccessLogs} from "src/AccessLogs.sol"; +import {Buffer} from "src/Buffer.sol"; +import {Memory} from "src/Memory.sol"; +import {BufferAux} from "./BufferAux.sol"; pragma solidity ^0.8.30; @@ -55,19 +56,19 @@ contract AccessLogsTest is Test { initialReadLeaf = patchLeaf( bytes32(type(uint256).max), initialWordAtPosition, position ); - for (uint256 i = 0; i < 59; i++) { - siblingHashes.push(keccak256(abi.encodePacked(bytes8(uint64(i))))); + for (uint64 i = 0; i < 59; i++) { + siblingHashes.push(keccak256(abi.encodePacked(bytes8(i)))); } } - function verifyWord(bytes32 h, uint64 p, uint64 w) internal { + function verifyWord(bytes32 h, uint64 p, uint64 w) internal view { (Buffer.Context memory buffer,) = makeReadBuffer(bytes8(w).swapEndian(), false); AccessLogs.Context memory accessLogs = AccessLogs.Context(h, buffer); assertEq(accessLogs.readWord(p.toPhysicalAddress()), w); } - function testReadWordHappyPath() public { + function testReadWordHappyPath() public view { (Buffer.Context memory buffer, bytes32 rootHash) = makeReadBuffer( bytes8(0x0000000000000001).swapEndian(), /*withReadValueMismatch=*/ @@ -104,7 +105,7 @@ contract AccessLogsTest is Test { ExternalAccessLogs.readWord(accessLogs, position.toPhysicalAddress()); } - function testWriteWordHappyPath() public { + function testWriteWordHappyPath() public view { uint64 wordWritten = 3; (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( initialReadLeaf, @@ -150,7 +151,7 @@ contract AccessLogsTest is Test { ); } - function testEndianSwap() public { + function testEndianSwap() public pure { assertEq( bytes8(0x0000000000008000).swapEndian(), bytes8(0x0080000000000000) ); @@ -176,19 +177,19 @@ contract AccessLogsTest is Test { return leaf; } - function patchLeaf( - bytes32 currentLeaf, - bytes8 newWord, - uint64 wordPosition - ) public view returns (bytes32) { + function patchLeaf(bytes32 currentLeaf, bytes8 newWord, uint64 wordPosition) + public + view + returns (bytes32) + { uint64 leafPosition = wordPosition & ~uint64(31); uint64 offset = position - leafPosition; - bytes32 erase_mask = bytes32(bytes8(type(uint64).max)); - erase_mask = erase_mask >> (offset << Memory.LOG2_WORD); - erase_mask = ~erase_mask; + bytes32 eraseMask = bytes32(bytes8(type(uint64).max)); + eraseMask = eraseMask >> (offset << Memory.LOG2_WORD); + eraseMask = ~eraseMask; - bytes32 result = currentLeaf & erase_mask; + bytes32 result = currentLeaf & eraseMask; result = result | (bytes32(newWord) >> (offset << Memory.LOG2_WORD)); return result; } @@ -199,7 +200,7 @@ contract AccessLogsTest is Test { returns (Buffer.Context memory, bytes32) { Buffer.Context memory buffer = Buffer.Context( - new bytes((59 << Memory.LOG2_LEAF) + 32 + 32), 0 + new bytes((uint64(59) << Memory.LOG2_LEAF) + 32 + 32), 0 ); bytes32 readData = patchLeaf(initialReadLeaf, readWord, position); @@ -207,7 +208,8 @@ contract AccessLogsTest is Test { buffer.writeBytes32(readData); bytes32 readHash = keccak256(abi.encodePacked(readData)); if (withReadValueMismatch) { - readHash = keccak256(abi.encodePacked(bytes8(readHash))); + // re-hash to get a value that will not match the expected one + readHash = keccak256(abi.encodePacked(readHash)); } for (uint256 i = 0; i < 59; i++) { @@ -225,14 +227,15 @@ contract AccessLogsTest is Test { returns (Buffer.Context memory, bytes32) { Buffer.Context memory buffer = Buffer.Context( - new bytes((59 << Memory.LOG2_LEAF) + 32 + 32 + 32), 0 + new bytes((uint64(59) << Memory.LOG2_LEAF) + 32 + 32 + 32), 0 ); // write leaf data, leaf hash and sibling hashes buffer.writeBytes32(readLeaf); bytes32 readHash = keccak256(abi.encodePacked(readLeaf)); if (withReadValueMismatch) { - readHash = keccak256(abi.encodePacked(bytes8(readHash))); + // re-hash to get a value that will not match the expected one + readHash = keccak256(abi.encodePacked(readHash)); } for (uint256 i = 0; i < 59; i++) { diff --git a/test/BufferAux.sol b/test/BufferAux.sol index 7693e31..9b1d4d9 100644 --- a/test/BufferAux.sol +++ b/test/BufferAux.sol @@ -15,7 +15,7 @@ // pragma solidity ^0.8.30; -import "src/Buffer.sol"; +import {Buffer} from "src/Buffer.sol"; library BufferAux { using BufferAux for Buffer.Context; diff --git a/test/EmulatorCompat.t.sol b/test/EmulatorCompat.t.sol index 476cf13..412e902 100644 --- a/test/EmulatorCompat.t.sol +++ b/test/EmulatorCompat.t.sol @@ -13,9 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import "forge-std/console.sol"; -import "forge-std/Test.sol"; -import "src/EmulatorCompat.sol"; +import {Test} from "forge-std/Test.sol"; + +import {EmulatorCompat} from "src/EmulatorCompat.sol"; pragma solidity ^0.8.30; @@ -36,7 +36,7 @@ contract EmulatorCompatTest is Test { uint32 constant UINT32_MAX = type(uint32).max; uint64 constant UINT64_MAX = type(uint64).max; - function testSanity() public { + function testSanity() public pure { assertEq(UINT16_MAX, 65535); assertEq(UINT32_MAX, 4294967295); assertEq(UINT64_MAX, 18446744073709551615); @@ -48,7 +48,7 @@ contract EmulatorCompatTest is Test { assertEq(INT64_MIN, -INT64_MAX - 1); } - function testCompat() public { + function testCompat() public pure { assertEq(EmulatorCompat.uint64ToInt32(1), 1); assertEq(EmulatorCompat.uint64ToInt32(0xffffffff), -1); assertEq(EmulatorCompat.uint64ToInt32(0xffffffff << 31), INT32_MIN); @@ -149,7 +149,7 @@ contract EmulatorCompatTest is Test { assertEq(EmulatorCompat.int8ToUint64(int8(-128)), 0xffffffffffffff80); } - function testUint32Log2() public { + function testUint32Log2() public pure { assertEq(EmulatorCompat.uint32Log2(1), 0); assertEq(EmulatorCompat.uint32Log2(2), 1); assertEq(EmulatorCompat.uint32Log2(3), 1); diff --git a/test/Memory.t.sol b/test/Memory.t.sol index 620f2bf..c4f765c 100644 --- a/test/Memory.t.sol +++ b/test/Memory.t.sol @@ -13,11 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import "forge-std/console.sol"; -import "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; -import "./BufferAux.sol"; -import "./MemoryAux.sol"; +import {Memory} from "src/Memory.sol"; +import {MemoryAux} from "./MemoryAux.sol"; pragma solidity ^0.8.30; @@ -27,8 +26,13 @@ contract MemoryTest is Test { using MemoryAux for Memory.PhysicalAddress; function testStrideAlignment() public { - for (uint128 paddr = 8; paddr <= (1 << 63); paddr *= 2) { - for (uint8 l = 0; ((1 << l) <= (paddr >> Memory.LOG2_LEAF)); ++l) { + for (uint64 e = 3; e <= 63; e += 1) { + uint64 paddr = uint64(1) << e; + for ( + uint8 l = 0; + ((uint64(1) << l) <= (paddr >> Memory.LOG2_LEAF)); + ++l + ) { uint64(paddr).toPhysicalAddress() .strideFromPhysicalAddress(Memory.alignedSizeFromLog2(l)); @@ -37,7 +41,7 @@ contract MemoryTest is Test { uint64(paddr - 1).toPhysicalAddress() .strideFromPhysicalAddress(Memory.alignedSizeFromLog2(l)); - if ((1 << l) == (paddr >> Memory.LOG2_LEAF)) { + if ((uint64(1) << l) == (paddr >> Memory.LOG2_LEAF)) { // address has to be aligned with stride size vm.expectRevert(); uint64(paddr + paddr / 2).toPhysicalAddress() diff --git a/test/MemoryAux.sol b/test/MemoryAux.sol index 7141f70..e8154f8 100644 --- a/test/MemoryAux.sol +++ b/test/MemoryAux.sol @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import "src/Memory.sol"; +import {Memory} from "src/Memory.sol"; pragma solidity ^0.8.30; diff --git a/test/SendCmioResponse.t.sol b/test/SendCmioResponse.t.sol index 67900f9..e0e7e10 100644 --- a/test/SendCmioResponse.t.sol +++ b/test/SendCmioResponse.t.sol @@ -17,14 +17,14 @@ // 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/EmulatorConstants.sol"; -import "src/SendCmioResponse.sol"; -import "./AccessLogJsonParse.sol"; -import "./BufferAux.sol"; +import {AccessLogs} from "src/AccessLogs.sol"; +import {Buffer} from "src/Buffer.sol"; +import {EmulatorConstants} from "src/EmulatorConstants.sol"; +import {SendCmioResponse} from "src/SendCmioResponse.sol"; +import {AccessLogJsonParse} from "./AccessLogJsonParse.sol"; +import {BufferAux} from "./BufferAux.sol"; contract SendCmioResponse_Test is AccessLogJsonParse { using Buffer for Buffer.Context; @@ -37,7 +37,7 @@ contract SendCmioResponse_Test is AccessLogJsonParse { string constant SEND_CMIO_RESPONSE_NOOP_PATH = "send-cmio-response-noop-steps.json"; - uint256 constant siblingsLength = 59; + uint256 constant SIBLINGS_LENGTH = 59; struct Entry { string binaryFilename; @@ -48,7 +48,7 @@ contract SendCmioResponse_Test is AccessLogJsonParse { } string constant ENTRY_TYPE_DESCRIPTION = - "Entry(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)"; + "CatalogEntryJson(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)"; function testSendCmioResponse() public { Entry[] memory catalog = @@ -60,7 +60,7 @@ contract SendCmioResponse_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 ( @@ -101,10 +101,10 @@ contract SendCmioResponse_Test is AccessLogJsonParse { // the test log file was generated passing the initial root hash as the revert root hash SendCmioResponse.sendCmioResponse( accessLogs, - initialRootHash, reason, paddedResponseHash, - uint32(response.length) + uint32(response.length), + initialRootHash ); // ensure that the final root hash matches the expected value assertEq( @@ -125,7 +125,7 @@ contract SendCmioResponse_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); bool found = false; for (uint256 i = 0; i < catalog.length; i++) { @@ -171,10 +171,10 @@ contract SendCmioResponse_Test is AccessLogJsonParse { // the test log file was generated passing the initial root hash as the revert root hash SendCmioResponse.sendCmioResponse( accessLogs, - initialRootHash, reason, paddedResponseHash, - uint32(response.length) + uint32(response.length), + initialRootHash ); // ensure that the final root hash matches the expected value assertEq( @@ -193,6 +193,7 @@ contract SendCmioResponse_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); @@ -206,6 +207,7 @@ contract SendCmioResponse_Test is AccessLogJsonParse { view returns (string memory) { + // forge-lint: disable-next-line(unsafe-cheatcode) return vm.readFile(path); } diff --git a/test/UArchInterpret.sol b/test/UArchInterpret.sol index 4ca1dd3..3b4dec8 100644 --- a/test/UArchInterpret.sol +++ b/test/UArchInterpret.sol @@ -15,7 +15,8 @@ // pragma solidity ^0.8.30; -import "src/UArchStep.sol"; +import {AccessLogs} from "src/AccessLogs.sol"; +import {UArchStep} from "src/UArchStep.sol"; library UArchInterpret { /// @notice Run interpret until machine halts. diff --git a/test/UArchInterpret.t.sol b/test/UArchInterpret.t.sol index 4430dc8..27b4c6e 100644 --- a/test/UArchInterpret.t.sol +++ b/test/UArchInterpret.t.sol @@ -13,11 +13,16 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import "forge-std/console.sol"; -import "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; +import {Test} from "forge-std/Test.sol"; -import "./UArchInterpret.sol"; -import "src/EmulatorConstants.sol"; +import {AccessLogs} from "src/AccessLogs.sol"; +import {Buffer} from "src/Buffer.sol"; +import {EmulatorCompat} from "src/EmulatorCompat.sol"; +import {EmulatorConstants} from "src/EmulatorConstants.sol"; +import {Memory} from "src/Memory.sol"; +import {UArchStep} from "src/UArchStep.sol"; +import {UArchInterpret} from "./UArchInterpret.sol"; pragma solidity ^0.8.30; @@ -51,9 +56,9 @@ contract UArchInterpretTest is Test { string constant JSON_PATH = "./test/uarch-log/"; string constant BINARIES_PATH = "./test/uarch-bin/"; string constant ENTRY_TYPE_DESCRIPTION = - "Entry(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)"; + "CatalogEntryJson(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)"; - function testBinaries() public { + function testBinaries() public view { Entry[] memory catalog = loadCatalog(string.concat(JSON_PATH, CATALOG_PATH)); @@ -94,7 +99,7 @@ contract UArchInterpretTest is Test { } } - function testStepEarlyReturn() public { + function testStepEarlyReturn() public pure { AccessLogs.Context memory a = newAccessLogsContext(); // init pc to ram start @@ -153,7 +158,7 @@ contract UArchInterpretTest is Test { ExternalUArchInterpret.interpret(a); } - function testHaltEcallReachesCycleLimit() public { + function testHaltEcallReachesCycleLimit() public pure { AccessLogs.Context memory a = newAccessLogsContext(); a.buffer.data = bytes.concat(a.buffer.data, new bytes(8)); @@ -215,6 +220,7 @@ contract UArchInterpretTest is Test { 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); diff --git a/test/UArchReset.t.sol b/test/UArchReset.t.sol index 51c0d64..5dcfa38 100644 --- a/test/UArchReset.t.sol +++ b/test/UArchReset.t.sol @@ -17,14 +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/EmulatorConstants.sol"; -import "src/UArchReset.sol"; -import "./AccessLogJsonParse.sol"; -import "./BufferAux.sol"; +import {AccessLogs} from "src/AccessLogs.sol"; +import {Buffer} from "src/Buffer.sol"; +import {UArchReset} from "src/UArchReset.sol"; +import {AccessLogJsonParse} from "./AccessLogJsonParse.sol"; +import {BufferAux} from "./BufferAux.sol"; contract UArchReset_Test is AccessLogJsonParse { using Buffer for Buffer.Context; @@ -34,7 +33,7 @@ contract UArchReset_Test is AccessLogJsonParse { string constant JSON_PATH = "./test/uarch-log/"; string constant CATALOG_PATH = "catalog.json"; - uint256 constant siblingsLength = 42; + uint256 constant SIBLINGS_LENGTH = 42; struct Entry { string binaryFilename; @@ -45,7 +44,7 @@ contract UArchReset_Test is AccessLogJsonParse { } string constant ENTRY_TYPE_DESCRIPTION = - "Entry(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)"; + "CatalogEntryJson(string binaryFilename,string finalRootHash,string initialRootHash,string logFilename,uint256 steps)"; function testReset() public { Entry[] memory catalog = @@ -55,7 +54,7 @@ contract UArchReset_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); // count the fixtures replayed below, so a missing entry fails loudly uint256 found = 0; @@ -114,6 +113,7 @@ contract UArchReset_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); @@ -127,6 +127,7 @@ contract UArchReset_Test is AccessLogJsonParse { view returns (string memory) { + // forge-lint: disable-next-line(unsafe-cheatcode) return vm.readFile(path); }