diff --git a/.gitignore b/.gitignore index 1f85ede..c46aa2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -cache/ -out/ \ No newline at end of file +onchain/cache/ +onchain/out/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 888d42d..e9dccc6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ -[submodule "lib/forge-std"] - path = lib/forge-std + +[submodule "onchain/lib/forge-std"] + path = onchain/lib/forge-std url = https://github.com/foundry-rs/forge-std diff --git a/foundry.toml b/onchain/foundry.toml similarity index 100% rename from foundry.toml rename to onchain/foundry.toml diff --git a/lib/forge-std b/onchain/lib/forge-std similarity index 100% rename from lib/forge-std rename to onchain/lib/forge-std diff --git a/script/Counter.s.sol b/onchain/script/Counter.s.sol similarity index 100% rename from script/Counter.s.sol rename to onchain/script/Counter.s.sol diff --git a/onchain/src/Push3IntAdapter.sol b/onchain/src/Push3IntAdapter.sol new file mode 100644 index 0000000..40f4c39 --- /dev/null +++ b/onchain/src/Push3IntAdapter.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/console.sol"; +import {Push3Interpreter} from "../src/Push3Interpreter.sol"; + +/** + * @title Push3IntAdapter + * @dev An adapter contract for our Push3Interpreter that provides + * various integer-only I/O function overloads. We now create + * a SUBLIST descriptor for the entire code array, placing it + * on the EXEC stack. We ignore finalCodeStack/finalExecStack + * to avoid compiler warnings. + */ +contract Push3IntAdapter { + Push3Interpreter public interpreter; + + constructor(address _interpreter) { + interpreter = Push3Interpreter(_interpreter); + } + + /** + * @notice Utility function to build a single sublist descriptor + * covering the entire `code` array, offset=0, length=code.length. + */ + function _makeFullSublistDescriptor(bytes calldata code) + internal + view + returns (uint256) + { + // CodeTag.SUBLIST is typically enum = 3, but + // let's fetch it via + // (uint8)Push3Interpreter.CodeTag.SUBLIST + // or we can directly pass 3 if we know the numeric value. + + // We'll do: + return interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, // the tag + 0, // offset + uint32(code.length), // length of entire code array + 0 // leftover bits=0 + ); + } + + /** + * @notice Single int input, single int output. + * 1) Build a sublist descriptor for the entire code, + * 2) Place that descriptor in the EXEC stack, + * 3) Preload `in1` on the integer stack, + * 4) Run the interpreter, + * 5) Return top of final int stack. + */ + function run1In1Out(bytes calldata code, int256 in1) + external + view + returns (int256 out1) + { + // 1) Build the sublist descriptor + uint256 fullDesc = _makeFullSublistDescriptor(code); + + // 2) EXEC stack of size 1 + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = fullDesc; + + // No code stack + uint256[] memory initCodeStack = new uint256[](0); + + // 3) Preload int stack + int256[] memory initIntStack = new int256[](1); + initIntStack[0] = in1; + + // 4) Run + (, , int256[] memory finalIntStack) = + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // 5) Return top of final int stack + require(finalIntStack.length > 0, "No integer output produced"); + out1 = finalIntStack[finalIntStack.length - 1]; + } + + /** + * @notice Two ints input, single int output + */ + function run2In1Out(bytes calldata code, int256 in1, int256 in2) + external + view + returns (int256 out1) + { + uint256 fullDesc = _makeFullSublistDescriptor(code); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = fullDesc; + + uint256[] memory initCodeStack = new uint256[](0); + + int256[] memory initIntStack = new int256[](2); + initIntStack[0] = in1; + initIntStack[1] = in2; + + (, , int256[] memory finalIntStack) = + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + require(finalIntStack.length > 0, "No integer output"); + + out1 = finalIntStack[finalIntStack.length - 1]; + } + + /** + * @notice Single int input, two int outputs + */ + function run1In2Out(bytes calldata code, int256 in1) + external + view + returns (int256 out1, int256 out2) + { + uint256 fullDesc = _makeFullSublistDescriptor(code); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = fullDesc; + + uint256[] memory initCodeStack = new uint256[](0); + + int256[] memory initIntStack = new int256[](1); + initIntStack[0] = in1; + + (, , int256[] memory finalIntStack) = + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + require(finalIntStack.length >= 2, "Not enough outputs"); + out1 = finalIntStack[finalIntStack.length - 1]; + out2 = finalIntStack[finalIntStack.length - 2]; + } + + /** + * @notice Zero int input, returns entire final int stack + */ + function run0InNOut(bytes calldata code) + external + view + returns (int256[] memory) + { + uint256 fullDesc = _makeFullSublistDescriptor(code); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = fullDesc; + + uint256[] memory initCodeStack = new uint256[](0); + int256[] memory initIntStack = new int256[](0); + + (, , int256[] memory finalIntStack) = + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + return finalIntStack; + } +} diff --git a/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol similarity index 80% rename from src/Push3Interpreter.sol rename to onchain/src/Push3Interpreter.sol index 6b7be32..3871fab 100644 --- a/src/Push3Interpreter.sol +++ b/onchain/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,12 @@ contract Push3Interpreter { } enum OpCode { - NOOP, // 0 - INTEGER_PLUS // 1 + NOOP, // 0 + INTEGER_PLUS, // 1 + INTEGER_MINUS, // 2 + INTEGER_MULT, // 3 + INTEGER_DUP, // 4 + INTEGER_POP // 5 } // ----------------------------------------------------- @@ -111,12 +114,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 +134,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 +194,24 @@ 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 if (tokenType == 0x06) { + temp[count] = makeInstruction(OpCode.INTEGER_DUP); + count++; + } + else if (tokenType == 0x07) { + temp[count] = makeInstruction(OpCode.INTEGER_POP); + count++; + } else { // unknown => NOOP temp[count] = makeInstruction(OpCode.NOOP); @@ -260,6 +285,38 @@ 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 (op == OpCode.INTEGER_DUP) { + if (intTop >= 1) { + int256 a = intStack[intTop - 1]; + intStack[intTop] = a; + intTop++; + } + } + else if (op == OpCode.INTEGER_POP) { + if (intTop >= 1) { + intTop -= 1; + } + } } else if (tag == CodeTag.INT_LITERAL) { int32 val = extractInt32(topDesc); diff --git a/onchain/test/Push3IntAdapter.t.sol b/onchain/test/Push3IntAdapter.t.sol new file mode 100644 index 0000000..4ecd13c --- /dev/null +++ b/onchain/test/Push3IntAdapter.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Push3Interpreter} from "../src/Push3Interpreter.sol"; +import {Push3IntAdapter} from "../src/Push3IntAdapter.sol"; + +/** + * @title Push3IntAdapterTest + * @dev Foundry test for the Push3IntAdapter contract, + * which composes with the Push3Interpreter. + */ +contract Push3IntAdapterTest is Test { + Push3Interpreter public interpreter; + Push3IntAdapter public adapter; + + function setUp() public { + // 1) Deploy the interpreter + interpreter = new Push3Interpreter(); + + // 2) Deploy the adapter, referencing the interpreter address + adapter = new Push3IntAdapter(address(interpreter)); + } + + /** + * @notice Test run1In1Out with a minimal code array. + * For example, we might do a program that just leaves its input unchanged + * (like a NOOP). + */ + function test_run1In1Out_noop() public view { + bytes memory code = hex"03000100"; + + int256 inputVal = 123; + int256 result = adapter.run1In1Out(code, inputVal); + + // We expect the code does nothing => output == input + assertEq(result, inputVal, "Expected same value out"); + } + + /** + * @notice Test run2In1Out. We'll push 5, 7, do an op that leaves 12. + */ + function test_run2In1Out_plus() public view { + bytes memory code = hex"03000101"; + + int256 in1 = 5; + int256 in2 = 7; + int256 result = adapter.run2In1Out(code, in1, in2); + + assertEq(result, 12, "Expected 5 + 7 = 12"); + } + + /** + * @notice Test run1In2Out. Push a constant to a stack with 1. + * For now, we do a placeholder code. + */ + function test_run1In2Out() public view { + bytes memory code = hex"0300050200000003"; + + int256 inVal = 42; + (int256 top, int256 secondTop) = adapter.run1In2Out(code, inVal); + + assertEq(top, 3, "top stack val mismatch"); + assertEq(secondTop, 42, "second top stack val mismatch"); + } + + /** + * @notice Test run0InNOut. + * We'll feed a code that pushes some constants, and see the final stack. + */ + function test_run0InNOut() public view { + + bytes memory code = hex"03000A02000000030200000005"; + + int256[] memory stackResult = adapter.run0InNOut(code); + + assertEq(stackResult.length, 2, "Expected 2 final stack items"); + assertEq(stackResult[0], 3, "Item 0 mismatch"); + assertEq(stackResult[1], 5, "Item 1 mismatch"); + } +} diff --git a/onchain/test/Push3Interpreter.t.sol b/onchain/test/Push3Interpreter.t.sol new file mode 100644 index 0000000..8ef693f --- /dev/null +++ b/onchain/test/Push3Interpreter.t.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {Push3Interpreter} from "../src/Push3Interpreter.sol"; + +/** + * @title Push3InterpreterTest + * @dev Foundry test contract for the updated Push3Interpreter + */ +contract Push3InterpreterTest is Test { + Push3Interpreter public interpreter; + + function setUp() public { + interpreter = new Push3Interpreter(); + } + + /** + * @notice Test a small program that: + * 1) Pushes literal 10 (int32) + * 2) Pushes literal 32 (int32) + * 3) Calls INTEGER_PLUS + * + * We'll store everything in a single SUBLIST: + * 0x03 => SUBLIST + * 0x00 0x0B => subLen=11 + * Then the next 11 bytes are: + * 0x02 + (00 00 00 0A) => INT_LITERAL(10) + * 0x02 + (00 00 00 20) => INT_LITERAL(32) + * 0x01 => INTEGER_PLUS + * + * 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 view { + // The final code array (14 bytes): + // [0] = 0x03 + // [1..2] = 0x000B + // [3] = 0x02 + // [4..7] = 0x0000000A => 10 + // [8] = 0x02 + // [9..12] = 0x00000020 => 32 + // [13] = 0x01 => plus + + bytes memory code = hex"03000B020000000A020000002001"; + + // Or fully concatenated: + // 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01 + + // 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 it + ( + , + , + int256[] memory finalIntStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // Should produce finalIntStack = [42] + 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"); + } + + function test_Dup() public view { + + bytes memory code = hex"030006020000000A06"; + + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, + 9, + 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); + + assertEq(finalIntStack.length, 2, "finalIntStack should have length 1"); + assertEq(finalIntStack[0], finalIntStack[1], "The top 2 values on the stack should be equal."); + } + + function test_Pop() public view { + + bytes memory code = hex"030006020000000A07"; + + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, + 9, + 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); + + assertEq(finalIntStack.length, 0, "finalIntStack should be empty"); + } + + /** + * @notice Test with an unknown token (0xF0). + * We expect the parser to treat it as "unknown => NOOP" + * and produce a NOOP instruction descriptor. + * We'll confirm finalIntStack remains empty (or unchanged). + */ + function test_UnknownToken() public view { + // We'll create a 5-byte code: + // 0x03 => SUBLIST + // 0x00 0x01 => length=1 + // 0xF0 => unknown token + bytes memory code = hex"030001F0"; + + // We'll parse offset=0, length=4 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 4, // length + 0 + ); + + // init stacks + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + int256[] memory initIntStack = new int256[](0); + + ( + , + , + int256[] memory finalIntStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // finalIntStack should remain empty => no literal was pushed + assertEq(finalIntStack.length, 0, "Expected finalIntStack to remain empty with unknown token => NOOP"); + } + + /** + * @notice Test a partial literal: we claim to have 4 bytes, but the code array ends early. + * This should trigger the `else { break; }` branch in parseSublist + * when trying to read 4 bytes for the INT_LITERAL. + */ + function test_OutOfRangeLiteral() public view { + // 0x03 => SUBLIST + // 0x00 0x04 => length=4 + // 0x02 => INT_LITERAL token + // Then we have only 1 byte left instead of 4 => insufficient + // + // total = 1 + 2 + 1 + 1 = 5 bytes + // The parser tries to read 4 bytes after seeing 0x04, fails, hits break. + bytes memory code = hex"03000402FF"; + + // parse offset=0, length=5 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, + 5, + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + int256[] memory initIntStack = new int256[](0); + + ( + , + , + int256[] memory finalIntStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // Because the parse breaks early, we expect finalIntStack is empty or unchanged + assertEq(finalIntStack.length, 0, "Expected parse to break on out-of-range literal => no ints pushed"); + } + + /** + * @notice Test "not enough int args" scenario: + * We'll push only 1 literal, then do INTEGER_PLUS which needs 2 integers on the stack. + * In the main loop, it sees intTop < 2 => it does nothing. + */ + function test_NotEnoughIntArgs() public view { + bytes memory code = hex"030006020000000305"; + // Breaking it down: + // 0x03 => SUBLIST + // 0x00 0x06 => length=6 + // 0x02 00000003 => INT_LITERAL(3) + // 0x05 => INTEGER_MUL + + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, + 9, + 0 + ); + + // set up stacks + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + int256[] memory initIntStack = new int256[](0); + + ( + , + , + int256[] memory finalIntStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // We expect finalIntStack => [5], + // because the PLUS instruction sees only 1 int => does nothing => 5 remains + assertEq(finalIntStack.length, 1, "Should have 1 int left"); + assertEq(finalIntStack[0], 3, "Expected stack top=3 after insufficient-args plus"); + } + + /** + * @notice Test sublist length overflow: + * subLen claims bigger than the code array. Should trigger else { break; } + * in the sublist parsing for that sublist. + */ + function test_SublistLengthOverflow() public view { + + bytes memory code = hex"050010FFFFFF"; + + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, + 6, + 0 + ); + + // init + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + int256[] memory initIntStack = new int256[](0); + + ( + , + , + int256[] memory finalIntStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + + // Expect no items pushed => finalIntStack empty + assertEq(finalIntStack.length, 0, "Expected empty stack if sublist length is out of range => parse break"); + } +} diff --git a/test/Push3Interpreter.t.sol b/test/Push3Interpreter.t.sol deleted file mode 100644 index 4ec2683..0000000 --- a/test/Push3Interpreter.t.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {Push3Interpreter} from "../src/Push3Interpreter.sol"; - -/** - * @title Push3InterpreterTest - * @dev Foundry test contract for the updated Push3Interpreter - */ -contract Push3InterpreterTest is Test { - Push3Interpreter public interpreter; - - function setUp() public { - interpreter = new Push3Interpreter(); - } - - /** - * @notice Test a small program that: - * 1) Pushes literal 10 (int32) - * 2) Pushes literal 32 (int32) - * 3) Calls INTEGER_PLUS - * - * We'll store everything in a single SUBLIST: - * 0x03 => SUBLIST - * 0x00 0x0B => subLen=11 - * Then the next 11 bytes are: - * 0x02 + (00 00 00 0A) => INT_LITERAL(10) - * 0x02 + (00 00 00 20) => INT_LITERAL(32) - * 0x01 => INTEGER_PLUS - * - * 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 { - // The final code array (14 bytes): - // [0] = 0x03 - // [1..2] = 0x000B - // [3] = 0x02 - // [4..7] = 0x0000000A => 10 - // [8] = 0x02 - // [9..12] = 0x00000020 => 32 - // [13] = 0x01 => plus - - bytes memory code = hex"03000B020000000A020000002001"; - - // Or fully concatenated: - // 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01 - - // 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 it - ( - uint256[] memory finalCodeStack, - uint256[] memory finalExecStack, - int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); - - // Should produce finalIntStack = [42] - assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); - assertEq(finalIntStack[0], 42, "Should be 10+32=42"); - } -}