Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/test_fixtures/abis/RaindexV6SubParser.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/generated/RaindexV6SubParserPointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.25;
// THIS FILE IS AUTOGENERATED BY THE BUILD SCRIPT. DO NOT EDIT BY HAND.

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0xbb26d1e20a8360e1508cbc0938a2ed1ad7af70827261928c041de84987ca731e);
bytes32 constant BYTECODE_HASH = bytes32(0x6250f10f963da9e6d6220eb1a3a656e610ae2dc27a7a41f79265932479288c6b);

/// @dev The hash of the meta that describes the contract.
bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x8336a7fad53960ed41ca79692ce772de758261a38ddecfa23983c3b89d00aaba);
Expand Down Expand Up @@ -39,13 +39,13 @@ uint8 constant PARSE_META_BUILD_DEPTH = 1;
/// to things that happen entirely on the interpreter such as well known
/// constants and references to the context grid.
bytes constant SUB_PARSER_WORD_PARSERS =
hex"175a17771786179517a417b417c417d317e317f21802181218211831184018501860187018e418f3190219121922193219421951196019701980199019a0187f1898";
hex"175a17771786179517a417b417c417d317e317f21802181218211831184018501860187018e118f018ff190f191f192f193f194e195d196d197d198d199d187f1898";

/// @dev Every two bytes is a function pointer for an operand handler.
/// These positional indexes all map to the same indexes looked up in the parse
/// meta.
bytes constant OPERAND_HANDLER_FUNCTION_POINTERS =
hex"1d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d471d701e0c";
hex"1d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d441d6d1e09";

