diff --git a/src/Push3Interpreter.sol b/src/Push3Interpreter.sol index 6b7be32..fa8bd78 100644 --- a/src/Push3Interpreter.sol +++ b/src/Push3Interpreter.sol @@ -6,8 +6,7 @@ import "forge-std/console.sol"; /** * @title Push3Interpreter * @dev Demonstrates a minimal Push3-like interpreter that uses - * a token-based bytecode approach. We fix the assembly - * endianness issues by copying exact bytes for 2- and 4-byte reads. + * a token-based bytecode approach. */ contract Push3Interpreter { // ----------------------------------------------------- @@ -21,8 +20,10 @@ contract Push3Interpreter { } enum OpCode { - NOOP, // 0 - INTEGER_PLUS // 1 + NOOP, // 0 + INTEGER_PLUS, // 1 + INTEGER_MINUS, // 2 + INTEGER_MULT // 3 } // ----------------------------------------------------- @@ -111,12 +112,15 @@ contract Push3Interpreter { */ function readUint16(bytes calldata code, uint32 start) internal pure returns (uint16 val) { require(start + 2 <= code.length, "readUint16 out of range"); + uint256 word; assembly { let buf := mload(0x40) calldatacopy(buf, add(code.offset, start), 2) - let word := mload(buf) + word := mload(buf) val := and(word, 0xffff) } + // Not sure this is correct + val = uint16(word >> 240); } // ----------------------------------------------------- @@ -128,6 +132,7 @@ contract Push3Interpreter { * 0x01 => INTEGER_PLUS * 0x02 => INT_LITERAL => read next 4 bytes => int32 * 0x03 => SUBLIST => read next 2 bytes => subLen => parse that + * 0x04 => INTEGER_MINUS */ function parseSublist(bytes calldata code, uint32 off, uint32 len) internal @@ -187,6 +192,16 @@ contract Push3Interpreter { break; } } + else if (tokenType == 0x04) { + // INTEGER_PLUS + temp[count] = makeInstruction(OpCode.INTEGER_MINUS); + count++; + } + else if (tokenType == 0x05) { + // INTEGER_PLUS + temp[count] = makeInstruction(OpCode.INTEGER_MULT); + count++; + } else { // unknown => NOOP temp[count] = makeInstruction(OpCode.NOOP); @@ -260,6 +275,26 @@ contract Push3Interpreter { intTop++; } } + else if (op == OpCode.INTEGER_MINUS) { + // pop top 2 => minus + if (intTop >= 2) { + int256 a = intStack[intTop - 1]; + int256 b = intStack[intTop - 2]; + intTop -= 2; + intStack[intTop] = b - a; + intTop++; + } + } + else if (op == OpCode.INTEGER_MULT) { + // pop top 2 => minus + if (intTop >= 2) { + int256 a = intStack[intTop - 1]; + int256 b = intStack[intTop - 2]; + intTop -= 2; + intStack[intTop] = b * a; + intTop++; + } + } } else if (tag == CodeTag.INT_LITERAL) { int32 val = extractInt32(topDesc); diff --git a/test/Push3Interpreter.t.sol b/test/Push3Interpreter.t.sol index 4ec2683..3277f64 100644 --- a/test/Push3Interpreter.t.sol +++ b/test/Push3Interpreter.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; import {Push3Interpreter} from "../src/Push3Interpreter.sol"; /** @@ -32,7 +33,7 @@ contract Push3InterpreterTest is Test { * So total = 1 + 2 + (1+4 + 1+4 + 1) = 14 bytes * => 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01 */ - function test_LiteralsAndPlus() public { + function test_LiteralsAndPlus() public view { // The final code array (14 bytes): // [0] = 0x03 // [1..2] = 0x000B @@ -63,8 +64,8 @@ contract Push3InterpreterTest is Test { // Run it ( - uint256[] memory finalCodeStack, - uint256[] memory finalExecStack, + , + , int256[] memory finalIntStack ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); @@ -72,4 +73,104 @@ contract Push3InterpreterTest is Test { assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); assertEq(finalIntStack[0], 42, "Should be 10+32=42"); } + + /** + * @notice Test pushing literal 10, literal 3, then INTEGER_MINUS => (10 - 3) = 7 + * + * The plan: + * 0x03 => SUBLIST token + * 0x00 0x0B => subLen=11 + * + * Then 11 bytes: + * 0x02 + (00 00 00 0A) => INT_LITERAL(10) + * 0x02 + (00 00 00 03) => INT_LITERAL(3) + * 0x04 => INTEGER_MINUS (assuming 0x04 is assigned to minus) + */ + function test_Minus() public view { + // We'll produce 14 total bytes, same structure as the plus example + // Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x04 => minus] + + bytes memory code = hex"03000B020000000A020000000304"; + // Breaking it down: + // 0x03 => SUBLIST + // 0x00 0x0B => length=11 + // 0x02 0000000A => INT_LITERAL(10) + // 0x02 00000003 => INT_LITERAL(3) + // 0x04 => INTEGER_MINUS + + // We'll parse offset=0, length=14 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 14, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + + // Run + ( + , + , + int256[] memory finalIntStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // Check final result => should be [7] + assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); + assertEq(finalIntStack[0], 7, "Should be 10 - 3 = 7"); + } + + /** + * @notice Test pushing literal 10, literal 3, then INTEGER_MULT => (10 * 3) = 30 + * + * The plan: + * 0x03 => SUBLIST token + * 0x00 0x0B => subLen=11 + * + * Then 11 bytes: + * 0x02 + (00 00 00 0A) => INT_LITERAL(10) + * 0x02 + (00 00 00 03) => INT_LITERAL(3) + * 0x05 => INTEGER_MULT + */ + function test_Mult() public view { + // We'll produce 14 total bytes, same structure as the plus example + // Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x05 => mul] + + bytes memory code = hex"03000B020000000A020000000305"; + // Breaking it down: + // 0x03 => SUBLIST + // 0x00 0x0B => length=11 + // 0x02 0000000A => INT_LITERAL(10) + // 0x02 00000003 => INT_LITERAL(3) + // 0x05 => INTEGER_MUL + + // We'll parse offset=0, length=14 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 14, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + + // Run + ( + , + , + int256[] memory finalIntStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // Check final result => should be [30] + assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); + assertEq(finalIntStack[0], 30, "Should be 10 * 3 = 30"); + } }