From 92e353dcda09ee7867e1aaaebabd34df1326486c Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 03:58:50 +0100 Subject: [PATCH 1/8] Add INTEGER_MINUS and INETEGER_MULT. --- src/Push3Interpreter.sol | 45 +++++++++++++-- test/Push3Interpreter.t.sol | 107 +++++++++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 8 deletions(-) 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"); + } } From 40172b8eafe796fd7bae2f273159ccd2ecf383fb Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 04:22:24 +0100 Subject: [PATCH 2/8] Imrove test coverage. --- test/Push3Interpreter.t.sol | 145 ++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/test/Push3Interpreter.t.sol b/test/Push3Interpreter.t.sol index 3277f64..4c1b106 100644 --- a/test/Push3Interpreter.t.sol +++ b/test/Push3Interpreter.t.sol @@ -173,4 +173,149 @@ contract Push3InterpreterTest is Test { assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); assertEq(finalIntStack[0], 30, "Should be 10 * 3 = 30"); } + + /** + * @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"); + } } From fce9ae4d6f34730702fccab0982dd93f2841cb49 Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 05:28:57 +0100 Subject: [PATCH 3/8] Add int adapter. --- src/Push3IntAdapter.sol | 152 +++++++++++++++++++++++++++++++++++++ test/Push3IntAdapter.t.sol | 81 ++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 src/Push3IntAdapter.sol create mode 100644 test/Push3IntAdapter.t.sol diff --git a/src/Push3IntAdapter.sol b/src/Push3IntAdapter.sol new file mode 100644 index 0000000..40f4c39 --- /dev/null +++ b/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/test/Push3IntAdapter.t.sol b/test/Push3IntAdapter.t.sol new file mode 100644 index 0000000..bff9229 --- /dev/null +++ b/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 { + 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 { + 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 { + 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 { + + 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"); + } +} From 6b874bf8ba5613d32521b53268b80fd5f59929f9 Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 05:45:54 +0100 Subject: [PATCH 4/8] Make test view to avoid warnings. --- test/Push3IntAdapter.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Push3IntAdapter.t.sol b/test/Push3IntAdapter.t.sol index bff9229..4ecd13c 100644 --- a/test/Push3IntAdapter.t.sol +++ b/test/Push3IntAdapter.t.sol @@ -27,7 +27,7 @@ contract Push3IntAdapterTest is Test { * For example, we might do a program that just leaves its input unchanged * (like a NOOP). */ - function test_run1In1Out_noop() public { + function test_run1In1Out_noop() public view { bytes memory code = hex"03000100"; int256 inputVal = 123; @@ -40,7 +40,7 @@ contract Push3IntAdapterTest is Test { /** * @notice Test run2In1Out. We'll push 5, 7, do an op that leaves 12. */ - function test_run2In1Out_plus() public { + function test_run2In1Out_plus() public view { bytes memory code = hex"03000101"; int256 in1 = 5; @@ -54,7 +54,7 @@ contract Push3IntAdapterTest is Test { * @notice Test run1In2Out. Push a constant to a stack with 1. * For now, we do a placeholder code. */ - function test_run1In2Out() public { + function test_run1In2Out() public view { bytes memory code = hex"0300050200000003"; int256 inVal = 42; @@ -68,7 +68,7 @@ contract Push3IntAdapterTest is Test { * @notice Test run0InNOut. * We'll feed a code that pushes some constants, and see the final stack. */ - function test_run0InNOut() public { + function test_run0InNOut() public view { bytes memory code = hex"03000A02000000030200000005"; From dadfb7983a2829e18f262a0cbc6822101df22be1 Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 05:46:09 +0100 Subject: [PATCH 5/8] Add integer dup and pop. --- src/Push3Interpreter.sol | 24 +++++++++++++++- test/Push3Interpreter.t.sol | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/Push3Interpreter.sol b/src/Push3Interpreter.sol index fa8bd78..3871fab 100644 --- a/src/Push3Interpreter.sol +++ b/src/Push3Interpreter.sol @@ -23,7 +23,9 @@ contract Push3Interpreter { NOOP, // 0 INTEGER_PLUS, // 1 INTEGER_MINUS, // 2 - INTEGER_MULT // 3 + INTEGER_MULT, // 3 + INTEGER_DUP, // 4 + INTEGER_POP // 5 } // ----------------------------------------------------- @@ -202,6 +204,14 @@ contract Push3Interpreter { 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); @@ -295,6 +305,18 @@ contract Push3Interpreter { 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/test/Push3Interpreter.t.sol b/test/Push3Interpreter.t.sol index 4c1b106..8ef693f 100644 --- a/test/Push3Interpreter.t.sol +++ b/test/Push3Interpreter.t.sol @@ -174,6 +174,61 @@ contract Push3InterpreterTest is Test { 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" From 9f3636d2327d4efc6e10d3df482c523fcd063aab Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 06:00:54 +0100 Subject: [PATCH 6/8] Remove forge-std. --- .gitignore | 4 ++-- .gitmodules | 4 +--- lib/forge-std | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) delete mode 160000 lib/forge-std 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..8b13789 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1 @@ -[submodule "lib/forge-std"] - path = lib/forge-std - url = https://github.com/foundry-rs/forge-std + diff --git a/lib/forge-std b/lib/forge-std deleted file mode 160000 index b93cf4b..0000000 --- a/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b93cf4bc34ff214c099dc970b153f85ade8c9f66 From 818da97ea55f9e23b35a18edd620600b6618547e Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 06:03:14 +0100 Subject: [PATCH 7/8] Move onchain logic to speperate dir. --- foundry.toml => onchain/foundry.toml | 0 {script => onchain/script}/Counter.s.sol | 0 {src => onchain/src}/Push3IntAdapter.sol | 0 {src => onchain/src}/Push3Interpreter.sol | 0 {test => onchain/test}/Push3IntAdapter.t.sol | 0 {test => onchain/test}/Push3Interpreter.t.sol | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename foundry.toml => onchain/foundry.toml (100%) rename {script => onchain/script}/Counter.s.sol (100%) rename {src => onchain/src}/Push3IntAdapter.sol (100%) rename {src => onchain/src}/Push3Interpreter.sol (100%) rename {test => onchain/test}/Push3IntAdapter.t.sol (100%) rename {test => onchain/test}/Push3Interpreter.t.sol (100%) diff --git a/foundry.toml b/onchain/foundry.toml similarity index 100% rename from foundry.toml rename to onchain/foundry.toml 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/src/Push3IntAdapter.sol b/onchain/src/Push3IntAdapter.sol similarity index 100% rename from src/Push3IntAdapter.sol rename to onchain/src/Push3IntAdapter.sol diff --git a/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol similarity index 100% rename from src/Push3Interpreter.sol rename to onchain/src/Push3Interpreter.sol diff --git a/test/Push3IntAdapter.t.sol b/onchain/test/Push3IntAdapter.t.sol similarity index 100% rename from test/Push3IntAdapter.t.sol rename to onchain/test/Push3IntAdapter.t.sol diff --git a/test/Push3Interpreter.t.sol b/onchain/test/Push3Interpreter.t.sol similarity index 100% rename from test/Push3Interpreter.t.sol rename to onchain/test/Push3Interpreter.t.sol From 50288eb30c14f4c9557b7e8b07a8bf1f9de234f9 Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 06:04:03 +0100 Subject: [PATCH 8/8] forge install: forge-std v1.9.5 --- .gitmodules | 3 +++ onchain/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 160000 onchain/lib/forge-std diff --git a/.gitmodules b/.gitmodules index 8b13789..e9dccc6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1 +1,4 @@ +[submodule "onchain/lib/forge-std"] + path = onchain/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/onchain/lib/forge-std b/onchain/lib/forge-std new file mode 160000 index 0000000..b93cf4b --- /dev/null +++ b/onchain/lib/forge-std @@ -0,0 +1 @@ +Subproject commit b93cf4bc34ff214c099dc970b153f85ade8c9f66