From bb820de989c130ecd0405dbf5468215548ba0a6f Mon Sep 17 00:00:00 2001 From: developer3516 Date: Mon, 17 Aug 2026 15:27:18 +0530 Subject: [PATCH] test(StdJson): cover readBytes at parseRaw's inferred-type lengths `readBytes` has no test coverage at all, and the length at which it once broke is still a special case in the layer beneath it. `parseRaw` infers a value's type rather than taking a declared one: a hex string of exactly 20 bytes encodes as an `address` and one of exactly 32 bytes as a `bytes32`, while every other length encodes as dynamic `bytes`. That inference is what broke `readBytes` in v1.2.0, where it decoded `parseRaw` output as `bytes` instead of calling `vm.parseJsonBytes`. Reading through the dedicated cheatcode is correct today, so pin it: assert `readBytes` round-trips 19, 20, 21, 31, 32 and 33 byte values, bracketing both inference boundaries. Also document the inference on `parseRaw`, since reaching it through that function still reverts and nothing said so. Closes #592 --- src/StdJson.sol | 4 ++++ test/StdJson.t.sol | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/StdJson.sol b/src/StdJson.sol index 2c894422..7962dd41 100644 --- a/src/StdJson.sol +++ b/src/StdJson.sol @@ -34,6 +34,10 @@ library stdJson { /// @notice ABI-encodes the JSON value selected by `key`. /// @dev `key` uses the same selector syntax as `vm.parseJson*`, such as `.a` or `$`. + /// @dev The value's type is inferred, not declared. A hex string of exactly 20 bytes is encoded + /// as an `address` and one of exactly 32 bytes as a `bytes32`; every other length is encoded as + /// dynamic `bytes`. Decoding those two lengths as `bytes` therefore reverts, so prefer + /// `readBytes` when the value is meant to be byte data. function parseRaw(string memory json, string memory key) internal pure returns (bytes memory) { return vm.parseJson(json, key); } diff --git a/test/StdJson.t.sol b/test/StdJson.t.sol index 5594a54a..2ec7b15d 100644 --- a/test/StdJson.t.sol +++ b/test/StdJson.t.sol @@ -30,6 +30,34 @@ contract StdJsonTest is Test { assertEq(json.readUint(".a"), 123); } + // Regression test for https://github.com/foundry-rs/forge-std/issues/592 + // `parseRaw` infers a value's type, encoding a 20-byte hex string as an `address` and a + // 32-byte one as a `bytes32`. Reading either back as `bytes` reverts, which is how + // `readBytes` broke when it decoded `parseRaw` output instead of calling + // `vm.parseJsonBytes`. Pin the lengths on both sides of each inference boundary. + function test_ReadBytesAtInferredTypeLengths() public pure { + string memory nineteen = '{"a":"0x00000000000000000000000000000000000000"}'; + assertEq(nineteen.readBytes(".a"), hex"00000000000000000000000000000000000000"); + + string memory twentyZeros = '{"a":"0x0000000000000000000000000000000000000000"}'; + assertEq(twentyZeros.readBytes(".a"), hex"0000000000000000000000000000000000000000"); + + string memory twentyAddressShaped = '{"a":"0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad6"}'; + assertEq(twentyAddressShaped.readBytes(".a"), hex"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad6"); + + string memory twentyOne = '{"a":"0x000000000000000000000000000000000000000000"}'; + assertEq(twentyOne.readBytes(".a"), hex"000000000000000000000000000000000000000000"); + + string memory thirtyOne = '{"a":"0x00000000000000000000000000000000000000000000000000000000000012"}'; + assertEq(thirtyOne.readBytes(".a"), hex"00000000000000000000000000000000000000000000000000000000000012"); + + string memory thirtyTwo = '{"a":"0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad64bf5122f344554c53bde2ebb"}'; + assertEq(thirtyTwo.readBytes(".a"), hex"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad64bf5122f344554c53bde2ebb"); + + string memory thirtyThree = '{"a":"0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad64bf5122f344554c53bde2ebb00"}'; + assertEq(thirtyThree.readBytes(".a"), hex"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad64bf5122f344554c53bde2ebb00"); + } + function test_writeJson() public { string memory json = "json"; json.serialize("a", uint256(123));