From 4f9ee32ed552a48534c52ffd1a7f65119e02bac4 Mon Sep 17 00:00:00 2001 From: Marcos Pernambuco Motta <1091485+mpernambuco@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:38:48 -0300 Subject: [PATCH 1/6] feat: increase tree leaf log2 size to 5 --- .github/workflows/build.yml | 2 +- .github/workflows/test.yml | 2 +- .gitignore | 1 + Makefile | 2 +- foundry.toml | 1 + shasum-download | 4 +- src/AccessLogs.sol | 44 ++++++++++++++----- src/Memory.sol | 2 +- src/UArchConstants.sol | 2 +- templates/AccessLogs.sol.template | 34 ++++++++++----- templates/UArchReplay.t.sol.template | 18 +++----- test/AccessLogs.t.sol | 65 ++++++++++++++++------------ 12 files changed, 107 insertions(+), 70 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ef84749..33b89d5d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly-008922d5165c764859bc540d7298045eebf5bc60 + version: nightly-fe2acca4e379793539db80e032d76ffe0110298b - run: forge build - run: forge fmt --check src test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4df1a7dd..323c46e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly-008922d5165c764859bc540d7298045eebf5bc60 + version: nightly-fe2acca4e379793539db80e032d76ffe0110298b - name: Run all tests run: make test-all diff --git a/.gitignore b/.gitignore index 47ffa89f..c23500ea 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ test/uarch-log *.log test/UArchReplay_*.t.sol .DS_Store +tests/* diff --git a/Makefile b/Makefile index aa22d652..ba62cf66 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TEST_DIR := test DOWNLOADDIR := downloads SRC_DIR := src -EMULATOR_VERSION ?= v0.17.0 +EMULATOR_VERSION ?= v0.18.0-test4 TESTS_DATA_FILE ?= cartesi-machine-tests-data-$(EMULATOR_VERSION).deb TESTS_DATA_DOWNLOAD_URL := https://github.com/cartesi/machine-emulator/releases/download/$(EMULATOR_VERSION)/$(TESTS_DATA_FILE) diff --git a/foundry.toml b/foundry.toml index 21bf4aaa..574c3a88 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,6 +5,7 @@ libs = ["node_modules", "lib"] gas_reports = ["*"] memory_limit = 11149934592 fs_permissions = [{access = "read", path = "./test/uarch-log/"}, {access = "read", path = "./test/uarch-bin/"}] +gas_limit = "18446744073709551615" [fmt] line_length = 80 diff --git a/shasum-download b/shasum-download index 2ddbfe18..ce921cff 100644 --- a/shasum-download +++ b/shasum-download @@ -1,2 +1,2 @@ -d3b254f274fd6c32e6688365886fbe6b86f91aab733a83cdb73e2461ed7a0d96 downloads/cartesi-machine-tests-data-v0.17.0.deb -8f0cd797b78466b62184257b3276d6c029c5ea5a53672036ad507cbf417d0211 downloads/uarch-riscv-tests-json-logs-v0.17.0.tar.gz +6024a9c7ebf6d10d3721753401c0edd7c3f1392cd26f1cc549fc58d15647a04a downloads/cartesi-machine-tests-data-v0.18.0-test4.deb +994af6de340e7efaf024545ddc44e850761115a9be98b82eeec441c869151525 downloads/uarch-riscv-tests-json-logs-v0.18.0-test4.tar.gz diff --git a/src/AccessLogs.sol b/src/AccessLogs.sol index 6491b450..50d7de34 100644 --- a/src/AccessLogs.sol +++ b/src/AccessLogs.sol @@ -61,9 +61,8 @@ library AccessLogs { return end2; } - /// @dev bytes buffer layouts differently for `readWord` and `writeWord`, - ///`readWord` [8 bytes as uint64 value, 32 bytes as drive hash, 61 * 32 bytes as sibling proofs] - ///`writeWord` [32 bytes as old drive hash, 32 * 61 bytes as sibling proofs] + /// @dev bytes buffer layout is the same for `readWord` and `writeWord`, + /// [32 bytes as read data], [59 * 32 bytes as sibling hashes] // // Read methods @@ -86,7 +85,7 @@ library AccessLogs { returns (bytes32) { Memory.Region memory r = - Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(0)); + Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(2)); return readRegion(a, r); } @@ -94,13 +93,21 @@ library AccessLogs { AccessLogs.Context memory a, Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { - bytes8 b8 = a.buffer.consumeBytes8(); - bytes32 valHash = keccak256(abi.encodePacked(b8)); + bytes32 readData = a.buffer.consumeBytes32(); + bytes32 valHash = keccak256(abi.encodePacked(readData)); + Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap( + Memory.PhysicalAddress.unwrap(readAddress) & ~uint64(31) + ); + uint64 offset = Memory.PhysicalAddress.unwrap(readAddress) + - Memory.PhysicalAddress.unwrap(leafAddress); + + bytes8 readValue = bytes8(readData << (offset << 3)); + bytes32 expectedValHash = - readLeaf(a, Memory.strideFromWordAddress(readAddress)); + readLeaf(a, Memory.strideFromWordAddress(leafAddress)); require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(b8); + return machineWordToSolidityUint64(readValue); } // @@ -129,7 +136,7 @@ library AccessLogs { bytes32 newHash ) internal pure { Memory.Region memory r = - Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(0)); + Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(2)); writeRegion(a, r, newHash); } @@ -138,10 +145,25 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { + bytes32 writtenData = a.buffer.consumeBytes32(); + Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap( + Memory.PhysicalAddress.unwrap(writeAddress) & ~uint64(31) + ); + uint64 offset = Memory.PhysicalAddress.unwrap(writeAddress) + - Memory.PhysicalAddress.unwrap(leafAddress); + + uint64 expectedNewValue = + machineWordToSolidityUint64(bytes8(writtenData << (offset << 3))); + + require( + newValue == expectedNewValue, + "Access log value does not contain the expected written value" + ); + writeLeaf( a, - Memory.strideFromWordAddress(writeAddress), - keccak256(abi.encodePacked(solidityUint64ToMachineWord(newValue))) + Memory.strideFromWordAddress(leafAddress), + keccak256(abi.encodePacked(writtenData)) ); } } diff --git a/src/Memory.sol b/src/Memory.sol index 7fa8540e..1597ef62 100644 --- a/src/Memory.sol +++ b/src/Memory.sol @@ -90,7 +90,7 @@ library Memory { pure returns (Stride) { - return strideFromPhysicalAddress(startAddress, alignedSizeFromLog2(0)); + return strideFromPhysicalAddress(startAddress, alignedSizeFromLog2(2)); } function validateStrideLength(Stride stride, AlignedSize alignedSize) diff --git a/src/UArchConstants.sol b/src/UArchConstants.sol index e9183fb3..0fc2a89a 100644 --- a/src/UArchConstants.sol +++ b/src/UArchConstants.sol @@ -35,7 +35,7 @@ library UArchConstants { uint64 constant RESET_POSITION = 0x400000; uint8 constant RESET_ALIGNED_SIZE = 22; bytes32 constant PRESTINE_STATE = - 0xc2b9cf2658f233f44c403aba6e9f9aa6709e00617b349c0190113517f03d13de; + 0xb0a850996464081741f4eefe12160dd2511514b3d606ec5b9c1b183fbe5a248f; uint64 constant UARCH_ECALL_FN_HALT = 1; uint64 constant UARCH_ECALL_FN_PUTCHAR = 2; // END OF AUTO-GENERATED CODE diff --git a/templates/AccessLogs.sol.template b/templates/AccessLogs.sol.template index 7254c6b1..dc2742df 100644 --- a/templates/AccessLogs.sol.template +++ b/templates/AccessLogs.sol.template @@ -74,9 +74,8 @@ library AccessLogs { //:#ifndef test - /// @dev bytes buffer layouts differently for `readWord` and `writeWord`, - ///`readWord` [8 bytes as uint64 value, 32 bytes as drive hash, 61 * 32 bytes as sibling proofs] - ///`writeWord` [32 bytes as old drive hash, 32 * 61 bytes as sibling proofs] + /// @dev bytes buffer layout is the same for `readWord` and `writeWord`, + /// [32 bytes as read data], [59 * 32 bytes as sibling hashes] // // Read methods @@ -99,7 +98,7 @@ library AccessLogs { returns (bytes32) { Memory.Region memory r = - Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(0)); + Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(2)); return readRegion(a, r); } @@ -107,13 +106,18 @@ library AccessLogs { AccessLogs.Context memory a, Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { - bytes8 b8 = a.buffer.consumeBytes8(); - bytes32 valHash = keccak256(abi.encodePacked(b8)); + bytes32 readData = a.buffer.consumeBytes32(); + bytes32 valHash = keccak256(abi.encodePacked(readData)); + Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap(Memory.PhysicalAddress.unwrap(readAddress) & ~uint64(31)); + uint64 offset = Memory.PhysicalAddress.unwrap(readAddress) - Memory.PhysicalAddress.unwrap(leafAddress); + + bytes8 readValue = bytes8(readData << (offset << 3)); + bytes32 expectedValHash = - readLeaf(a, Memory.strideFromWordAddress(readAddress)); + readLeaf(a, Memory.strideFromWordAddress(leafAddress)); require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(b8); + return machineWordToSolidityUint64(readValue); } // @@ -142,7 +146,7 @@ library AccessLogs { bytes32 newHash ) internal pure { Memory.Region memory r = - Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(0)); + Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(2)); writeRegion(a, r, newHash); } @@ -151,10 +155,18 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { + bytes32 writtenData = a.buffer.consumeBytes32(); + Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap(Memory.PhysicalAddress.unwrap(writeAddress) & ~uint64(31)); + uint64 offset = Memory.PhysicalAddress.unwrap(writeAddress) - Memory.PhysicalAddress.unwrap(leafAddress); + + uint64 expectedNewValue = machineWordToSolidityUint64(bytes8(writtenData << (offset << 3))); + + require(newValue == expectedNewValue, "Access log value does not contain the expected written value"); + writeLeaf( a, - Memory.strideFromWordAddress(writeAddress), - keccak256(abi.encodePacked(solidityUint64ToMachineWord(newValue))) + Memory.strideFromWordAddress(leafAddress), + keccak256(abi.encodePacked(writtenData)) ); } diff --git a/templates/UArchReplay.t.sol.template b/templates/UArchReplay.t.sol.template index 4c5a4e50..659b04c5 100644 --- a/templates/UArchReplay.t.sol.template +++ b/templates/UArchReplay.t.sol.template @@ -33,7 +33,7 @@ contract UArchReplay_@X@_Test is Test { string constant JSON_PATH = "./test/uarch-log/"; string constant CATALOG_PATH = "catalog.json"; - uint256 constant siblingsLength = 61; + uint256 constant siblingsLength = 59; struct Entry { string binaryFilename; @@ -84,12 +84,10 @@ contract UArchReplay_@X@_Test is Test { vm.parseBytes32(string.concat("0x", catalog[i].initialRootHash)); bytes32 finalRootHash = vm.parseBytes32(string.concat("0x", catalog[i].finalRootHash)); - for (uint256 j = 0; j < catalog[i].steps; j++) { console.log("Replaying step %d ...", j); // load json log loadBufferFromRawJson(buffer, rj, j); - AccessLogs.Context memory accessLogs = AccessLogs.Context( initialRootHash, Buffer.Context(buffer, 0) ); @@ -140,20 +138,14 @@ contract UArchReplay_@X@_Test is Test { Buffer.Context memory buffer = Buffer.Context(data, 0); for (uint256 i = 0; i < arrayLength; i++) { - if ( - keccak256(abi.encodePacked(rawAccesses[i].typeAccess)) - == keccak256(abi.encodePacked("read")) - ) { - bytes8 word = bytes8( - vm.parseBytes(string.concat("0x", rawAccesses[i].value)) - ); - buffer.writeBytes8(word); - } + bytes32 word = bytes32( + vm.parseBytes32(string.concat("0x", rawAccesses[i].value)) + ); + buffer.writeBytes32(word); buffer.writeBytes32( vm.parseBytes32(string.concat("0x", rawAccesses[i].read_hash)) ); - for (uint256 j = 0; j < siblingsLength; j++) { buffer.writeBytes32( vm.parseBytes32( diff --git a/test/AccessLogs.t.sol b/test/AccessLogs.t.sol index de81440b..cd0c1e93 100644 --- a/test/AccessLogs.t.sol +++ b/test/AccessLogs.t.sol @@ -33,16 +33,21 @@ contract AccessLogsTest is Test { uint64 position = 800; function setUp() public { - // the hashes include 62 elements, the hash at access position, and other 61 siblings + // the hashes include 60 elements, the hash at access position, and other 59 siblings // hash value at access position hashes.push( - keccak256(abi.encodePacked(bytes8(0x0000000000000001).swapEndian())) + keccak256( + abi.encodePacked( + makeLeaf(bytes8(0x0000000000000001).swapEndian(), position) + ) + ) ); + // direct sibling hash hashes.push( keccak256(abi.encodePacked(bytes8(0x0000000000000002).swapEndian())) ); - for (uint256 i = 2; i < 62; i++) { + for (uint256 i = 2; i < 60; i++) { hashes.push( keccak256(abi.encodePacked(hashes[i - 1], hashes[i - 2])) ); @@ -66,7 +71,7 @@ contract AccessLogsTest is Test { ); vm.expectRevert("Read region root doesn't match"); - accessLogs.readWord((position + 8).toPhysicalAddress()); + accessLogs.readWord((position + 32).toPhysicalAddress()); } function testReadWordValue() public { @@ -80,9 +85,9 @@ contract AccessLogsTest is Test { } function testWriteWord() public view { - AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes()); uint64 valueWritten = 1; + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, writeBufferFromHashes(valueWritten)); // // write should succeed accessLogs.writeWord(position.toPhysicalAddress(), valueWritten); @@ -92,21 +97,21 @@ contract AccessLogsTest is Test { hashes[0] = ( keccak256(abi.encodePacked(bytes8(0x0000000000000002).swapEndian())) ); - AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes()); uint64 valueWritten = 1; + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, writeBufferFromHashes(valueWritten)); vm.expectRevert("Write region root doesn't match"); accessLogs.writeWord(position.toPhysicalAddress(), valueWritten); } function testWriteWordRootPosition() public { - AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes()); uint64 valueWritten = 1; + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, writeBufferFromHashes(valueWritten)); vm.expectRevert("Write region root doesn't match"); - accessLogs.writeWord((position + 8).toPhysicalAddress(), valueWritten); + accessLogs.writeWord((position + 32).toPhysicalAddress(), valueWritten); } function testEndianSwap() public { @@ -124,16 +129,28 @@ contract AccessLogsTest is Test { ); } + function makeLeaf(bytes8 word, uint64 wordPosition) + public + view + returns (bytes32) + { + uint64 leafPosition = wordPosition & ~uint64(31); + uint64 offset = position - leafPosition; + bytes32 b32 = bytes32(word) << (offset << 3); + return b32; + } + function readBufferFromHashes(bytes8 word) private view returns (Buffer.Context memory) { + bytes32 b32 = makeLeaf(word, position); Buffer.Context memory buffer = - Buffer.Context(new bytes((62 << 5) + 8), 0); - buffer.writeBytes8(word); + Buffer.Context(new bytes((59 << 5) + 32 + 32), 0); + buffer.writeBytes32(b32); // leaf containing the readd word - for (uint256 i = 0; i < 62; i++) { + for (uint256 i = 0; i < 60; i++) { buffer.writeBytes32(hashes[i]); } @@ -143,27 +160,19 @@ contract AccessLogsTest is Test { return buffer; } - function writeBufferFromHashes() + function writeBufferFromHashes(uint64 valueWritten) private view returns (Buffer.Context memory) { - Buffer.Context memory buffer = Buffer.Context(new bytes(62 << 5), 0); - - for (uint256 i = 0; i < 62; i++) { - buffer.writeBytes32(hashes[i]); - } - - // reset offset for replay - buffer.offset = 0; - - return buffer; + bytes8 b8 = bytes8(valueWritten); + return readBufferFromHashes(b8.swapEndian()); } function rootFromHashes(bytes32 drive) private view returns (bytes32) { - Buffer.Context memory buffer = Buffer.Context(new bytes(61 << 5), 0); + Buffer.Context memory buffer = Buffer.Context(new bytes(59 << 5), 0); - for (uint256 i = 0; i < 61; i++) { + for (uint256 i = 0; i < 59; i++) { buffer.writeBytes32(hashes[i + 1]); } @@ -172,7 +181,7 @@ contract AccessLogsTest is Test { (bytes32 root,) = buffer.peekRoot( Memory.regionFromStride( Memory.strideFromWordAddress(position.toPhysicalAddress()), - Memory.alignedSizeFromLog2(0) + Memory.alignedSizeFromLog2(2) ), drive ); From 5340fa79cc30c70aa9ac9f4c15ccdd76b7260ec6 Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Thu, 25 Jul 2024 22:04:34 -0300 Subject: [PATCH 2/6] feat: increase leaf size --- helper_scripts/generate_UArchConstants.sh | 6 +-- src/AccessLogs.sol | 38 ++++++++++-------- src/Buffer.sol | 9 +++-- src/Memory.sol | 47 +++++++++++++++++------ src/UArchCompat.sol | 2 +- templates/AccessLogs.sol.template | 33 ++++++++++------ test/AccessLogs.t.sol | 11 +++--- test/Memory.t.sol | 4 +- 8 files changed, 95 insertions(+), 55 deletions(-) diff --git a/helper_scripts/generate_UArchConstants.sh b/helper_scripts/generate_UArchConstants.sh index 74a059cb..83d482eb 100755 --- a/helper_scripts/generate_UArchConstants.sh +++ b/helper_scripts/generate_UArchConstants.sh @@ -18,9 +18,9 @@ let last=total-end+1 h=`head -n $start $TEMPLATE_FILE` t=`tail -n -$last $TEMPLATE_FILE` -cd $EMULATOR_DIR -make build-emulator-image -cd - +# cd $EMULATOR_DIR +# make build-emulator-image +# cd - # run the Lua script that instantiates the cartesi module and # outputs the uarch constants values diff --git a/src/AccessLogs.sol b/src/AccessLogs.sol index 50d7de34..2abc6259 100644 --- a/src/AccessLogs.sol +++ b/src/AccessLogs.sol @@ -24,6 +24,7 @@ import "./UArchConstants.sol"; library AccessLogs { using Buffer for Buffer.Context; using Memory for Memory.AlignedSize; + using Memory for Memory.PhysicalAddress; struct Context { bytes32 currentRootHash; @@ -85,7 +86,7 @@ library AccessLogs { returns (bytes32) { Memory.Region memory r = - Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(2)); + Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(0)); return readRegion(a, r); } @@ -95,16 +96,13 @@ library AccessLogs { ) internal pure returns (uint64) { bytes32 readData = a.buffer.consumeBytes32(); bytes32 valHash = keccak256(abi.encodePacked(readData)); - Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap( - Memory.PhysicalAddress.unwrap(readAddress) & ~uint64(31) - ); - uint64 offset = Memory.PhysicalAddress.unwrap(readAddress) - - Memory.PhysicalAddress.unwrap(leafAddress); - bytes8 readValue = bytes8(readData << (offset << 3)); + (Memory.PhysicalAddress leafAddress, uint64 offset) = + readAddress.truncateToLeaf(); + bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); bytes32 expectedValHash = - readLeaf(a, Memory.strideFromWordAddress(leafAddress)); + readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); require(valHash == expectedValHash, "Read value doesn't match"); return machineWordToSolidityUint64(readValue); @@ -136,7 +134,7 @@ library AccessLogs { bytes32 newHash ) internal pure { Memory.Region memory r = - Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(2)); + Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(0)); writeRegion(a, r, newHash); } @@ -146,14 +144,12 @@ library AccessLogs { uint64 newValue ) internal pure { bytes32 writtenData = a.buffer.consumeBytes32(); - Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap( - Memory.PhysicalAddress.unwrap(writeAddress) & ~uint64(31) - ); - uint64 offset = Memory.PhysicalAddress.unwrap(writeAddress) - - Memory.PhysicalAddress.unwrap(leafAddress); - uint64 expectedNewValue = - machineWordToSolidityUint64(bytes8(writtenData << (offset << 3))); + (Memory.PhysicalAddress leafAddress, uint64 offset) = + writeAddress.truncateToLeaf(); + uint64 expectedNewValue = machineWordToSolidityUint64( + getBytes8FromBytes32AtOffset(writtenData, offset) + ); require( newValue == expectedNewValue, @@ -162,8 +158,16 @@ library AccessLogs { writeLeaf( a, - Memory.strideFromWordAddress(leafAddress), + Memory.strideFromLeafAddress(leafAddress), keccak256(abi.encodePacked(writtenData)) ); } + + function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) + internal + pure + returns (bytes8) + { + return bytes8(source << (offset << Memory.LOG2_WORD)); + } } diff --git a/src/Buffer.sol b/src/Buffer.sol index 9b212418..adeb73e8 100644 --- a/src/Buffer.sol +++ b/src/Buffer.sol @@ -81,10 +81,13 @@ library Buffer { ) internal pure returns (bytes32, uint8) { // require that multiplier makes sense! uint8 logOfSize = region.alignedSize.log2(); - require(logOfSize <= LOG2RANGE, "Cannot be bigger than the tree itself"); + require( + logOfSize <= Memory.LOG2_MAX_SIZE, + "Cannot be bigger than the tree itself" + ); uint64 stride = Memory.Stride.unwrap(region.stride); - uint8 nodesCount = LOG2RANGE - logOfSize; + uint8 nodesCount = Memory.LOG2_MAX_SIZE - logOfSize; for (uint64 i = 0; i < nodesCount; i++) { Buffer.Context memory siblings = @@ -113,8 +116,6 @@ library Buffer { return root; } - uint8 constant LOG2RANGE = 61; - function isEven(uint64 x) private pure returns (bool) { return x % 2 == 0; } diff --git a/src/Memory.sol b/src/Memory.sol index 1597ef62..903fa62e 100644 --- a/src/Memory.sol +++ b/src/Memory.sol @@ -18,8 +18,8 @@ pragma solidity ^0.8.0; library Memory { // Specifies a memory region and it's merkle hash. // The size is given in the number of leaves in the tree, - // and therefore are word-sized. - // This means a `alignedSize` specifies a region the size of a word. + // and therefore are leaf-sized. + // This means a `alignedSize` specifies a region the size of a leaf. // The address has to be aligned to a power-of-two. // By using an stride, we can guarantee that the address is aligned. // The address is given by `stride * (1 << log2s)`. @@ -45,7 +45,7 @@ library Memory { return regionFromStride(stride, alignedSize); } - function regionFromWordAddress(PhysicalAddress startAddress) + function regionFromLeafAddress(PhysicalAddress startAddress) internal pure returns (Region memory) @@ -53,7 +53,6 @@ library Memory { return regionFromPhysicalAddress(startAddress, alignedSizeFromLog2(0)); } - // // Stride and PhysicalAddress // // When using memory address and a size in merkle trees to refer to a memory region, @@ -74,23 +73,26 @@ library Memory { ) internal pure returns (Stride) { uint64 s = alignedSize.size(); uint64 addr = PhysicalAddress.unwrap(startAddress); - // assert memory address is word-aligned (8-byte long) - assert(addr & 7 == 0); - uint64 position = PhysicalAddress.unwrap(startAddress) >> 3; + + // assert memory address is leaf-aligned (32-byte long) + assert(addr & LEAF_MASK == 0); + uint64 position = PhysicalAddress.unwrap(startAddress) >> LOG2_LEAF; + // assert position and size are aligned // position has to be a multiple of size // equivalent to: size = 2^a, position = 2^b, position = size * 2^c, where c >= 0 assert(((s - 1) & position) == 0); uint64 stride = position / s; + return Stride.wrap(stride); } - function strideFromWordAddress(PhysicalAddress startAddress) + function strideFromLeafAddress(PhysicalAddress startAddress) internal pure returns (Stride) { - return strideFromPhysicalAddress(startAddress, alignedSizeFromLog2(2)); + return strideFromPhysicalAddress(startAddress, alignedSizeFromLog2(0)); } function validateStrideLength(Stride stride, AlignedSize alignedSize) @@ -101,7 +103,6 @@ library Memory { assert(Stride.unwrap(stride) * s < MAX_STRIDE); } - // // AlignedSize // // The size is given in the number of leaves in the tree, @@ -109,7 +110,11 @@ library Memory { type AlignedSize is uint8; - uint64 constant MAX_SIZE = (1 << 61); + uint8 constant LOG2_WORD = 3; + uint8 constant LOG2_LEAF = 5; + uint8 constant LOG2_MAX_SIZE = 64 - LOG2_LEAF; + uint64 constant LEAF_MASK = uint64(1 << LOG2_LEAF) - 1; + uint64 constant MAX_SIZE = uint64(1 << LOG2_MAX_SIZE); using Memory for AlignedSize; @@ -134,4 +139,24 @@ library Memory { { return PhysicalAddress.wrap(uint64Address); } + + function truncateToLeaf(PhysicalAddress addr) + internal + pure + returns (PhysicalAddress, uint64) + { + uint64 r = Memory.PhysicalAddress.unwrap(addr) & ~LEAF_MASK; + PhysicalAddress truncated = Memory.PhysicalAddress.wrap(r); + uint64 offset = minus(addr, truncated); + return (truncated, offset); + } + + function minus(PhysicalAddress lhs, PhysicalAddress rhs) + internal + pure + returns (uint64) + { + return Memory.PhysicalAddress.unwrap(lhs) + - Memory.PhysicalAddress.unwrap(rhs); + } } diff --git a/src/UArchCompat.sol b/src/UArchCompat.sol index a6c45479..94697042 100644 --- a/src/UArchCompat.sol +++ b/src/UArchCompat.sol @@ -104,7 +104,7 @@ library UArchCompat { Memory.regionFromPhysicalAddress( UArchConstants.RESET_POSITION.toPhysicalAddress(), Memory.alignedSizeFromLog2( - UArchConstants.RESET_ALIGNED_SIZE - 3 + UArchConstants.RESET_ALIGNED_SIZE - Memory.LOG2_LEAF ) ), UArchConstants.PRESTINE_STATE diff --git a/templates/AccessLogs.sol.template b/templates/AccessLogs.sol.template index dc2742df..ba0265e0 100644 --- a/templates/AccessLogs.sol.template +++ b/templates/AccessLogs.sol.template @@ -35,6 +35,7 @@ import "./UArchConstants.sol"; library AccessLogs { using Buffer for Buffer.Context; using Memory for Memory.AlignedSize; + using Memory for Memory.PhysicalAddress; struct Context { bytes32 currentRootHash; @@ -98,7 +99,7 @@ library AccessLogs { returns (bytes32) { Memory.Region memory r = - Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(2)); + Memory.regionFromStride(readStride, Memory.alignedSizeFromLog2(0)); return readRegion(a, r); } @@ -108,13 +109,12 @@ library AccessLogs { ) internal pure returns (uint64) { bytes32 readData = a.buffer.consumeBytes32(); bytes32 valHash = keccak256(abi.encodePacked(readData)); - Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap(Memory.PhysicalAddress.unwrap(readAddress) & ~uint64(31)); - uint64 offset = Memory.PhysicalAddress.unwrap(readAddress) - Memory.PhysicalAddress.unwrap(leafAddress); - - bytes8 readValue = bytes8(readData << (offset << 3)); + + (Memory.PhysicalAddress leafAddress, uint64 offset) = readAddress.truncateToLeaf(); + bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); bytes32 expectedValHash = - readLeaf(a, Memory.strideFromWordAddress(leafAddress)); + readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); require(valHash == expectedValHash, "Read value doesn't match"); return machineWordToSolidityUint64(readValue); @@ -146,7 +146,7 @@ library AccessLogs { bytes32 newHash ) internal pure { Memory.Region memory r = - Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(2)); + Memory.regionFromStride(writeStride, Memory.alignedSizeFromLog2(0)); writeRegion(a, r, newHash); } @@ -156,20 +156,29 @@ library AccessLogs { uint64 newValue ) internal pure { bytes32 writtenData = a.buffer.consumeBytes32(); - Memory.PhysicalAddress leafAddress = Memory.PhysicalAddress.wrap(Memory.PhysicalAddress.unwrap(writeAddress) & ~uint64(31)); - uint64 offset = Memory.PhysicalAddress.unwrap(writeAddress) - Memory.PhysicalAddress.unwrap(leafAddress); - - uint64 expectedNewValue = machineWordToSolidityUint64(bytes8(writtenData << (offset << 3))); + + (Memory.PhysicalAddress leafAddress, uint64 offset) = writeAddress.truncateToLeaf(); + uint64 expectedNewValue = + machineWordToSolidityUint64( + getBytes8FromBytes32AtOffset(writtenData, offset)); require(newValue == expectedNewValue, "Access log value does not contain the expected written value"); writeLeaf( a, - Memory.strideFromWordAddress(leafAddress), + Memory.strideFromLeafAddress(leafAddress), keccak256(abi.encodePacked(writtenData)) ); } + function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) + internal + pure + returns (bytes8) + { + return bytes8(source << (offset << Memory.LOG2_WORD)); + } + //:#else /// @dev This library mocks the `templates/AccessLogs.sol` yet with a very different implementation. diff --git a/test/AccessLogs.t.sol b/test/AccessLogs.t.sol index cd0c1e93..e3a73c88 100644 --- a/test/AccessLogs.t.sol +++ b/test/AccessLogs.t.sol @@ -147,8 +147,8 @@ contract AccessLogsTest is Test { { bytes32 b32 = makeLeaf(word, position); Buffer.Context memory buffer = - Buffer.Context(new bytes((59 << 5) + 32 + 32), 0); - buffer.writeBytes32(b32); // leaf containing the readd word + Buffer.Context(new bytes((59 << Memory.LOG2_LEAF) + 32 + 32), 0); + buffer.writeBytes32(b32); // leaf containing the read word for (uint256 i = 0; i < 60; i++) { buffer.writeBytes32(hashes[i]); @@ -170,7 +170,8 @@ contract AccessLogsTest is Test { } function rootFromHashes(bytes32 drive) private view returns (bytes32) { - Buffer.Context memory buffer = Buffer.Context(new bytes(59 << 5), 0); + Buffer.Context memory buffer = + Buffer.Context(new bytes(59 << Memory.LOG2_LEAF), 0); for (uint256 i = 0; i < 59; i++) { buffer.writeBytes32(hashes[i + 1]); @@ -180,8 +181,8 @@ contract AccessLogsTest is Test { buffer.offset = 0; (bytes32 root,) = buffer.peekRoot( Memory.regionFromStride( - Memory.strideFromWordAddress(position.toPhysicalAddress()), - Memory.alignedSizeFromLog2(2) + Memory.strideFromLeafAddress(position.toPhysicalAddress()), + Memory.alignedSizeFromLog2(0) ), drive ); diff --git a/test/Memory.t.sol b/test/Memory.t.sol index 8503c99c..360f58ec 100644 --- a/test/Memory.t.sol +++ b/test/Memory.t.sol @@ -28,7 +28,7 @@ contract MemoryTest is Test { function testStrideAlignment() public { for (uint128 paddr = 8; paddr <= (1 << 63); paddr *= 2) { - for (uint8 l = 0; ((1 << l) <= (paddr >> 3)); ++l) { + for (uint8 l = 0; ((1 << l) <= (paddr >> Memory.LOG2_LEAF)); ++l) { uint64(paddr).toPhysicalAddress().strideFromPhysicalAddress( Memory.alignedSizeFromLog2(l) ); @@ -39,7 +39,7 @@ contract MemoryTest is Test { Memory.alignedSizeFromLog2(l) ); - if ((1 << l) == (paddr >> 3)) { + if ((1 << l) == (paddr >> Memory.LOG2_LEAF)) { // address has to be aligned with stride size vm.expectRevert(); uint64(paddr + paddr / 2).toPhysicalAddress() From ef68cdd390f30569aad6be97493fe9765791c900 Mon Sep 17 00:00:00 2001 From: Marcos Pernambuco Motta <1091485+mpernambuco@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:44:59 -0300 Subject: [PATCH 3/6] feat: refactor AccessLogs.writeWord() --- Makefile | 2 +- helper_scripts/generate_UArchConstants.sh | 6 +- shasum-download | 4 +- src/AccessLogs.sol | 61 ++++-- src/UArchConstants.sol | 2 +- templates/AccessLogs.sol.template | 54 +++-- templates/UArchReplay.t.sol.template | 15 +- test/AccessLogs.t.sol | 234 ++++++++++++++-------- test/UArchReset.t.sol | 23 ++- 9 files changed, 252 insertions(+), 149 deletions(-) diff --git a/Makefile b/Makefile index ba62cf66..fb5c3303 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TEST_DIR := test DOWNLOADDIR := downloads SRC_DIR := src -EMULATOR_VERSION ?= v0.18.0-test4 +EMULATOR_VERSION ?= v0.18.0-test5 TESTS_DATA_FILE ?= cartesi-machine-tests-data-$(EMULATOR_VERSION).deb TESTS_DATA_DOWNLOAD_URL := https://github.com/cartesi/machine-emulator/releases/download/$(EMULATOR_VERSION)/$(TESTS_DATA_FILE) diff --git a/helper_scripts/generate_UArchConstants.sh b/helper_scripts/generate_UArchConstants.sh index 83d482eb..74a059cb 100755 --- a/helper_scripts/generate_UArchConstants.sh +++ b/helper_scripts/generate_UArchConstants.sh @@ -18,9 +18,9 @@ let last=total-end+1 h=`head -n $start $TEMPLATE_FILE` t=`tail -n -$last $TEMPLATE_FILE` -# cd $EMULATOR_DIR -# make build-emulator-image -# cd - +cd $EMULATOR_DIR +make build-emulator-image +cd - # run the Lua script that instantiates the cartesi module and # outputs the uarch constants values diff --git a/shasum-download b/shasum-download index ce921cff..9fb5a693 100644 --- a/shasum-download +++ b/shasum-download @@ -1,2 +1,2 @@ -6024a9c7ebf6d10d3721753401c0edd7c3f1392cd26f1cc549fc58d15647a04a downloads/cartesi-machine-tests-data-v0.18.0-test4.deb -994af6de340e7efaf024545ddc44e850761115a9be98b82eeec441c869151525 downloads/uarch-riscv-tests-json-logs-v0.18.0-test4.tar.gz +e2dcd5598a67844d6a1199189089f2cc9ca306fcb80c45a634376861c89e61de downloads/cartesi-machine-tests-data-v0.18.0-test5.deb +d167c42c61a1753190b28461fbe018d4620a2379afc982ed468e3422dd27c8ba downloads/uarch-riscv-tests-json-logs-v0.18.0-test5.tar.gz diff --git a/src/AccessLogs.sol b/src/AccessLogs.sol index 2abc6259..3461b632 100644 --- a/src/AccessLogs.sol +++ b/src/AccessLogs.sol @@ -62,8 +62,9 @@ library AccessLogs { return end2; } - /// @dev bytes buffer layout is the same for `readWord` and `writeWord`, - /// [32 bytes as read data], [59 * 32 bytes as sibling hashes] + /// @dev bytes buffer layout is the different for `readWord` and `writeWord`, + /// readWord: [32 bytes as read data], [59 * 32 bytes as sibling hashes] + /// writeWord [32 bytes as written hash] [32 bytes as read data], [59 * 32 bytes as sibling hashes] // // Read methods @@ -95,17 +96,18 @@ library AccessLogs { Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { bytes32 readData = a.buffer.consumeBytes32(); - bytes32 valHash = keccak256(abi.encodePacked(readData)); - - (Memory.PhysicalAddress leafAddress, uint64 offset) = + bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = readAddress.truncateToLeaf(); - bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); + bytes8 word = getBytes8FromBytes32AtOffset(readData, wordOffset); - bytes32 expectedValHash = + bytes32 expectedReadHash = readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); - require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(readValue); + require( + computedReadHash == expectedReadHash, "Read value doesn't match" + ); + return machineWordToSolidityUint64(word); } // @@ -143,24 +145,28 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - bytes32 writtenData = a.buffer.consumeBytes32(); - - (Memory.PhysicalAddress leafAddress, uint64 offset) = + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = writeAddress.truncateToLeaf(); - uint64 expectedNewValue = machineWordToSolidityUint64( - getBytes8FromBytes32AtOffset(writtenData, offset) - ); + bytes32 writtenHash = a.buffer.consumeBytes32(); + bytes32 readData = a.buffer.consumeBytes32(); + // check if read data hashes to the same read hash that is next in the buffer + bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); + bytes32 loggedReadHash = a.buffer.peekBytes32(); require( - newValue == expectedNewValue, - "Access log value does not contain the expected written value" + computedReadHash == loggedReadHash, + "logged and computed read hashes mismatch" ); - writeLeaf( - a, - Memory.strideFromLeafAddress(leafAddress), - keccak256(abi.encodePacked(writtenData)) + // construct the written data by patching the verified read data + bytes32 computedWrittenData = setBytes8InBytes32AtOffset( + readData, wordOffset, solidityUint64ToMachineWord(newValue) ); + bytes32 computedWrittenHash = + keccak256(abi.encodePacked(computedWrittenData)); + require(computedWrittenHash == writtenHash, "Written hash mismatch"); + + writeLeaf(a, Memory.strideFromLeafAddress(leafAddress), writtenHash); } function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) @@ -170,4 +176,17 @@ library AccessLogs { { return bytes8(source << (offset << Memory.LOG2_WORD)); } + + function setBytes8InBytes32AtOffset( + bytes32 target, + uint64 offset, + bytes8 source + ) internal pure returns (bytes32) { + bytes32 erase_mask = bytes32(bytes8(type(uint64).max)); + erase_mask = erase_mask >> (offset << Memory.LOG2_WORD); + erase_mask = ~erase_mask; + bytes32 result = target & erase_mask; + result = result | (bytes32(source) >> (offset << Memory.LOG2_WORD)); + return result; + } } diff --git a/src/UArchConstants.sol b/src/UArchConstants.sol index 0fc2a89a..a4393048 100644 --- a/src/UArchConstants.sol +++ b/src/UArchConstants.sol @@ -35,7 +35,7 @@ library UArchConstants { uint64 constant RESET_POSITION = 0x400000; uint8 constant RESET_ALIGNED_SIZE = 22; bytes32 constant PRESTINE_STATE = - 0xb0a850996464081741f4eefe12160dd2511514b3d606ec5b9c1b183fbe5a248f; + 0x4de6115bdadc23724cf20c5580d718525ce81b294c8c149d3658020c380df109; uint64 constant UARCH_ECALL_FN_HALT = 1; uint64 constant UARCH_ECALL_FN_PUTCHAR = 2; // END OF AUTO-GENERATED CODE diff --git a/templates/AccessLogs.sol.template b/templates/AccessLogs.sol.template index ba0265e0..82da0a46 100644 --- a/templates/AccessLogs.sol.template +++ b/templates/AccessLogs.sol.template @@ -75,8 +75,9 @@ library AccessLogs { //:#ifndef test - /// @dev bytes buffer layout is the same for `readWord` and `writeWord`, - /// [32 bytes as read data], [59 * 32 bytes as sibling hashes] + /// @dev bytes buffer layout is the different for `readWord` and `writeWord`, + /// readWord: [32 bytes as read data], [59 * 32 bytes as sibling hashes] + /// writeWord [32 bytes as written hash] [32 bytes as read data], [59 * 32 bytes as sibling hashes] // // Read methods @@ -108,16 +109,15 @@ library AccessLogs { Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { bytes32 readData = a.buffer.consumeBytes32(); - bytes32 valHash = keccak256(abi.encodePacked(readData)); + bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = readAddress.truncateToLeaf(); + bytes8 word = getBytes8FromBytes32AtOffset(readData, wordOffset); - (Memory.PhysicalAddress leafAddress, uint64 offset) = readAddress.truncateToLeaf(); - bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); - - bytes32 expectedValHash = + bytes32 expectedReadHash = readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); - require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(readValue); + require(computedReadHash == expectedReadHash, "Read value doesn't match"); + return machineWordToSolidityUint64(word); } // @@ -155,20 +155,21 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - bytes32 writtenData = a.buffer.consumeBytes32(); + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = writeAddress.truncateToLeaf(); + bytes32 writtenHash = a.buffer.consumeBytes32(); + bytes32 readData = a.buffer.consumeBytes32(); - (Memory.PhysicalAddress leafAddress, uint64 offset) = writeAddress.truncateToLeaf(); - uint64 expectedNewValue = - machineWordToSolidityUint64( - getBytes8FromBytes32AtOffset(writtenData, offset)); + // check if read data hashes to the same read hash that is next in the buffer + bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); + bytes32 loggedReadHash = a.buffer.peekBytes32(); + require(computedReadHash ==loggedReadHash, "logged and computed read hashes mismatch"); - require(newValue == expectedNewValue, "Access log value does not contain the expected written value"); + // construct the written data by patching the verified read data + bytes32 computedWrittenData = setBytes8InBytes32AtOffset(readData, wordOffset, solidityUint64ToMachineWord(newValue)); + bytes32 computedWrittenHash = keccak256(abi.encodePacked(computedWrittenData)); + require(computedWrittenHash == writtenHash, "Written hash mismatch"); - writeLeaf( - a, - Memory.strideFromLeafAddress(leafAddress), - keccak256(abi.encodePacked(writtenData)) - ); + writeLeaf(a, Memory.strideFromLeafAddress(leafAddress), writtenHash); } function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) @@ -178,6 +179,19 @@ library AccessLogs { { return bytes8(source << (offset << Memory.LOG2_WORD)); } + + function setBytes8InBytes32AtOffset(bytes32 target, uint64 offset, bytes8 source) + internal + pure + returns (bytes32) + { + bytes32 erase_mask = bytes32(bytes8(type(uint64).max)); + erase_mask = erase_mask >> (offset << Memory.LOG2_WORD); + erase_mask = ~erase_mask; + bytes32 result = target & erase_mask; + result = result | (bytes32(source) >> (offset << Memory.LOG2_WORD)); + return result; + } //:#else diff --git a/templates/UArchReplay.t.sol.template b/templates/UArchReplay.t.sol.template index 659b04c5..ee82376b 100644 --- a/templates/UArchReplay.t.sol.template +++ b/templates/UArchReplay.t.sol.template @@ -47,12 +47,12 @@ contract UArchReplay_@X@_Test is Test { struct RawAccess { uint256 addressAccess; - string hash; uint256 log2_size; string read_hash; + string read_value; string[] sibling_hashes; string typeAccess; - string value; + string written_hash; } function testReplay_@X@() public { @@ -91,7 +91,6 @@ contract UArchReplay_@X@_Test is Test { AccessLogs.Context memory accessLogs = AccessLogs.Context( initialRootHash, Buffer.Context(buffer, 0) ); - // initialRootHash is passed and will be updated through out the step UArchStep.step(accessLogs); initialRootHash = accessLogs.currentRootHash; @@ -138,8 +137,16 @@ contract UArchReplay_@X@_Test is Test { Buffer.Context memory buffer = Buffer.Context(data, 0); for (uint256 i = 0; i < arrayLength; i++) { + if ( + keccak256(abi.encodePacked(rawAccesses[i].typeAccess)) + == keccak256(abi.encodePacked("write")) + ) { + buffer.writeBytes32( + vm.parseBytes32(string.concat("0x", rawAccesses[i].written_hash)) + ); + } bytes32 word = bytes32( - vm.parseBytes32(string.concat("0x", rawAccesses[i].value)) + vm.parseBytes32(string.concat("0x", rawAccesses[i].read_value)) ); buffer.writeBytes32(word); diff --git a/test/AccessLogs.t.sol b/test/AccessLogs.t.sol index e3a73c88..30d9691a 100644 --- a/test/AccessLogs.t.sol +++ b/test/AccessLogs.t.sol @@ -28,90 +28,114 @@ contract AccessLogsTest is Test { using BufferAux for Buffer.Context; using Memory for uint64; - bytes32[] hashes; - bytes32 rootHash; - uint64 position = 800; + uint64 position = 800; // position of the word being tested + bytes8 initialWordAtPosition; // bytes of the word at position + bytes32 initialReadLeaf; // leaf containing the word at position + bytes32[] siblingHashes; // siblings of the leaf containing position function setUp() public { - // the hashes include 60 elements, the hash at access position, and other 59 siblings - // hash value at access position - hashes.push( - keccak256( - abi.encodePacked( - makeLeaf(bytes8(0x0000000000000001).swapEndian(), position) - ) - ) + initialWordAtPosition = bytes8(0x0000000000000001).swapEndian(); + initialReadLeaf = patchLeaf( + bytes32(type(uint256).max), initialWordAtPosition, position ); - - // direct sibling hash - hashes.push( - keccak256(abi.encodePacked(bytes8(0x0000000000000002).swapEndian())) - ); - for (uint256 i = 2; i < 60; i++) { - hashes.push( - keccak256(abi.encodePacked(hashes[i - 1], hashes[i - 2])) - ); + for (uint256 i = 0; i < 59; i++) { + siblingHashes.push(keccak256(abi.encodePacked(bytes8(uint64(i))))); } - rootHash = rootFromHashes(hashes[0]); } - function testReadWord() public { - AccessLogs.Context memory accessLogs = AccessLogs.Context( - rootHash, - readBufferFromHashes(bytes8(0x0000000000000001).swapEndian()) + function testReadWordHappyPath() public { + (Buffer.Context memory buffer, bytes32 rootHash) = makeReadBuffer( + bytes8(0x0000000000000001).swapEndian(), + /*withReadValueMismatch=*/ + false ); - + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, buffer); assertEq(accessLogs.readWord(position.toPhysicalAddress()), 1); } - function testReadWordRoot() public { - AccessLogs.Context memory accessLogs = AccessLogs.Context( - rootHash, - readBufferFromHashes(bytes8(0x0000000000000001).swapEndian()) + function testReadWordBadRegion() public { + (Buffer.Context memory buffer, bytes32 rootHash) = makeReadBuffer( + bytes8(0x0000000000000001).swapEndian(), + /*withReadValueMismatch=*/ + false ); - + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, buffer); vm.expectRevert("Read region root doesn't match"); accessLogs.readWord((position + 32).toPhysicalAddress()); } - function testReadWordValue() public { - AccessLogs.Context memory accessLogs = AccessLogs.Context( - rootHash, - readBufferFromHashes(bytes8(0x0000000000000002).swapEndian()) + function testReadWordWrongValue() public { + (Buffer.Context memory buffer, bytes32 rootHash) = makeReadBuffer( + bytes8(0x0000000000000001).swapEndian(), + /*withReadValueMismatch=*/ + true ); + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, buffer); vm.expectRevert("Read value doesn't match"); accessLogs.readWord(position.toPhysicalAddress()); } - function testWriteWord() public view { - uint64 valueWritten = 1; + function testWriteWordHappyPath() public view { + uint64 wordWritten = 1; + (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( + initialReadLeaf, + bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ + false, + /* withWrittenValueMismatch= */ + false + ); AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes(valueWritten)); - - // // write should succeed - accessLogs.writeWord(position.toPhysicalAddress(), valueWritten); + AccessLogs.Context(rootHash, buffer); + accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); } - function testWriteWordRootValue() public { - hashes[0] = ( - keccak256(abi.encodePacked(bytes8(0x0000000000000002).swapEndian())) + function testWriteWordBadRegion() public { + uint64 wordWritten = 1; + (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( + initialReadLeaf, + bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ + false, + /* withWrittenValueMismatch= */ + false ); - uint64 valueWritten = 1; AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes(valueWritten)); - + AccessLogs.Context(rootHash, buffer); vm.expectRevert("Write region root doesn't match"); - accessLogs.writeWord(position.toPhysicalAddress(), valueWritten); + accessLogs.writeWord((position + 32).toPhysicalAddress(), wordWritten); } - function testWriteWordRootPosition() public { - uint64 valueWritten = 1; + function testWriteWordReadMismatch() public { + uint64 wordWritten = 1; + (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( + initialReadLeaf, + bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ + true, + /* withWrittenValueMismatch= */ + false + ); AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes(valueWritten)); + AccessLogs.Context(rootHash, buffer); + vm.expectRevert("logged and computed read hashes mismatch"); + accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); + } - vm.expectRevert("Write region root doesn't match"); - accessLogs.writeWord((position + 32).toPhysicalAddress(), valueWritten); + function testWriteWordWriteMismatch() public { + uint64 wordWritten = 1; + (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( + initialReadLeaf, + bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ + false, + /* withWrittenValueMismatch= */ + true + ); + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, buffer); + vm.expectRevert("Written hash mismatch"); + accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); } function testEndianSwap() public { @@ -136,57 +160,95 @@ contract AccessLogsTest is Test { { uint64 leafPosition = wordPosition & ~uint64(31); uint64 offset = position - leafPosition; - bytes32 b32 = bytes32(word) << (offset << 3); - return b32; + bytes32 leaf = bytes32(word) >> (offset << Memory.LOG2_WORD); + return leaf; } - function readBufferFromHashes(bytes8 word) - private + function patchLeaf(bytes32 currentLeaf, bytes8 newWord, uint64 wordPosition) + public view - returns (Buffer.Context memory) + returns (bytes32) { - bytes32 b32 = makeLeaf(word, position); - Buffer.Context memory buffer = - Buffer.Context(new bytes((59 << Memory.LOG2_LEAF) + 32 + 32), 0); - buffer.writeBytes32(b32); // leaf containing the read word - - for (uint256 i = 0; i < 60; i++) { - buffer.writeBytes32(hashes[i]); - } + uint64 leafPosition = wordPosition & ~uint64(31); + uint64 offset = position - leafPosition; - // reset offset for replay - buffer.offset = 0; + bytes32 erase_mask = bytes32(bytes8(type(uint64).max)); + erase_mask = erase_mask >> (offset << Memory.LOG2_WORD); + erase_mask = ~erase_mask; - return buffer; + bytes32 result = currentLeaf & erase_mask; + result = result | (bytes32(newWord) >> (offset << Memory.LOG2_WORD)); + return result; } - function writeBufferFromHashes(uint64 valueWritten) + function makeReadBuffer(bytes8 readWord, bool withReadValueMismatch) private view - returns (Buffer.Context memory) + returns (Buffer.Context memory, bytes32) { - bytes8 b8 = bytes8(valueWritten); - return readBufferFromHashes(b8.swapEndian()); + Buffer.Context memory buffer = + Buffer.Context(new bytes((59 << Memory.LOG2_LEAF) + 32 + 32), 0); + bytes32 readData = patchLeaf(initialReadLeaf, readWord, position); + + // write leaf data, leaf hash and sibling hashes + buffer.writeBytes32(readData); + bytes32 readHash = keccak256(abi.encodePacked(readData)); + if (withReadValueMismatch) { + readHash = keccak256(abi.encodePacked(bytes8(readHash))); + } + buffer.writeBytes32(readHash); + for (uint256 i = 0; i < 59; i++) { + buffer.writeBytes32(siblingHashes[i]); + } + // compute root hash and rewind buffer + buffer.offset = 0; + bytes32 rootHash = bubbleHashUp(readHash); + return (buffer, rootHash); } - function rootFromHashes(bytes32 drive) private view returns (bytes32) { - Buffer.Context memory buffer = - Buffer.Context(new bytes(59 << Memory.LOG2_LEAF), 0); + function makeWriteBuffer( + bytes32 readLeaf, + bytes8 writtenWord, + bool withReadValueMismatch, + bool withWrittenValueMismatch + ) private view returns (Buffer.Context memory, bytes32) { + Buffer.Context memory buffer = Buffer.Context( + new bytes((59 << Memory.LOG2_LEAF) + 32 + 32 + 32), 0 + ); + bytes32 writtenLeaf = patchLeaf(readLeaf, writtenWord, position); + bytes32 writtenHash = keccak256(abi.encodePacked(writtenLeaf)); + if (withWrittenValueMismatch) { + writtenHash = keccak256(abi.encodePacked(bytes8(writtenHash))); + } + buffer.writeBytes32(writtenHash); + // 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))); + } + buffer.writeBytes32(readHash); for (uint256 i = 0; i < 59; i++) { - buffer.writeBytes32(hashes[i + 1]); + buffer.writeBytes32(siblingHashes[i]); } - // reset offset for replay + // compute root hash and rewind buffer + bytes32 rootHash = bubbleHashUp(readHash); buffer.offset = 0; - (bytes32 root,) = buffer.peekRoot( - Memory.regionFromStride( - Memory.strideFromLeafAddress(position.toPhysicalAddress()), - Memory.alignedSizeFromLog2(0) - ), - drive - ); + return (buffer, rootHash); + } - return root; + function bubbleHashUp(bytes32 hash) private view returns (bytes32) { + uint64 addr = position >> Memory.LOG2_LEAF; + for (uint256 i = 0; i < 59; i++) { + if (addr & 1 == 0) { + hash = keccak256(abi.encodePacked(hash, siblingHashes[i])); + } else { + hash = keccak256(abi.encodePacked(siblingHashes[i], hash)); + } + addr = addr >> 1; + } + return hash; } } diff --git a/test/UArchReset.t.sol b/test/UArchReset.t.sol index 555f4a2c..1e792f15 100644 --- a/test/UArchReset.t.sol +++ b/test/UArchReset.t.sol @@ -48,12 +48,13 @@ contract UArchReset_Test is Test { } struct RawAccess { - uint256 position; - string hash; - uint256 log2Size; - string readHash; - string[] rawSiblings; - string accessType; + uint256 addressAccess; + uint256 log2_size; + string read_hash; + string read_value; + string[] sibling_hashes; + string typeAccess; + string written_hash; } // string val; omit val because it's not used in reset @@ -137,30 +138,30 @@ contract UArchReset_Test is Test { Buffer.Context memory buffer = Buffer.Context(data, 0); if ( - keccak256(abi.encodePacked(rawAccesses[0].accessType)) + keccak256(abi.encodePacked(rawAccesses[0].typeAccess)) == keccak256(abi.encodePacked("read")) ) { revert("should'nt have read access in reset"); } assertEq( - rawAccesses[0].position, + rawAccesses[0].addressAccess, UArchConstants.RESET_POSITION, "position should be (0x400000)" ); assertEq( - rawAccesses[0].log2Size, + rawAccesses[0].log2_size, UArchConstants.RESET_ALIGNED_SIZE, "log2Size should be 22" ); buffer.writeBytes32( - vm.parseBytes32(string.concat("0x", rawAccesses[0].readHash)) + vm.parseBytes32(string.concat("0x", rawAccesses[0].read_hash)) ); for (uint256 i = 0; i < siblingsLength; i++) { buffer.writeBytes32( vm.parseBytes32( - string.concat("0x", rawAccesses[0].rawSiblings[i]) + string.concat("0x", rawAccesses[0].sibling_hashes[i]) ) ); } From 1285a455f9a3a5b926d4df93b058a292983e9cda Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Sat, 3 Aug 2024 09:45:31 -0300 Subject: [PATCH 4/6] fix: fix memory write --- src/AccessLogs.sol | 72 +++++++++++++++---------------- templates/AccessLogs.sol.template | 66 +++++++++++++++------------- 2 files changed, 72 insertions(+), 66 deletions(-) diff --git a/src/AccessLogs.sol b/src/AccessLogs.sol index 3461b632..f8457fa0 100644 --- a/src/AccessLogs.sol +++ b/src/AccessLogs.sol @@ -62,9 +62,8 @@ library AccessLogs { return end2; } - /// @dev bytes buffer layout is the different for `readWord` and `writeWord`, - /// readWord: [32 bytes as read data], [59 * 32 bytes as sibling hashes] - /// writeWord [32 bytes as written hash] [32 bytes as read data], [59 * 32 bytes as sibling hashes] + /// @dev bytes buffer layout is the same for `readWord` and `writeWord`, + /// [32 bytes as read data], [59 * 32 bytes as sibling hashes] // // Read methods @@ -96,18 +95,17 @@ library AccessLogs { Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { bytes32 readData = a.buffer.consumeBytes32(); - bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); - (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = + bytes32 valHash = keccak256(abi.encodePacked(readData)); + + (Memory.PhysicalAddress leafAddress, uint64 offset) = readAddress.truncateToLeaf(); - bytes8 word = getBytes8FromBytes32AtOffset(readData, wordOffset); + bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); - bytes32 expectedReadHash = + bytes32 expectedValHash = readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); - require( - computedReadHash == expectedReadHash, "Read value doesn't match" - ); - return machineWordToSolidityUint64(word); + require(valHash == expectedValHash, "Read value doesn't match"); + return machineWordToSolidityUint64(readValue); } // @@ -145,28 +143,27 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = + (Memory.PhysicalAddress leafAddress, uint64 offset) = writeAddress.truncateToLeaf(); - bytes32 writtenHash = a.buffer.consumeBytes32(); - bytes32 readData = a.buffer.consumeBytes32(); - // check if read data hashes to the same read hash that is next in the buffer - bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); - bytes32 loggedReadHash = a.buffer.peekBytes32(); - require( - computedReadHash == loggedReadHash, - "logged and computed read hashes mismatch" + Memory.Region memory region = Memory.regionFromStride( + Memory.strideFromLeafAddress(leafAddress), + Memory.alignedSizeFromLog2(0) ); - // construct the written data by patching the verified read data - bytes32 computedWrittenData = setBytes8InBytes32AtOffset( - readData, wordOffset, solidityUint64ToMachineWord(newValue) + bytes32 oldLeaf = a.buffer.consumeBytes32(); + (bytes32 rootHash,) = + a.buffer.peekRoot(region, keccak256(abi.encodePacked(oldLeaf))); + + require(a.currentRootHash == rootHash, "Write word root doesn't match"); + + bytes32 newLeaf = setBytes8ToBytes32AtOffset( + solidityUint64ToMachineWord(newValue), oldLeaf, offset ); - bytes32 computedWrittenHash = - keccak256(abi.encodePacked(computedWrittenData)); - require(computedWrittenHash == writtenHash, "Written hash mismatch"); - writeLeaf(a, Memory.strideFromLeafAddress(leafAddress), writtenHash); + bytes32 newRootHash = + a.buffer.getRoot(region, keccak256(abi.encodePacked(newLeaf))); + a.currentRootHash = newRootHash; } function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) @@ -177,16 +174,17 @@ library AccessLogs { return bytes8(source << (offset << Memory.LOG2_WORD)); } - function setBytes8InBytes32AtOffset( - bytes32 target, - uint64 offset, - bytes8 source + function setBytes8ToBytes32AtOffset( + bytes8 word, + bytes32 leaf, + uint64 offset ) internal pure returns (bytes32) { - bytes32 erase_mask = bytes32(bytes8(type(uint64).max)); - erase_mask = erase_mask >> (offset << Memory.LOG2_WORD); - erase_mask = ~erase_mask; - bytes32 result = target & erase_mask; - result = result | (bytes32(source) >> (offset << Memory.LOG2_WORD)); - return result; + uint256 wordOffset = offset << Memory.LOG2_WORD; + bytes32 toWrite = bytes32(word) >> wordOffset; + + bytes32 wordMask = bytes32(~bytes8(0)); + bytes32 mask = ~(wordMask >> wordOffset); + + return (leaf & mask) | toWrite; } } diff --git a/templates/AccessLogs.sol.template b/templates/AccessLogs.sol.template index 82da0a46..e95f310b 100644 --- a/templates/AccessLogs.sol.template +++ b/templates/AccessLogs.sol.template @@ -75,9 +75,8 @@ library AccessLogs { //:#ifndef test - /// @dev bytes buffer layout is the different for `readWord` and `writeWord`, - /// readWord: [32 bytes as read data], [59 * 32 bytes as sibling hashes] - /// writeWord [32 bytes as written hash] [32 bytes as read data], [59 * 32 bytes as sibling hashes] + /// @dev bytes buffer layout is the same for `readWord` and `writeWord`, + /// [32 bytes as read data], [59 * 32 bytes as sibling hashes] // // Read methods @@ -109,15 +108,16 @@ library AccessLogs { Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { bytes32 readData = a.buffer.consumeBytes32(); - bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); - (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = readAddress.truncateToLeaf(); - bytes8 word = getBytes8FromBytes32AtOffset(readData, wordOffset); + bytes32 valHash = keccak256(abi.encodePacked(readData)); - bytes32 expectedReadHash = + (Memory.PhysicalAddress leafAddress, uint64 offset) = readAddress.truncateToLeaf(); + bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); + + bytes32 expectedValHash = readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); - require(computedReadHash == expectedReadHash, "Read value doesn't match"); - return machineWordToSolidityUint64(word); + require(valHash == expectedValHash, "Read value doesn't match"); + return machineWordToSolidityUint64(readValue); } // @@ -155,21 +155,27 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = writeAddress.truncateToLeaf(); - bytes32 writtenHash = a.buffer.consumeBytes32(); - bytes32 readData = a.buffer.consumeBytes32(); + (Memory.PhysicalAddress leafAddress, uint64 offset) = + writeAddress.truncateToLeaf(); - // check if read data hashes to the same read hash that is next in the buffer - bytes32 computedReadHash = keccak256(abi.encodePacked(readData)); - bytes32 loggedReadHash = a.buffer.peekBytes32(); - require(computedReadHash ==loggedReadHash, "logged and computed read hashes mismatch"); + Memory.Region memory region = Memory.regionFromStride( + Memory.strideFromLeafAddress(leafAddress), + Memory.alignedSizeFromLog2(0) + ); - // construct the written data by patching the verified read data - bytes32 computedWrittenData = setBytes8InBytes32AtOffset(readData, wordOffset, solidityUint64ToMachineWord(newValue)); - bytes32 computedWrittenHash = keccak256(abi.encodePacked(computedWrittenData)); - require(computedWrittenHash == writtenHash, "Written hash mismatch"); + bytes32 oldLeaf = a.buffer.consumeBytes32(); + (bytes32 rootHash,) = a.buffer.peekRoot(region, keccak256(abi.encodePacked(oldLeaf))); - writeLeaf(a, Memory.strideFromLeafAddress(leafAddress), writtenHash); + require( + a.currentRootHash == rootHash, "Write word root doesn't match" + ); + + bytes32 newLeaf = setBytes8ToBytes32AtOffset( + solidityUint64ToMachineWord(newValue), oldLeaf, offset + ); + + bytes32 newRootHash = a.buffer.getRoot(region, keccak256(abi.encodePacked(newLeaf))); + a.currentRootHash = newRootHash; } function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) @@ -179,20 +185,22 @@ library AccessLogs { { return bytes8(source << (offset << Memory.LOG2_WORD)); } - - function setBytes8InBytes32AtOffset(bytes32 target, uint64 offset, bytes8 source) + + function setBytes8ToBytes32AtOffset(bytes8 word, bytes32 leaf, uint64 offset) internal pure returns (bytes32) { - bytes32 erase_mask = bytes32(bytes8(type(uint64).max)); - erase_mask = erase_mask >> (offset << Memory.LOG2_WORD); - erase_mask = ~erase_mask; - bytes32 result = target & erase_mask; - result = result | (bytes32(source) >> (offset << Memory.LOG2_WORD)); - return result; + uint256 wordOffset = offset << Memory.LOG2_WORD; + bytes32 toWrite = bytes32(word) >> wordOffset; + + bytes32 wordMask = bytes32(~bytes8(0)); + bytes32 mask = ~(wordMask >> wordOffset); + + return (leaf & mask) | toWrite; } + //:#else /// @dev This library mocks the `templates/AccessLogs.sol` yet with a very different implementation. From 34741cf333f65789e4d303f1a2e7b8516c4c0d0d Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula Date: Mon, 5 Aug 2024 10:31:17 -0300 Subject: [PATCH 5/6] fix: change access logs --- src/AccessLogs.sol | 25 +++++---- templates/AccessLogs.sol.template | 25 +++++---- templates/UArchReplay.t.sol.template | 18 +++---- test/AccessLogs.t.sol | 76 +++++++++++----------------- 4 files changed, 65 insertions(+), 79 deletions(-) diff --git a/src/AccessLogs.sol b/src/AccessLogs.sol index f8457fa0..131ab2c3 100644 --- a/src/AccessLogs.sol +++ b/src/AccessLogs.sol @@ -94,18 +94,21 @@ library AccessLogs { AccessLogs.Context memory a, Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { - bytes32 readData = a.buffer.consumeBytes32(); - bytes32 valHash = keccak256(abi.encodePacked(readData)); - - (Memory.PhysicalAddress leafAddress, uint64 offset) = + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = readAddress.truncateToLeaf(); - bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); - bytes32 expectedValHash = - readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); + Memory.Region memory region = Memory.regionFromStride( + Memory.strideFromLeafAddress(leafAddress), + Memory.alignedSizeFromLog2(0) + ); + + bytes32 leaf = a.buffer.consumeBytes32(); + bytes32 rootHash = + a.buffer.getRoot(region, keccak256(abi.encodePacked(leaf))); + require(a.currentRootHash == rootHash, "Read word root doesn't match"); - require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(readValue); + bytes8 word = getBytes8FromBytes32AtOffset(leaf, wordOffset); + return machineWordToSolidityUint64(word); } // @@ -143,7 +146,7 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - (Memory.PhysicalAddress leafAddress, uint64 offset) = + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = writeAddress.truncateToLeaf(); Memory.Region memory region = Memory.regionFromStride( @@ -158,7 +161,7 @@ library AccessLogs { require(a.currentRootHash == rootHash, "Write word root doesn't match"); bytes32 newLeaf = setBytes8ToBytes32AtOffset( - solidityUint64ToMachineWord(newValue), oldLeaf, offset + solidityUint64ToMachineWord(newValue), oldLeaf, wordOffset ); bytes32 newRootHash = diff --git a/templates/AccessLogs.sol.template b/templates/AccessLogs.sol.template index e95f310b..b25a9dc3 100644 --- a/templates/AccessLogs.sol.template +++ b/templates/AccessLogs.sol.template @@ -107,17 +107,22 @@ library AccessLogs { AccessLogs.Context memory a, Memory.PhysicalAddress readAddress ) internal pure returns (uint64) { - bytes32 readData = a.buffer.consumeBytes32(); - bytes32 valHash = keccak256(abi.encodePacked(readData)); + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = + readAddress.truncateToLeaf(); - (Memory.PhysicalAddress leafAddress, uint64 offset) = readAddress.truncateToLeaf(); - bytes8 readValue = getBytes8FromBytes32AtOffset(readData, offset); + Memory.Region memory region = Memory.regionFromStride( + Memory.strideFromLeafAddress(leafAddress), + Memory.alignedSizeFromLog2(0) + ); - bytes32 expectedValHash = - readLeaf(a, Memory.strideFromLeafAddress(leafAddress)); + bytes32 leaf = a.buffer.consumeBytes32(); + bytes32 rootHash = a.buffer.getRoot(region, keccak256(abi.encodePacked(leaf))); + require( + a.currentRootHash == rootHash, "Read word root doesn't match" + ); - require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(readValue); + bytes8 word = getBytes8FromBytes32AtOffset(leaf, wordOffset); + return machineWordToSolidityUint64(word); } // @@ -155,7 +160,7 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - (Memory.PhysicalAddress leafAddress, uint64 offset) = + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = writeAddress.truncateToLeaf(); Memory.Region memory region = Memory.regionFromStride( @@ -171,7 +176,7 @@ library AccessLogs { ); bytes32 newLeaf = setBytes8ToBytes32AtOffset( - solidityUint64ToMachineWord(newValue), oldLeaf, offset + solidityUint64ToMachineWord(newValue), oldLeaf, wordOffset ); bytes32 newRootHash = a.buffer.getRoot(region, keccak256(abi.encodePacked(newLeaf))); diff --git a/templates/UArchReplay.t.sol.template b/templates/UArchReplay.t.sol.template index ee82376b..6bc22a9e 100644 --- a/templates/UArchReplay.t.sol.template +++ b/templates/UArchReplay.t.sol.template @@ -137,22 +137,16 @@ contract UArchReplay_@X@_Test is Test { Buffer.Context memory buffer = Buffer.Context(data, 0); for (uint256 i = 0; i < arrayLength; i++) { - if ( - keccak256(abi.encodePacked(rawAccesses[i].typeAccess)) - == keccak256(abi.encodePacked("write")) - ) { + if (rawAccesses[i].log2_size == 3) { + buffer.writeBytes32( + vm.parseBytes32(string.concat("0x", rawAccesses[i].read_value)) + ); + } else { buffer.writeBytes32( - vm.parseBytes32(string.concat("0x", rawAccesses[i].written_hash)) + vm.parseBytes32(string.concat("0x", rawAccesses[i].read_hash)) ); } - bytes32 word = bytes32( - vm.parseBytes32(string.concat("0x", rawAccesses[i].read_value)) - ); - buffer.writeBytes32(word); - buffer.writeBytes32( - vm.parseBytes32(string.concat("0x", rawAccesses[i].read_hash)) - ); for (uint256 j = 0; j < siblingsLength; j++) { buffer.writeBytes32( vm.parseBytes32( diff --git a/test/AccessLogs.t.sol b/test/AccessLogs.t.sol index 30d9691a..dd95aab2 100644 --- a/test/AccessLogs.t.sol +++ b/test/AccessLogs.t.sol @@ -43,6 +43,13 @@ contract AccessLogsTest is Test { } } + function verifyWord(bytes32 h, uint64 p, uint64 w) internal { + (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 { (Buffer.Context memory buffer, bytes32 rootHash) = makeReadBuffer( bytes8(0x0000000000000001).swapEndian(), @@ -62,7 +69,7 @@ contract AccessLogsTest is Test { ); AccessLogs.Context memory accessLogs = AccessLogs.Context(rootHash, buffer); - vm.expectRevert("Read region root doesn't match"); + vm.expectRevert("Read word root doesn't match"); accessLogs.readWord((position + 32).toPhysicalAddress()); } @@ -75,67 +82,51 @@ contract AccessLogsTest is Test { AccessLogs.Context memory accessLogs = AccessLogs.Context(rootHash, buffer); - vm.expectRevert("Read value doesn't match"); + vm.expectRevert("Read word root doesn't match"); accessLogs.readWord(position.toPhysicalAddress()); } - function testWriteWordHappyPath() public view { - uint64 wordWritten = 1; + function testWriteWordHappyPath() public { + uint64 wordWritten = 3; (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( initialReadLeaf, - bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ - false, - /* withWrittenValueMismatch= */ + // bytes8(wordWritten).swapEndian(), + /* withReadValueMismatch= */ false ); AccessLogs.Context memory accessLogs = AccessLogs.Context(rootHash, buffer); accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); + verifyWord(accessLogs.currentRootHash, position, wordWritten); } function testWriteWordBadRegion() public { - uint64 wordWritten = 1; + uint64 wordWritten = 3; (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( initialReadLeaf, - bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ - false, - /* withWrittenValueMismatch= */ + /* withReadValueMismatch= */ false ); AccessLogs.Context memory accessLogs = AccessLogs.Context(rootHash, buffer); - vm.expectRevert("Write region root doesn't match"); + vm.expectRevert("Write word root doesn't match"); accessLogs.writeWord((position + 32).toPhysicalAddress(), wordWritten); + verifyWord(accessLogs.currentRootHash, position, wordWritten); } function testWriteWordReadMismatch() public { - uint64 wordWritten = 1; - (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( - initialReadLeaf, - bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ - true, - /* withWrittenValueMismatch= */ - false - ); - AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, buffer); - vm.expectRevert("logged and computed read hashes mismatch"); - accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); - } - - function testWriteWordWriteMismatch() public { - uint64 wordWritten = 1; + uint64 wordWritten = 3; (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( initialReadLeaf, - bytes8(wordWritten).swapEndian(), /* withReadValueMismatch= */ - false, - /* withWrittenValueMismatch= */ + // bytes8(wordWritten).swapEndian(), + /* withReadValueMismatch= */ true ); AccessLogs.Context memory accessLogs = AccessLogs.Context(rootHash, buffer); - vm.expectRevert("Written hash mismatch"); + vm.expectRevert("Write word root doesn't match"); accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); + verifyWord(accessLogs.currentRootHash, position, wordWritten); } function testEndianSwap() public { @@ -196,7 +187,7 @@ contract AccessLogsTest is Test { if (withReadValueMismatch) { readHash = keccak256(abi.encodePacked(bytes8(readHash))); } - buffer.writeBytes32(readHash); + for (uint256 i = 0; i < 59; i++) { buffer.writeBytes32(siblingHashes[i]); } @@ -206,21 +197,14 @@ contract AccessLogsTest is Test { return (buffer, rootHash); } - function makeWriteBuffer( - bytes32 readLeaf, - bytes8 writtenWord, - bool withReadValueMismatch, - bool withWrittenValueMismatch - ) private view returns (Buffer.Context memory, bytes32) { + function makeWriteBuffer(bytes32 readLeaf, bool withReadValueMismatch) + private + view + returns (Buffer.Context memory, bytes32) + { Buffer.Context memory buffer = Buffer.Context( new bytes((59 << Memory.LOG2_LEAF) + 32 + 32 + 32), 0 ); - bytes32 writtenLeaf = patchLeaf(readLeaf, writtenWord, position); - bytes32 writtenHash = keccak256(abi.encodePacked(writtenLeaf)); - if (withWrittenValueMismatch) { - writtenHash = keccak256(abi.encodePacked(bytes8(writtenHash))); - } - buffer.writeBytes32(writtenHash); // write leaf data, leaf hash and sibling hashes buffer.writeBytes32(readLeaf); @@ -228,7 +212,7 @@ contract AccessLogsTest is Test { if (withReadValueMismatch) { readHash = keccak256(abi.encodePacked(bytes8(readHash))); } - buffer.writeBytes32(readHash); + for (uint256 i = 0; i < 59; i++) { buffer.writeBytes32(siblingHashes[i]); } From 8c4bc18286942a8d6fe2f1b970053d0ff82e044a Mon Sep 17 00:00:00 2001 From: Marcos Pernambuco Motta <1091485+mpernambuco@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:33:59 -0300 Subject: [PATCH 6/6] chore: update emulator version to v0.18.0 --- Makefile | 2 +- shasum-download | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index fb5c3303..2bd4dfaa 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TEST_DIR := test DOWNLOADDIR := downloads SRC_DIR := src -EMULATOR_VERSION ?= v0.18.0-test5 +EMULATOR_VERSION ?= v0.18.0 TESTS_DATA_FILE ?= cartesi-machine-tests-data-$(EMULATOR_VERSION).deb TESTS_DATA_DOWNLOAD_URL := https://github.com/cartesi/machine-emulator/releases/download/$(EMULATOR_VERSION)/$(TESTS_DATA_FILE) diff --git a/shasum-download b/shasum-download index 9fb5a693..3150f30d 100644 --- a/shasum-download +++ b/shasum-download @@ -1,2 +1,2 @@ -e2dcd5598a67844d6a1199189089f2cc9ca306fcb80c45a634376861c89e61de downloads/cartesi-machine-tests-data-v0.18.0-test5.deb -d167c42c61a1753190b28461fbe018d4620a2379afc982ed468e3422dd27c8ba downloads/uarch-riscv-tests-json-logs-v0.18.0-test5.tar.gz +45acd683f4a8db5ab846150e97857360ba0f44df5c1d787bb18affbcf08c2ace downloads/cartesi-machine-tests-data-v0.18.0.deb +016fbbb9d1866399a152f81dbb054447a144445ddc35e91da0a9dd242185463c downloads/uarch-riscv-tests-json-logs-v0.18.0.tar.gz