diff --git a/Makefile b/Makefile index df7b10e..955e985 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ TEST_DIR := test DOWNLOADDIR := downloads SRC_DIR := src -EMULATOR_VERSION ?= v0.20.0 -EMULATOR_TAG ?= +EMULATOR_VERSION ?= v0.21.0 +EMULATOR_TAG ?= -test2 SOLIDITY_VERSION ?= 0.8.30 @@ -77,9 +77,10 @@ coverage-prod: dep COVERAGE_OUTPUT_DIR ?= coverage coverage-report: $(COVERAGE_OUTPUT_DIR) - lcov -a lcov-mock.info -a lcov-prod.info -o $(COVERAGE_OUTPUT_DIR)/lcov.info + sed 's|SF:src/AccessLogs.sol|SF:src/AccessLogsMock.sol|' lcov-mock.info > lcov-mock-patched.info + lcov -a lcov-mock-patched.info -a lcov-prod.info -o $(COVERAGE_OUTPUT_DIR)/lcov.info lcov --summary $(COVERAGE_OUTPUT_DIR)/lcov.info | tee $(COVERAGE_OUTPUT_DIR)/coverage.txt - genhtml --ignore-errors unmapped $(COVERAGE_OUTPUT_DIR)/lcov.info -o $(COVERAGE_OUTPUT_DIR)/html + genhtml --ignore-errors unmapped --substitute 's|AccessLogsMock.sol|AccessLogs.sol|' $(COVERAGE_OUTPUT_DIR)/lcov.info -o $(COVERAGE_OUTPUT_DIR)/html $(COVERAGE_OUTPUT_DIR): mkdir -p $(COVERAGE_OUTPUT_DIR) diff --git a/helper_scripts/generate_EmulatorConstants.lua b/helper_scripts/generate_EmulatorConstants.lua index 3e3d9ff..f932b82 100755 --- a/helper_scripts/generate_EmulatorConstants.lua +++ b/helper_scripts/generate_EmulatorConstants.lua @@ -39,16 +39,24 @@ out:write(' uint64 constant HTIF_FROMHOST_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("htif_fromhost")) .. ';\n') out:write(' uint64 constant HTIF_TOHOST_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("htif_tohost")) .. ';\n') -out:write(' uint8 constant CMIO_YIELD_REASON_ADVANCE_STATE = 0x' .. - hex(cartesi.CMIO_YIELD_REASON_ADVANCE_STATE) .. ';\n') +out:write(' uint8 constant HTIF_YIELD_REASON_ADVANCE_STATE = 0x' .. + hex(cartesi.HTIF_YIELD_REASON_ADVANCE_STATE) .. ';\n') out:write(' uint32 constant HASH_TREE_LOG2_WORD_SIZE = 0x' .. hex(cartesi.HASH_TREE_LOG2_WORD_SIZE) .. ';\n') out:write(' uint32 constant HASH_TREE_WORD_SIZE = uint32(1) << HASH_TREE_LOG2_WORD_SIZE;\n') -out:write(' uint16 constant CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED = 0x' .. - hex(cartesi.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED) .. ';\n') -out:write(' uint16 constant CMIO_YIELD_MANUAL_REASON_RX_REJECTED = 0x' .. - hex(cartesi.CMIO_YIELD_MANUAL_REASON_RX_REJECTED) .. ';\n') -out:write(' uint16 constant CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION = 0x' .. - hex(cartesi.CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION) .. ';\n') +out:write(' uint32 constant HTIF_DEV_SHIFT = 0x' .. hex(cartesi.HTIF_DEV_SHIFT) .. ';\n') +out:write(' uint32 constant HTIF_CMD_SHIFT = 0x' .. hex(cartesi.HTIF_CMD_SHIFT) .. ';\n') +out:write(' uint32 constant HTIF_REASON_SHIFT = 0x' .. hex(cartesi.HTIF_REASON_SHIFT) .. ';\n') +out:write(' uint64 constant HTIF_DEV_MASK = 0x' .. hex(cartesi.HTIF_DEV_MASK) .. ';\n') +out:write(' uint64 constant HTIF_CMD_MASK = 0x' .. hex(cartesi.HTIF_CMD_MASK) .. ';\n') +out:write(' uint64 constant HTIF_REASON_MASK = 0x' .. hex(cartesi.HTIF_REASON_MASK) .. ';\n') +out:write(' uint64 constant HTIF_DEV_YIELD = 0x' .. hex(cartesi.HTIF_DEV_YIELD) .. ';\n') +out:write(' uint64 constant HTIF_YIELD_CMD_MANUAL = 0x' .. hex(cartesi.HTIF_YIELD_CMD_MANUAL) .. ';\n') +out:write(' uint16 constant HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED = 0x' .. + hex(cartesi.HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED) .. ';\n') +out:write(' uint16 constant HTIF_YIELD_MANUAL_REASON_RX_REJECTED = 0x' .. + hex(cartesi.HTIF_YIELD_MANUAL_REASON_RX_REJECTED) .. ';\n') +out:write(' uint16 constant HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION = 0x' .. + hex(cartesi.HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION) .. ';\n') out:write(' uint8 constant UARCH_STATE_LOG2_SIZE = ' .. cartesi.UARCH_STATE_LOG2_SIZE .. ';\n') out:write(' uint64 constant AR_CMIO_RX_BUFFER_START = 0x' .. hex(cartesi.AR_CMIO_RX_BUFFER_START) .. ';\n') out:write(' uint8 constant AR_CMIO_RX_BUFFER_LOG2_SIZE = 0x' .. hex(cartesi.AR_CMIO_RX_BUFFER_LOG2_SIZE) .. ';\n') diff --git a/helper_scripts/generate_SendCmioResponse.sh b/helper_scripts/generate_SendCmioResponse.sh index 4fe7081..4213d67 100755 --- a/helper_scripts/generate_SendCmioResponse.sh +++ b/helper_scripts/generate_SendCmioResponse.sh @@ -37,12 +37,12 @@ pattern="namespace cartesi \{(.*)\}" cpp_src=`echo "${BASH_REMATCH[1]}" \ | $SED "/Explicit instantiatio/d" \ | $SED "/template/d" \ - | $SED "/ uint32 length);/d" \ + | $SED "/uint32 length);/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, uint16 reason, bytes data, uint32 dataLength) {/function sendCmioResponse(AccessLogs.Context memory a, uint16 reason, bytes32 dataHash, uint32 dataLength) internal pure {/" \ + | $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/const uint64/uint64/g" \ | $SED "s/const uint32/uint32/g" \ | $SED "/^$/N;/^\n$/D" diff --git a/helper_scripts/generate_UArchReset.sh b/helper_scripts/generate_UArchReset.sh index bf3f136..e3901b3 100755 --- a/helper_scripts/generate_UArchReset.sh +++ b/helper_scripts/generate_UArchReset.sh @@ -9,6 +9,7 @@ CPP_RESET_PATH=${EMULATOR_DIR}"/src/uarch-reset-state.cpp" TEMPLATE_FILE="./templates/UArchReset.sol.template" TARGET_FILE="src/UArchReset.sol" COMPAT_FILE="src/EmulatorCompat.sol" +CONSTANTS_FILE="src/EmulatorConstants.sol" KEYWORD_START="START OF AUTO-GENERATED CODE" KEYWORD_END="END OF AUTO-GENERATED CODE" @@ -16,6 +17,9 @@ KEYWORD_END="END OF AUTO-GENERATED CODE" COMPAT_FNS=`cat $COMPAT_FILE | grep -o "function [^(]*(" | $SED "s/function//g" | $SED "s/(//g"` COMPAT_FNS=`echo $COMPAT_FNS | $SED -E "s/( |\n)/|/g"` +# get constant names from EmulatorConstants.sol +CONSTANTS=`cat $CONSTANTS_FILE | grep -E -o 'constant\s+[^ ]*' | $SED -E "s/constant//g; s/ //g" | tr '\n' '|' | sed "s/.$//"` + # grab head and tail of the template start=`cat "$TEMPLATE_FILE" | grep "$KEYWORD_START" -n | grep -Eo "[0-9]*"` end=`cat "$TEMPLATE_FILE" | grep "$KEYWORD_END" -n | grep -Eo "[0-9]*"` @@ -34,6 +38,7 @@ cpp_src=`echo "${BASH_REMATCH[1]}" \ | $SED "/Explicit instantiatio/d" \ | $SED "/template/d" \ | $SED -E "s/($COMPAT_FNS)/EmulatorCompat.\1/g" \ + | $SED -E "s/($CONSTANTS)([^a-zA-Z])/EmulatorConstants.\1\2/g" \ | $SED "s/void uarch_reset_state(UarchState &a) {/function reset(AccessLogs.Context memory a) internal pure {/"` # compose the solidity file from all components diff --git a/shasum-download b/shasum-download index 22fb8d4..6bc3a76 100644 --- a/shasum-download +++ b/shasum-download @@ -1,2 +1,2 @@ -fef8844a306d83eef9c30828986645f2b0ff149654490b0f7d47ec2883bcb693 downloads/machine-emulator-tests-data.deb -bc7a0cc14724167c2826967eaf62feb6a2c902b2b45a73f3c684e1c5ceaa8f69 downloads/uarch-riscv-tests-json-logs.tar.gz +fd4c5d56f5515626762602991d7e8de09f85958ac2ece813e6b9bea148dea016 downloads/machine-emulator-tests-data.deb +96843bf71e5a8cd1628ef6eb43c793f046ed643fef3aebbed545d69e4dcf606e downloads/uarch-riscv-tests-json-logs.tar.gz diff --git a/src/AdvanceStatus.sol b/src/AdvanceStatus.sol deleted file mode 100644 index c297e1f..0000000 --- a/src/AdvanceStatus.sol +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -/// @title AdvanceStatus -/// @notice Return advance status - -pragma solidity ^0.8.30; - -import "./EmulatorCompat.sol"; -import "./EmulatorConstants.sol"; - -library AdvanceStatus { - enum Status { - NOT_YIELDED, - ACCEPTED, - REJECTED, - EXCEPTION - } - - /* - typedef struct cmt_io_yield { - uint8_t dev; - uint8_t cmd; - uint16_t reason; - uint32_t data; - } cmt_io_yield_t; - */ - - error InvalidReason(uint16 reason); - - function advanceStatus(AccessLogs.Context memory a) - internal - pure - returns (Status) - { - if (!EmulatorCompat.readIflagsY(a)) { - return Status.NOT_YIELDED; - } - - // the following two approaches are equivalent: - // 1. swap the whole struct and then extract the reason - // 2. extract the reason from the struct and then swap the value - // EmulatorCompat.readWord already swaps the struct, so we can extract the reason directly - - uint64 tohost = - EmulatorCompat.readWord(a, EmulatorConstants.HTIF_TOHOST_ADDRESS); - uint16 reason = uint16(tohost >> 32); - - if (reason == EmulatorConstants.CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED) { - return Status.ACCEPTED; - } else if ( - reason == EmulatorConstants.CMIO_YIELD_MANUAL_REASON_RX_REJECTED - ) { - return Status.REJECTED; - } else if ( - reason == EmulatorConstants.CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION - ) { - return Status.EXCEPTION; - } else { - revert InvalidReason(reason); - } - } -} diff --git a/src/EmulatorCompat.sol b/src/EmulatorCompat.sol index 59bb62d..f8c1c9f 100644 --- a/src/EmulatorCompat.sol +++ b/src/EmulatorCompat.sol @@ -23,7 +23,11 @@ library EmulatorCompat { using Buffer for Buffer.Context; using Memory for uint64; - function getRevertRootHash(AccessLogs.Context memory a) + function revertState(AccessLogs.Context memory a) internal pure { + a.currentRootHash = readRevertRootHash(a); + } + + function readRevertRootHash(AccessLogs.Context memory a) internal pure returns (bytes32) @@ -107,7 +111,7 @@ library EmulatorCompat { ); } - function setRevertRootHash( + function writeRevertRootHash( AccessLogs.Context memory a, bytes32 revertRootHash ) internal pure { @@ -183,6 +187,16 @@ library EmulatorCompat { ); } + function readHtifTohost(AccessLogs.Context memory a) + internal + pure + returns (uint64) + { + return a.readWord( + EmulatorConstants.HTIF_TOHOST_ADDRESS.toPhysicalAddress() + ); + } + // Conversions and arithmetic functions function int8ToUint64(int8 val) internal pure returns (uint64) { @@ -376,4 +390,26 @@ library EmulatorCompat { return n; } + + function isYieldedManualWith(uint64 tohost, uint64 yieldReason) + internal + pure + returns (bool) + { + uint64 dev = uint64ShiftRight( + tohost & EmulatorConstants.HTIF_DEV_MASK, + EmulatorConstants.HTIF_DEV_SHIFT + ); + uint64 cmd = uint64ShiftRight( + tohost & EmulatorConstants.HTIF_CMD_MASK, + EmulatorConstants.HTIF_CMD_SHIFT + ); + uint64 reason = uint64ShiftRight( + tohost & EmulatorConstants.HTIF_REASON_MASK, + EmulatorConstants.HTIF_REASON_SHIFT + ); + return dev == EmulatorConstants.HTIF_DEV_YIELD + && cmd == EmulatorConstants.HTIF_YIELD_CMD_MANUAL + && reason == yieldReason; + } } diff --git a/src/EmulatorConstants.sol b/src/EmulatorConstants.sol index 92832af..ce3b614 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 = - 0xa2f4f0018081d795e47c7feae9300055e8551eda5bd6473e54ca80ece64ea620; + 0x245a715ed3343e88ea5b2810584faea872991cca0200979e8f43056cd2db59c6; uint64 constant UARCH_CYCLE_ADDRESS = 0x400008; uint64 constant UARCH_CYCLE_MAX = 0x100000; uint64 constant UARCH_HALT_FLAG_ADDRESS = 0x400000; @@ -46,12 +46,20 @@ library EmulatorConstants { uint64 constant IFLAGS_Y_ADDRESS = 0x300; uint64 constant HTIF_FROMHOST_ADDRESS = 0x330; uint64 constant HTIF_TOHOST_ADDRESS = 0x328; - uint8 constant CMIO_YIELD_REASON_ADVANCE_STATE = 0x0; + uint8 constant HTIF_YIELD_REASON_ADVANCE_STATE = 0x0; uint32 constant HASH_TREE_LOG2_WORD_SIZE = 0x5; uint32 constant HASH_TREE_WORD_SIZE = uint32(1) << HASH_TREE_LOG2_WORD_SIZE; - uint16 constant CMIO_YIELD_MANUAL_REASON_RX_ACCEPTED = 0x1; - uint16 constant CMIO_YIELD_MANUAL_REASON_RX_REJECTED = 0x2; - uint16 constant CMIO_YIELD_MANUAL_REASON_TX_EXCEPTION = 0x4; + uint32 constant HTIF_DEV_SHIFT = 0x38; + uint32 constant HTIF_CMD_SHIFT = 0x30; + uint32 constant HTIF_REASON_SHIFT = 0x20; + uint64 constant HTIF_DEV_MASK = 0xff00000000000000; + uint64 constant HTIF_CMD_MASK = 0xff000000000000; + uint64 constant HTIF_REASON_MASK = 0xffff00000000; + uint64 constant HTIF_DEV_YIELD = 0x2; + uint64 constant HTIF_YIELD_CMD_MANUAL = 0x1; + uint16 constant HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED = 0x1; + uint16 constant HTIF_YIELD_MANUAL_REASON_RX_REJECTED = 0x2; + uint16 constant HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION = 0x4; uint8 constant UARCH_STATE_LOG2_SIZE = 22; uint64 constant AR_CMIO_RX_BUFFER_START = 0x60000000; uint8 constant AR_CMIO_RX_BUFFER_LOG2_SIZE = 0x15; diff --git a/src/SendCmioResponse.sol b/src/SendCmioResponse.sol index fdf57bb..4bcb0e7 100644 --- a/src/SendCmioResponse.sol +++ b/src/SendCmioResponse.sol @@ -31,17 +31,34 @@ library SendCmioResponse { function sendCmioResponse( AccessLogs.Context memory a, + bytes32 revertRootHash, uint16 reason, bytes32 dataHash, uint32 dataLength ) 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. + // A response to a machine that is not waiting on a manual yield is a no-op. if (!EmulatorCompat.readIflagsY(a)) { - EmulatorCompat.throwRuntimeError(a, "iflags.Y is not set"); + return; + } + if (reason == EmulatorConstants.HTIF_YIELD_REASON_ADVANCE_STATE) { + // Advance-state responses are the input boundary of the rollups flow. They only apply to a + // machine waiting for an input on an rx-accepted manual yield. Sending one to a machine that + // yielded manual with any other reason (e.g., rejected an input or threw an exception) is a no-op. + uint64 tohost = EmulatorCompat.readHtifTohost(a); + if (!EmulatorCompat.isYieldedManualWith( + tohost, + EmulatorConstants.HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED + )) { + return; + } } // A zero length data is a valid response. We just skip writing to the rx buffer. + uint32 writeLengthLog2Size = 0; if (dataLength > 0) { // Find the write length: the smallest power of 2 that is >= dataLength and >= tree leaf size - uint32 writeLengthLog2Size = EmulatorCompat.uint32Log2(dataLength); + writeLengthLog2Size = EmulatorCompat.uint32Log2(dataLength); if ( writeLengthLog2Size < EmulatorConstants.HASH_TREE_LOG2_WORD_SIZE ) { @@ -53,14 +70,17 @@ library SendCmioResponse { ) { writeLengthLog2Size += 1; } + // A response with data that does not fit in the rx buffer is a no-op if ( writeLengthLog2Size > EmulatorConstants.AR_CMIO_RX_BUFFER_LOG2_SIZE ) { - EmulatorCompat.throwRuntimeError( - a, "CMIO response data is too large" - ); + return; } + } + // 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( EmulatorConstants.AR_CMIO_RX_BUFFER_START diff --git a/src/UArchReset.sol b/src/UArchReset.sol index 21bd8e7..33bb070 100644 --- a/src/UArchReset.sol +++ b/src/UArchReset.sol @@ -28,6 +28,21 @@ library UArchReset { function reset(AccessLogs.Context memory a) internal pure { EmulatorCompat.resetState(a); + // When the machine has rejected an input, the canonical state after the operation is + // the one recorded in the revert root hash (which has a pristine uarch) + uint64 iflagsY = + EmulatorCompat.readWord(a, EmulatorConstants.IFLAGS_Y_ADDRESS); + if (iflagsY != 0) { + uint64 tohost = EmulatorCompat.readWord( + a, EmulatorConstants.HTIF_TOHOST_ADDRESS + ); + if (EmulatorCompat.isYieldedManualWith( + tohost, + EmulatorConstants.HTIF_YIELD_MANUAL_REASON_RX_REJECTED + )) { + EmulatorCompat.revertState(a); + } + } } // END OF AUTO-GENERATED CODE diff --git a/templates/UArchReplay.t.sol.template b/templates/UArchReplay.t.sol.template index 028c354..ccf5fb2 100644 --- a/templates/UArchReplay.t.sol.template +++ b/templates/UArchReplay.t.sol.template @@ -122,6 +122,6 @@ contract UArchReplay_@X@_Test is AccessLogJsonParse { ); RawAccess[] memory rawAccesses = abi.decode(raw, (RawAccess[])); Buffer.Context memory buffer = Buffer.Context(data, 0); - _fillBufferFromRawAccesses(rawAccesses, buffer, siblingsLength); + _fillBufferFromRawAccesses(rawAccesses, buffer); } } diff --git a/test/AccessLogJsonParse.sol b/test/AccessLogJsonParse.sol index 5f1e68a..93e09f7 100644 --- a/test/AccessLogJsonParse.sol +++ b/test/AccessLogJsonParse.sol @@ -33,18 +33,24 @@ abstract contract AccessLogJsonParse is Test { function _fillBufferFromRawAccesses( RawAccess[] memory rawAccesses, - Buffer.Context memory buffer, - uint256 fixedSiblingsLength + Buffer.Context memory buffer ) internal pure { 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)); + } 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)); } else { buffer.writeBytes32(_parseHex32FromLogString(a.read_hash)); } - for (uint256 j = 0; j < fixedSiblingsLength; j++) { + uint256 siblingCount = a.sibling_hashes.length; + for (uint256 j = 0; j < siblingCount; j++) { buffer.writeBytes32( _parseHex32FromLogString(a.sibling_hashes[j]) ); diff --git a/test/SendCmioResponse.t.sol b/test/SendCmioResponse.t.sol index 14de898..1b4086c 100644 --- a/test/SendCmioResponse.t.sol +++ b/test/SendCmioResponse.t.sol @@ -34,6 +34,8 @@ contract SendCmioResponse_Test is AccessLogJsonParse { string constant JSON_PATH = "./test/uarch-log/"; string constant CATALOG_PATH = "catalog.json"; string constant SEND_CMIO_RESPONSE_PATH = "send-cmio-response-steps.json"; + string constant SEND_CMIO_RESPONSE_NOOP_PATH = + "send-cmio-response-noop-steps.json"; uint256 constant siblingsLength = 59; @@ -98,8 +100,13 @@ contract SendCmioResponse_Test is AccessLogJsonParse { } bytes32 paddedResponseHash = keccak256(paddedResponse); // call sendCmioResponse + // the test log file was generated passing the initial root hash as the revert root hash SendCmioResponse.sendCmioResponse( - accessLogs, reason, paddedResponseHash, uint32(response.length) + accessLogs, + initialRootHash, + reason, + paddedResponseHash, + uint32(response.length) ); // ensure that the final root hash matches the expected value assertEq( @@ -110,6 +117,81 @@ contract SendCmioResponse_Test is AccessLogJsonParse { } } + function testSendCmioResponseNoop() public { + Entry[] memory catalog = + loadCatalog(string.concat(JSON_PATH, CATALOG_PATH)); + string memory noopLog = + string.concat(JSON_PATH, SEND_CMIO_RESPONSE_NOOP_PATH); + + // all tests combined can easily run out of gas, stop metering + // 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); + bool found = false; + + for (uint256 i = 0; i < catalog.length; i++) { + if ( + keccak256(abi.encodePacked(catalog[i].logFilename)) + != keccak256( + abi.encodePacked("send-cmio-response-noop-steps.json") + ) + ) { + continue; + } + found = true; + console.log("Replaying log file %s ...", catalog[i].logFilename); + + string memory rj = loadJsonLog(noopLog); + + bytes32 initialRootHash = vm.parseBytes32( + string.concat("0x", catalog[i].initialRootHash) + ); + bytes32 finalRootHash = + vm.parseBytes32(string.concat("0x", catalog[i].finalRootHash)); + // the log was taken from a machine that yielded manual with reason + // rx-rejected, so the advance-state response must be a no-op + assertEq( + initialRootHash, + finalRootHash, + "initial and final root hashes must match" + ); + + loadBufferFromRawJson(buffer, rj); + + AccessLogs.Context memory accessLogs = + AccessLogs.Context(initialRootHash, Buffer.Context(buffer, 0)); + + // Prepare arguments for sendCmioResponse + // These values are hard-coded in order to match the values used when generating the test log file + uint16 reason = EmulatorConstants.HTIF_YIELD_REASON_ADVANCE_STATE; + bytes memory response = bytes("This is a test cmio response"); + bytes memory paddedResponse = new bytes(32); + for (uint256 j = 0; j < response.length; j++) { + paddedResponse[j] = response[j]; + } + bytes32 paddedResponseHash = keccak256(paddedResponse); + // call sendCmioResponse + // 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) + ); + // ensure that the final root hash matches the expected value + assertEq( + accessLogs.currentRootHash, + finalRootHash, + "final root hash must match" + ); + } + assertTrue( + found, "catalog has no send-cmio-response-noop-steps.json entry" + ); + } + function loadCatalog(string memory path) private view @@ -140,6 +222,6 @@ contract SendCmioResponse_Test is AccessLogJsonParse { ); RawAccess[] memory rawAccesses = abi.decode(raw, (RawAccess[])); Buffer.Context memory buffer = Buffer.Context(data, 0); - _fillBufferFromRawAccesses(rawAccesses, buffer, siblingsLength); + _fillBufferFromRawAccesses(rawAccesses, buffer); } } diff --git a/test/UArchReset.t.sol b/test/UArchReset.t.sol index 49f9056..996cdda 100644 --- a/test/UArchReset.t.sol +++ b/test/UArchReset.t.sol @@ -33,7 +33,6 @@ contract UArchReset_Test is AccessLogJsonParse { // configure the tests string constant JSON_PATH = "./test/uarch-log/"; string constant CATALOG_PATH = "catalog.json"; - string constant RESET_PATH = "reset-uarch-steps.json"; uint256 constant siblingsLength = 42; @@ -51,7 +50,6 @@ contract UArchReset_Test is AccessLogJsonParse { function testReset() public { Entry[] memory catalog = loadCatalog(string.concat(JSON_PATH, CATALOG_PATH)); - string memory resetLog = string.concat(JSON_PATH, RESET_PATH); // all tests combined can easily run out of gas, stop metering // also raise memory_limit in foundry.toml per https://github.com/foundry-rs/foundry/issues/3971 @@ -60,15 +58,28 @@ contract UArchReset_Test is AccessLogJsonParse { bytes memory buffer = new bytes(100 * (siblingsLength + 1) * 32); for (uint256 i = 0; i < catalog.length; i++) { + // run the plain reset log, the rejected-input log (whose final + // root hash is the recorded revert root hash), and the + // accepted-input log (which reads the manual yield but does not + // revert, so its final root hash is the post-reset root) if ( keccak256(abi.encodePacked(catalog[i].logFilename)) - != keccak256(abi.encodePacked("reset-uarch-steps.json")) + != keccak256(abi.encodePacked("reset-uarch-steps.json")) + && keccak256(abi.encodePacked(catalog[i].logFilename)) + != keccak256( + abi.encodePacked("reset-uarch-rejected-steps.json") + ) + && keccak256(abi.encodePacked(catalog[i].logFilename)) + != keccak256( + abi.encodePacked("reset-uarch-accepted-steps.json") + ) ) { continue; } console.log("Replaying log file %s ...", catalog[i].logFilename); - string memory rj = loadJsonLog(resetLog); + string memory rj = + loadJsonLog(string.concat(JSON_PATH, catalog[i].logFilename)); bytes32 initialRootHash = vm.parseBytes32( string.concat("0x", catalog[i].initialRootHash) @@ -123,24 +134,7 @@ contract UArchReset_Test is AccessLogJsonParse { rawJson, ".accesses", RAW_ACCESS_TYPE_DESCRIPTION ); RawAccess[] memory rawAccesses = abi.decode(raw, (RawAccess[])); - assertEq(rawAccesses.length, 1, "should be only 1 access in reset"); - - RawAccess memory a = rawAccesses[0]; - if (keccak256(bytes(a.accessType)) == keccak256(bytes("read"))) { - revert("should'nt have read access in reset"); - } - assertEq( - a.accessAddress, - EmulatorConstants.UARCH_STATE_START_ADDRESS, - "position should be (0x400000)" - ); - assertEq( - a.log2_size, - EmulatorConstants.UARCH_STATE_LOG2_SIZE, - "log2Size should be 22" - ); - Buffer.Context memory buffer = Buffer.Context(data, 0); - _fillBufferFromRawAccesses(rawAccesses, buffer, siblingsLength); + _fillBufferFromRawAccesses(rawAccesses, buffer); } }