/// @dev Every two bytes is a function pointer for a literal parser.
/// Literal dispatches are determined by the first byte(s) of the literal
Expand Down
8 changes: 4 additions & 4 deletions src/generated/candidate/RaindexV6SubParser.sol

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/lib/LibRaindexSubParser.sol
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ library LibRaindexSubParser {
returns (bool, bytes memory, bytes32[] memory)
{
uint256 column = uint256(OperandV2.unwrap(operand)) & 0xFF;
uint256 row = (uint256(OperandV2.unwrap(operand)) >> 8) & 0xFF;
uint256 row = uint256(OperandV2.unwrap(operand)) >> 8;
//slither-disable-next-line unused-return
return LibSubParse.subParserContext(CONTEXT_SIGNED_CONTEXT_START_COLUMN + column, row);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,13 @@ import {Test} from "forge-std-1.17.0/src/Test.sol";
import {OperandV2} from "rainlang-interface-0.2.13/src/interface/ISubParserV4.sol";
import {OPCODE_CONTEXT} from "rainlang-interface-0.2.13/src/interface/IInterpreterV4.sol";
import {ContextGridOverflow} from "rainlang-0.2.26/src/error/ErrSubParse.sol";
import {LibSubParse} from "rainlang-0.2.26/src/lib/parse/LibSubParse.sol";
import {LibRaindexSubParser} from "../../../src/lib/LibRaindexSubParser.sol";
import {CONTEXT_SIGNED_CONTEXT_START_COLUMN} from "../../../src/lib/LibRaindex.sol";

/// @title RaindexV6SubParserRoutingTokenDecimalsAndMasksTest
/// @notice Pins routing that the main `RaindexV6SubParser.routing.t.sol` suite
/// leaves underconstrained, all against FRESH-compiled source (the
/// `LibRaindexSubParser.subParser*` functions are called directly, so no etched
/// pointers / regen are involved and any source-constant change is observed):
///
/// 1. The absolute (column, row) of `subParserWithdrawTokenDecimals` and
/// `subParserDepositTokenDecimals`. The word-parser-wiring suite only pins
/// their slots RELATIVELY (pointer inequalities), so an absolute
/// (column, row) shift that lands on a still-distinct pointer survives there.
///
/// 2. The `& 0xFF` ROW mask in `subParserSignedContext`. The operand row is
/// `(operand >> 8) & 0xFF`. Because `LibSubParse.subParserContext` REVERTS
/// `ContextGridOverflow` when row > 255, dropping the mask turns a wide
/// operand from a successful in-range row into a revert. A wide operand whose
/// masked row is a small in-range value discriminates the masked code
/// (returns 4-byte context bytecode) from the unmasked code (reverts).
/// @notice Pins the absolute (column, row) of the deposit and withdraw token
/// decimals words, and that a signed-context row operand wider than a byte
/// reverts instead of wrapping.
contract RaindexV6SubParserRoutingTokenDecimalsAndMasksTest is Test {
/// @dev Decode the 4-byte context bytecode blob returned by a parser:
/// byte 0 = opcode index, byte 1 = io byte, byte 2 = row, byte 3 = column.
Expand All @@ -51,6 +38,8 @@ contract RaindexV6SubParserRoutingTokenDecimalsAndMasksTest is Test {
}

/// deposit-token-decimals routes to the deposit column (5), row 4.
/// The wiring suite pins this via pointer equality to withdraw-target-amount;
/// this is the direct (column, row) assertion as a self-contained anchor.
function testRouteDepositTokenDecimalsAbsolute() external pure {
(bool success, bytes memory bytecode,) =
LibRaindexSubParser.subParserDepositTokenDecimals(0, 0, OperandV2.wrap(0));
Expand All @@ -60,37 +49,15 @@ contract RaindexV6SubParserRoutingTokenDecimalsAndMasksTest is Test {
assertEq(row, 4, "deposit-token-decimals row is 4");
}

/// The signed-context ROW is `(operand >> 8) & 0xFF`. An operand of
/// `0x010000` has low byte 0 (column offset 0 -> column 8) and `>> 8` =
/// `0x0100` = 256. WITH the mask the row is `0x0100 & 0xFF` = 0, so the
/// parser succeeds and returns (column 8, row 0). WITHOUT the mask the row is
/// 256, which exceeds uint8 and reverts `ContextGridOverflow(8, 256)`. This
/// asserts the masked, non-reverting outcome, so dropping the row mask flips
/// success into a revert and fails the test.
function testSignedContextRowMaskKeepsRowInRange() external pure {
(bool success, bytes memory bytecode,) =
LibRaindexSubParser.subParserSignedContext(0, 0, OperandV2.wrap(bytes32(uint256(0x010000))));
assertTrue(success, "parser must succeed (row mask keeps row in range)");
(uint8 row, uint8 column) = decode(bytecode);
assertEq(column, 8, "column is signed-context start (8) + low byte 0");
assertEq(row, 0, "row is (0x010000 >> 8) & 0xFF = 0");
}

/// Confirms the discriminator above is genuine: the SAME wide operand, with
/// the row mask DROPPED (`>> 8` without `& 0xFF`), drives the row to 256 and
/// `LibSubParse.subParserContext` reverts `ContextGridOverflow(8, 256)`. This
/// documents the exact revert the production mask prevents, so the pairing of
/// these two tests pins the mask: removing it makes the happy test above
/// revert with this very selector/args.
function testSignedContextUnmaskedRowWouldOverflow() external {
uint256 column = uint256(0x010000) & 0xFF; // low byte 0
uint256 unmaskedRow = uint256(0x010000) >> 8; // 256
vm.expectRevert(abi.encodeWithSelector(ContextGridOverflow.selector, column + 8, unmaskedRow));
this.callContext(column + 8, unmaskedRow);
/// A signed-context row wider than a byte reverts rather than wrapping onto
/// another row.
function testSignedContextWideRowReverts() external {
vm.expectRevert(abi.encodeWithSelector(ContextGridOverflow.selector, CONTEXT_SIGNED_CONTEXT_START_COLUMN, 256));
this.callSignedContext(OperandV2.wrap(bytes32(uint256(0x010000))));
}

/// @dev External wrapper so `vm.expectRevert` can catch the library revert.
function callContext(uint256 column, uint256 row) external pure returns (bool, bytes memory, bytes32[] memory) {
return LibSubParse.subParserContext(column, row);
function callSignedContext(OperandV2 operand) external pure returns (bool, bytes memory, bytes32[] memory) {
return LibRaindexSubParser.subParserSignedContext(0, 0, operand);
}
}
15 changes: 15 additions & 0 deletions test/concrete/parser/RaindexV6SubParser.signers.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ pragma solidity =0.8.25;

import {StackAllocationMismatch} from "rainlang-0.2.26/src/error/ErrIntegrity.sol";
import {ExpectedOperand, UnexpectedOperandValue} from "rainlang-0.2.26/src/error/ErrParse.sol";
import {ContextGridOverflow} from "rainlang-0.2.26/src/error/ErrSubParse.sol";
import {OpTest, StackItem} from "rainlang-0.2.26/src/../test/abstract/OpTest.sol";
import {Strings} from "@openzeppelin-contracts-5.7.0/utils/Strings.sol";
import {LibRaindexSubParserContextFixture} from "test/util/fixture/LibRaindexSubParserContextFixture.sol";
import {LibRaindexDeploy} from "../../../src/lib/deploy/LibRaindexDeploy.sol";
import {CONTEXT_SIGNED_CONTEXT_SIGNERS_COLUMN} from "../../../src/lib/LibRaindex.sol";
import {LibEtchRaindex} from "test/util/lib/LibEtchRaindex.sol";

contract RaindexV6SubParserSignersTest is OpTest {
Expand Down Expand Up @@ -72,4 +74,17 @@ contract RaindexV6SubParserSignersTest is OpTest {

checkUnhappyParse2(rainlang, abi.encodeWithSelector(StackAllocationMismatch.selector, 2, 1));
}

/// A signer index past the last context row reverts rather than wrapping
/// onto another signer.
function testSubParserContextSignerUnhappyIndexOverflow() external {
address subParserAddress = LibRaindexDeploy.SUB_PARSER_DEPLOYED_ADDRESS;

bytes memory rainlang =
bytes(string.concat("using-words-from ", subParserAddress.toHexString(), " _: signer<256>();"));

checkUnhappyParse(
rainlang, abi.encodeWithSelector(ContextGridOverflow.selector, CONTEXT_SIGNED_CONTEXT_SIGNERS_COLUMN, 256)
);
}
}
Loading