From fce9ae4d6f34730702fccab0982dd93f2841cb49 Mon Sep 17 00:00:00 2001 From: zjkree Date: Tue, 24 Dec 2024 05:28:57 +0100 Subject: [PATCH] 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"); + } +}