diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ef8474..33b89d5 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 4df1a7d..323c46e 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 47ffa89..c23500e 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 aa22d65..2bd4dfa 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 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 21bf4aa..574c3a8 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 2ddbfe1..3150f30 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 +45acd683f4a8db5ab846150e97857360ba0f44df5c1d787bb18affbcf08c2ace downloads/cartesi-machine-tests-data-v0.18.0.deb +016fbbb9d1866399a152f81dbb054447a144445ddc35e91da0a9dd242185463c downloads/uarch-riscv-tests-json-logs-v0.18.0.tar.gz diff --git a/src/AccessLogs.sol b/src/AccessLogs.sol index 6491b45..131ab2c 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; @@ -61,9 +62,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 @@ -94,13 +94,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 expectedValHash = - readLeaf(a, Memory.strideFromWordAddress(readAddress)); + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = + readAddress.truncateToLeaf(); - require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(b8); + 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"); + + bytes8 word = getBytes8FromBytes32AtOffset(leaf, wordOffset); + return machineWordToSolidityUint64(word); } // @@ -138,10 +146,48 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - writeLeaf( - a, - Memory.strideFromWordAddress(writeAddress), - keccak256(abi.encodePacked(solidityUint64ToMachineWord(newValue))) + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = + writeAddress.truncateToLeaf(); + + Memory.Region memory region = Memory.regionFromStride( + Memory.strideFromLeafAddress(leafAddress), + Memory.alignedSizeFromLog2(0) ); + + 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, wordOffset + ); + + bytes32 newRootHash = + a.buffer.getRoot(region, keccak256(abi.encodePacked(newLeaf))); + a.currentRootHash = newRootHash; + } + + function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) + internal + pure + returns (bytes8) + { + return bytes8(source << (offset << Memory.LOG2_WORD)); + } + + function setBytes8ToBytes32AtOffset( + bytes8 word, + bytes32 leaf, + uint64 offset + ) internal pure returns (bytes32) { + 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/src/Buffer.sol b/src/Buffer.sol index 9b21241..adeb73e 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 7fa8540..903fa62 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,18 +73,21 @@ 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) @@ -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 a6c4547..9469704 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/src/UArchConstants.sol b/src/UArchConstants.sol index e9183fb..a439304 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; + 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 7254c6b..b25a9dc 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; @@ -74,9 +75,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 @@ -107,13 +107,22 @@ 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 expectedValHash = - readLeaf(a, Memory.strideFromWordAddress(readAddress)); + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = + readAddress.truncateToLeaf(); - require(valHash == expectedValHash, "Read value doesn't match"); - return machineWordToSolidityUint64(b8); + 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" + ); + + bytes8 word = getBytes8FromBytes32AtOffset(leaf, wordOffset); + return machineWordToSolidityUint64(word); } // @@ -151,13 +160,52 @@ library AccessLogs { Memory.PhysicalAddress writeAddress, uint64 newValue ) internal pure { - writeLeaf( - a, - Memory.strideFromWordAddress(writeAddress), - keccak256(abi.encodePacked(solidityUint64ToMachineWord(newValue))) + (Memory.PhysicalAddress leafAddress, uint64 wordOffset) = + writeAddress.truncateToLeaf(); + + Memory.Region memory region = Memory.regionFromStride( + Memory.strideFromLeafAddress(leafAddress), + Memory.alignedSizeFromLog2(0) ); + + 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, wordOffset + ); + + bytes32 newRootHash = a.buffer.getRoot(region, keccak256(abi.encodePacked(newLeaf))); + a.currentRootHash = newRootHash; } + function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset) + internal + pure + returns (bytes8) + { + return bytes8(source << (offset << Memory.LOG2_WORD)); + } + + function setBytes8ToBytes32AtOffset(bytes8 word, bytes32 leaf, uint64 offset) + internal + pure + returns (bytes32) + { + 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. diff --git a/templates/UArchReplay.t.sol.template b/templates/UArchReplay.t.sol.template index 4c5a4e5..6bc22a9 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; @@ -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 { @@ -84,16 +84,13 @@ 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) ); - // initialRootHash is passed and will be updated through out the step UArchStep.step(accessLogs); initialRootHash = accessLogs.currentRootHash; @@ -140,20 +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("read")) - ) { - bytes8 word = bytes8( - vm.parseBytes(string.concat("0x", rawAccesses[i].value)) + 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].read_hash)) ); - buffer.writeBytes8(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 de81440..dd95aab 100644 --- a/test/AccessLogs.t.sol +++ b/test/AccessLogs.t.sol @@ -28,85 +28,105 @@ 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 62 elements, the hash at access position, and other 61 siblings - // hash value at access position - hashes.push( - keccak256(abi.encodePacked(bytes8(0x0000000000000001).swapEndian())) + 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 < 62; 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 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(), + /*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 ); - - vm.expectRevert("Read region root doesn't match"); - accessLogs.readWord((position + 8).toPhysicalAddress()); + AccessLogs.Context memory accessLogs = + AccessLogs.Context(rootHash, buffer); + vm.expectRevert("Read word 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"); + vm.expectRevert("Read word root doesn't match"); accessLogs.readWord(position.toPhysicalAddress()); } - function testWriteWord() public view { + function testWriteWordHappyPath() public { + uint64 wordWritten = 3; + (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( + initialReadLeaf, + // bytes8(wordWritten).swapEndian(), + /* withReadValueMismatch= */ + false + ); AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes()); - uint64 valueWritten = 1; - - // // write should succeed - accessLogs.writeWord(position.toPhysicalAddress(), valueWritten); + AccessLogs.Context(rootHash, buffer); + accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); + verifyWord(accessLogs.currentRootHash, position, wordWritten); } - function testWriteWordRootValue() public { - hashes[0] = ( - keccak256(abi.encodePacked(bytes8(0x0000000000000002).swapEndian())) + function testWriteWordBadRegion() public { + uint64 wordWritten = 3; + (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( + initialReadLeaf, + /* withReadValueMismatch= */ + false ); AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes()); - uint64 valueWritten = 1; - - vm.expectRevert("Write region root doesn't match"); - accessLogs.writeWord(position.toPhysicalAddress(), valueWritten); + AccessLogs.Context(rootHash, buffer); + vm.expectRevert("Write word root doesn't match"); + accessLogs.writeWord((position + 32).toPhysicalAddress(), wordWritten); + verifyWord(accessLogs.currentRootHash, position, wordWritten); } - function testWriteWordRootPosition() public { + function testWriteWordReadMismatch() public { + uint64 wordWritten = 3; + (Buffer.Context memory buffer, bytes32 rootHash) = makeWriteBuffer( + initialReadLeaf, + // bytes8(wordWritten).swapEndian(), + /* withReadValueMismatch= */ + true + ); AccessLogs.Context memory accessLogs = - AccessLogs.Context(rootHash, writeBufferFromHashes()); - uint64 valueWritten = 1; - - vm.expectRevert("Write region root doesn't match"); - accessLogs.writeWord((position + 8).toPhysicalAddress(), valueWritten); + AccessLogs.Context(rootHash, buffer); + vm.expectRevert("Write word root doesn't match"); + accessLogs.writeWord(position.toPhysicalAddress(), wordWritten); + verifyWord(accessLogs.currentRootHash, position, wordWritten); } function testEndianSwap() public { @@ -124,59 +144,95 @@ contract AccessLogsTest is Test { ); } - function readBufferFromHashes(bytes8 word) - private + function makeLeaf(bytes8 word, uint64 wordPosition) + public view - returns (Buffer.Context memory) + returns (bytes32) { - Buffer.Context memory buffer = - Buffer.Context(new bytes((62 << 5) + 8), 0); - buffer.writeBytes8(word); + uint64 leafPosition = wordPosition & ~uint64(31); + uint64 offset = position - leafPosition; + bytes32 leaf = bytes32(word) >> (offset << Memory.LOG2_WORD); + return leaf; + } - for (uint256 i = 0; i < 62; i++) { - buffer.writeBytes32(hashes[i]); - } + function patchLeaf(bytes32 currentLeaf, bytes8 newWord, uint64 wordPosition) + public + view + returns (bytes32) + { + 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() + function makeReadBuffer(bytes8 readWord, bool withReadValueMismatch) private view - returns (Buffer.Context memory) + returns (Buffer.Context memory, bytes32) { - Buffer.Context memory buffer = Buffer.Context(new bytes(62 << 5), 0); - - for (uint256 i = 0; i < 62; i++) { - buffer.writeBytes32(hashes[i]); + 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))); } - // reset offset for replay + for (uint256 i = 0; i < 59; i++) { + buffer.writeBytes32(siblingHashes[i]); + } + // compute root hash and rewind buffer buffer.offset = 0; - - return buffer; + bytes32 rootHash = bubbleHashUp(readHash); + return (buffer, rootHash); } - function rootFromHashes(bytes32 drive) private view returns (bytes32) { - Buffer.Context memory buffer = Buffer.Context(new bytes(61 << 5), 0); + 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 + ); + + // 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))); + } - for (uint256 i = 0; i < 61; i++) { - buffer.writeBytes32(hashes[i + 1]); + for (uint256 i = 0; i < 59; i++) { + 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.strideFromWordAddress(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/Memory.t.sol b/test/Memory.t.sol index 8503c99..360f58e 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() diff --git a/test/UArchReset.t.sol b/test/UArchReset.t.sol index 555f4a2..1e792f1 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]) ) ); }