From ce1cc8adada0027b419bd04ba9d52e8bd4faf829 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Tue, 31 Dec 2024 02:41:28 +0300 Subject: [PATCH 01/17] extend gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b79784c..f14a111 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ onchain/out/ offchain/target/ -.direnv \ No newline at end of file +.direnv +.env -- 2.34.1 From 7d74d8471701989c27022b62ed3ceace66c02b46 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Fri, 3 Jan 2025 08:34:30 +0300 Subject: [PATCH 02/17] extend CodeTag and OpCode enums with a boolean type --- onchain/src/Push3Interpreter.sol | 37 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index 3871fab..7a70ffd 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -9,6 +9,11 @@ import "forge-std/console.sol"; * a token-based bytecode approach. */ contract Push3Interpreter { + // ----------------------------------------------------- + // 0. CONSTANTS + // ----------------------------------------------------- + uint8 internal constant OPCODE_BOOL_OFFSET = 0x6; + // ----------------------------------------------------- // 1. ENUMS // ----------------------------------------------------- @@ -16,16 +21,34 @@ contract Push3Interpreter { NO_TAG, // 0 INSTRUCTION, // 1 INT_LITERAL, // 2 - SUBLIST // 3 + SUBLIST, // 3 + BOOL_LITERAL // 4 } enum OpCode { - NOOP, // 0 - INTEGER_PLUS, // 1 - INTEGER_MINUS, // 2 - INTEGER_MULT, // 3 - INTEGER_DUP, // 4 - INTEGER_POP // 5 + NOOP, // 0 + INTEGER_PLUS, // 1 + INTEGER_MINUS, // 2 + INTEGER_MULT, // 3 + INTEGER_DUP, // 4 + INTEGER_POP, // 5 + BOOL_DUP, // OPCODE_BOOL_OFFSET + 0 + BOOL_POP, // OPCODE_BOOL_OFFSET + 1 + BOOL_SWAP, // OPCODE_BOOL_OFFSET + 2 + BOOL_FLUSH, // OPCODE_BOOL_OFFSET + 3 + BOOL_STACKDEPTH, // OPCODE_BOOL_OFFSET + 4 + BOOL_NOT, // OPCODE_BOOL_OFFSET + 5 + BOOL_AND, // OPCODE_BOOL_OFFSET + 6 + BOOL_OR, // OPCODE_BOOL_OFFSET + 7 + BOOL_EQ, // OPCODE_BOOL_OFFSET + 8 + BOOL_FROMFLOAT, // OPCODE_BOOL_OFFSET + 9 + BOOL_FROMINTEGER,// OPCODE_BOOL_OFFSET + 10 + BOOL_ROT, // OPCODE_BOOL_OFFSET + 11 + BOOL_SHOVE, // OPCODE_BOOL_OFFSET + 12 + BOOL_YANK, // OPCODE_BOOL_OFFSET + 13 + BOOL_YANKDUP, // OPCODE_BOOL_OFFSET + 14 + BOOL_DEFINE, // OPCODE_BOOL_OFFSET + 15 + BOOL_RAND // OPCODE_BOOL_OFFSET + 16 } // ----------------------------------------------------- -- 2.34.1 From d38937095e3b00a03312545deb2d7f25d730d3f2 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Fri, 3 Jan 2025 09:21:57 +0300 Subject: [PATCH 03/17] extend Descriptor with a boolean type --- onchain/src/Push3Interpreter.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index 7a70ffd..4049a63 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -108,11 +108,21 @@ contract Push3Interpreter { return makeDescriptor(CodeTag.INT_LITERAL, 0, 0, masked); } + function makeBoolLiteral(bool val) internal pure returns (uint256) { + uint256 masked; + if (val) masked = uint256(1); + return makeDescriptor(CodeTag.BOOL_LITERAL, 0, 0, masked); + } + function extractInt32(uint256 desc) internal pure returns (int32) { // read the low 32 bits return int32(uint32(getLow184(desc))); } + function extractBool(uint256 desc) internal pure returns (bool) { + return desc & 1 == 1; + } + // ----------------------------------------------------- // 3. HELPER READS // ----------------------------------------------------- -- 2.34.1 From 0be8408a3a29f626bc409ed6644b4e234680d56f Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Fri, 3 Jan 2025 10:51:45 +0300 Subject: [PATCH 04/17] extend helper reads with a boolean type --- onchain/src/Push3Interpreter.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index 4049a63..0ec3699 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -142,6 +142,19 @@ contract Push3Interpreter { val = uint32(word >> 224); } + function readBool(bytes calldata code, uint32 start) internal pure returns (bool val) { + require(start + 1 <= code.length, "readBool out of range"); + uint256 word; + assembly { + let buf := mload(0x40) // free memory + // copy exactly 1 byte from code into buf + calldatacopy(buf, add(code.offset, start), 1) + // assign 1 byte to a word + word := shr(248, mload(buf)) + } + val = word & 1 == 1; + } + /** * @dev Read 2 bytes from `code` at `start` as uint16. */ -- 2.34.1 From 9c657167a8b6ed1c7189dd3d8f64f264dbfa7b02 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Fri, 3 Jan 2025 21:04:03 +0300 Subject: [PATCH 05/17] refactored into ordered CodeTag and OpCode (same keys in a code) --- onchain/src/Push3Interpreter.sol | 195 ++++++++++++++++++------------- 1 file changed, 115 insertions(+), 80 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index 0ec3699..ffa3cfe 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -12,7 +12,9 @@ contract Push3Interpreter { // ----------------------------------------------------- // 0. CONSTANTS // ----------------------------------------------------- - uint8 internal constant OPCODE_BOOL_OFFSET = 0x6; + uint8 internal constant OPCODE_INTEGER_OFFSET = 0x05; + uint8 internal constant OPCODE_BOOL_OFFSET = 0x0a; + uint8 internal constant OPCODE_LAST = OPCODE_BOOL_OFFSET + 0x10; // ----------------------------------------------------- // 1. ENUMS @@ -21,18 +23,22 @@ contract Push3Interpreter { NO_TAG, // 0 INSTRUCTION, // 1 INT_LITERAL, // 2 - SUBLIST, // 3 - BOOL_LITERAL // 4 + BOOL_LITERAL, // 3 + SUBLIST // 4 } enum OpCode { NOOP, // 0 - INTEGER_PLUS, // 1 - INTEGER_MINUS, // 2 - INTEGER_MULT, // 3 - INTEGER_DUP, // 4 - INTEGER_POP, // 5 - BOOL_DUP, // OPCODE_BOOL_OFFSET + 0 + NOOP_1, // 1 - empty for INSTRUCTION + NOOP_2, // 2 - empty for INT_LITERAL + NOOP_3, // 3 - empty for BOOL_LITERAL + NOOP_4, // 4 - empty for SUBLIST + INTEGER_PLUS, // 5 - OPCODE_INTEGER_OFFSET + INTEGER_MINUS, // OPCODE_INTEGER_OFFSET + 1 + INTEGER_MULT, // OPCODE_INTEGER_OFFSET + 2 + INTEGER_DUP, // OPCODE_INTEGER_OFFSET + 3 + INTEGER_POP, // OPCODE_INTEGER_OFFSET + 4 + BOOL_DUP, // 10 - OPCODE_BOOL_OFFSET BOOL_POP, // OPCODE_BOOL_OFFSET + 1 BOOL_SWAP, // OPCODE_BOOL_OFFSET + 2 BOOL_FLUSH, // OPCODE_BOOL_OFFSET + 3 @@ -175,12 +181,15 @@ contract Push3Interpreter { // 4. SUBLIST PARSING // ----------------------------------------------------- /** - * Token format: - * 0x00 => NOOP - * 0x01 => INTEGER_PLUS + * Token format is combination of ordered CodeTag and OpCode: + * 0x00 => NOOP/NO_TAG + * 0x01 => INSTRUCTION => TODO: decide what to do * 0x02 => INT_LITERAL => read next 4 bytes => int32 - * 0x03 => SUBLIST => read next 2 bytes => subLen => parse that - * 0x04 => INTEGER_MINUS + * 0x03 => BOOL_LITERAL => read next 1 byte => bool + * 0x04 => SUBLIST => read next 2 bytes => subLen => parse that + * 0x05 => INTEGER_PLUS + * 0x06 => INTEGER_MINUS + * ... opcode list with type offsets */ function parseSublist(bytes calldata code, uint32 off, uint32 len) internal @@ -206,9 +215,13 @@ contract Push3Interpreter { count++; } else if (tokenType == 0x01) { - // INTEGER_PLUS - temp[count] = makeInstruction(OpCode.INTEGER_PLUS); - count++; + // INSTRUCTION + // TODO: implement what is decided + // OpCode instructionOpcode = OpCode(readUint8(code,cur)); + // validate instruction in range, otherwise NOOP + // temp[count] = makeInstruction(instructionOpcode); + // count++; + break; // unimplemented } else if (tokenType == 0x02) { // INT_LITERAL => 4 bytes @@ -223,6 +236,17 @@ contract Push3Interpreter { } } else if (tokenType == 0x03) { + // BOOL_LITERAL + if (cur + 1 <= endPos && (cur + 1) <= code.length) { + bool val = readBool(code, cur); + cur += 1; + temp[count] = makeBoolLiteral(val); + count++; + } else { + break; + } + } + else if (tokenType == 0x04) { // SUBLIST => read 2 bytes => length if (cur + 2 <= endPos && (cur + 2) <= code.length) { uint16 subLen = readUint16(code, cur); @@ -240,22 +264,9 @@ 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); + else if (tokenType <= OPCODE_LAST) { + // OPCODE + temp[count] = makeInstruction(OpCode(tokenType)); count++; } else { @@ -275,17 +286,19 @@ contract Push3Interpreter { // 5. MAIN INTERPRETER // ----------------------------------------------------- function runInterpreter( - bytes calldata code, - uint256[] calldata initCodeStack, - uint256[] calldata initExecStack, - int256[] calldata initIntStack + bytes calldata code, // non-empty - genetic agent (DNA) + uint256[] calldata initCodeStack, // empty + uint256[] calldata initExecStack, // descriptors + int256[] calldata initIntStack, // 32bit word + bool[] calldata initBoolStack ) external pure returns ( uint256[] memory finalCodeStack, uint256[] memory finalExecStack, - int256[] memory finalIntStack + int256[] memory finalIntStack, + bool[] memory finalBoolStack ) { // A) CODE STACK @@ -309,6 +322,13 @@ contract Push3Interpreter { intStack[k] = initIntStack[k]; } + // D) BOOL STACK + bool[] memory boolStack = new bool[](initBoolStack.length + 256); + uint256 boolTop = initBoolStack.length; + for (uint256 k = 0; k < initBoolStack.length; k++) { + boolStack[k] = initBoolStack[k]; + } + // D) MAIN LOOP while (execTop > 0) { uint256 topDesc = execStack[execTop - 1]; @@ -321,47 +341,11 @@ contract Push3Interpreter { if (op == OpCode.NOOP) { // do nothing } - else if (op == OpCode.INTEGER_PLUS) { - // pop top 2 => sum - if (intTop >= 2) { - int256 a = intStack[intTop - 1]; - int256 b = intStack[intTop - 2]; - intTop -= 2; - intStack[intTop] = b + a; - intTop++; - } + else if (uint8(op) <= OPCODE_INTEGER_OFFSET) { + (intTop, intStack) = execIntOpCode(intTop, intStack, op); } - 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 (uint8(op) <= OPCODE_BOOL_OFFSET) { + (boolTop, boolStack) = execBoolOpCode(boolTop, boolStack, op); } } else if (tag == CodeTag.INT_LITERAL) { @@ -369,6 +353,11 @@ contract Push3Interpreter { intStack[intTop] = int256(val); intTop++; } + else if (tag == CodeTag.BOOL_LITERAL) { + bool val = extractBool(topDesc); + boolStack[boolTop] = val; + boolTop++; + } else if (tag == CodeTag.SUBLIST) { // parse sublist => push in reverse uint32 off = getOffset(topDesc); @@ -376,8 +365,8 @@ contract Push3Interpreter { if (off + len <= code.length) { uint256[] memory parsed = parseSublist(code, off, len); - for (uint256 p = 0; p < parsed.length; p++) { - execStack[execTop] = parsed[parsed.length - 1 - p]; + for (uint256 p = 0; p < parsed.length; p++) { // index 0 1 2 + execStack[execTop] = parsed[parsed.length - 1 - p]; // [0x01, 0x20, 0x0a] execTop++; } } @@ -403,4 +392,50 @@ contract Push3Interpreter { finalIntStack[i] = intStack[i]; } } + + function execIntOpCode(uint256 intTop, int256[] memory intStack, OpCode op) internal returns (uint256, int256[] memory) { + if (op == OpCode.INTEGER_PLUS) { + // pop top 2 => sum + 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_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; + } + } + return (intTop, intStack); + } } -- 2.34.1 From 68d9076424f63317312138bb08284f8ad765db97 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 15:12:22 +0300 Subject: [PATCH 06/17] return to: all opcode implementations inline --- onchain/src/Push3Interpreter.sol | 92 +++++++++++++++----------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index ffa3cfe..f6d0415 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -342,10 +342,52 @@ contract Push3Interpreter { // do nothing } else if (uint8(op) <= OPCODE_INTEGER_OFFSET) { - (intTop, intStack) = execIntOpCode(intTop, intStack, op); + // INTEGER OPCODES + if (op == OpCode.INTEGER_PLUS) { + // pop top 2 => sum + 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_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 (uint8(op) <= OPCODE_BOOL_OFFSET) { - (boolTop, boolStack) = execBoolOpCode(boolTop, boolStack, op); + // BOOL OPCODES } } else if (tag == CodeTag.INT_LITERAL) { @@ -392,50 +434,4 @@ contract Push3Interpreter { finalIntStack[i] = intStack[i]; } } - - function execIntOpCode(uint256 intTop, int256[] memory intStack, OpCode op) internal returns (uint256, int256[] memory) { - if (op == OpCode.INTEGER_PLUS) { - // pop top 2 => sum - 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_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; - } - } - return (intTop, intStack); - } } -- 2.34.1 From ead3797ea0096508770912a20dbf07b5285741d2 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 15:54:11 +0300 Subject: [PATCH 07/17] refactor helper read funcs and add instruction into parseSublist --- onchain/src/Push3Interpreter.sol | 78 ++++++++++++++++---------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index f6d0415..db5e4e9 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -133,48 +133,46 @@ contract Push3Interpreter { // 3. HELPER READS // ----------------------------------------------------- + /** + * @dev Read `x` bytes from `code` at `start` as uint. + */ + function readUint(bytes calldata code, uint32 start, uint256 numBytes) internal pure returns (uint256 word) { + if (start + bytesNum) > code.length) revert ReadUintOutOfRange(); + assembly { + let buf := mload(0x40) // free memory + // copy exactly x bytes from code into buf + calldatacopy(buf, add(code.offset, start), numBytes) + word := mload(buf) + } + word >>= 256 - numBytes * 8; + } + /** * @dev Read 4 bytes from `code` at `start` as uint32. */ function readUint32(bytes calldata code, uint32 start) internal pure returns (uint32 val) { - require(start + 4 <= code.length, "readUint32 out of range"); - uint256 word; - assembly { - let buf := mload(0x40) // free memory - // copy exactly 4 bytes from code into buf - calldatacopy(buf, add(code.offset, start), 4) - word := mload(buf) - } - val = uint32(word >> 224); - } - - function readBool(bytes calldata code, uint32 start) internal pure returns (bool val) { - require(start + 1 <= code.length, "readBool out of range"); - uint256 word; - assembly { - let buf := mload(0x40) // free memory - // copy exactly 1 byte from code into buf - calldatacopy(buf, add(code.offset, start), 1) - // assign 1 byte to a word - word := shr(248, mload(buf)) - } - val = word & 1 == 1; + val = uint32(readUint(code, start, 4)); } /** * @dev Read 2 bytes from `code` at `start` as uint16. */ 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) - word := mload(buf) - val := and(word, 0xffff) - } - // Not sure this is correct - val = uint16(word >> 240); + val = uint16(readUint(code, start, 2)); + } + + /** + * @dev Read 1 byte from `code` at `start` as uint8. + */ + function readUint8(bytes calldata code, uint32 start) internal pure returns (uint8 val) { + val = uint8(readUint(code, start, 1)); + } + + /** + * @dev Read 1 byte from `code` at `start` as bool. + */ + function readBool(bytes calldata code, uint32 start) internal pure returns (bool val) { + val = readUint(code, start, 1) & 1 == 1; } // ----------------------------------------------------- @@ -183,7 +181,7 @@ contract Push3Interpreter { /** * Token format is combination of ordered CodeTag and OpCode: * 0x00 => NOOP/NO_TAG - * 0x01 => INSTRUCTION => TODO: decide what to do + * 0x01 => INSTRUCTION => read next 1 byte => OpCode * 0x02 => INT_LITERAL => read next 4 bytes => int32 * 0x03 => BOOL_LITERAL => read next 1 byte => bool * 0x04 => SUBLIST => read next 2 bytes => subLen => parse that @@ -216,12 +214,14 @@ contract Push3Interpreter { } else if (tokenType == 0x01) { // INSTRUCTION - // TODO: implement what is decided - // OpCode instructionOpcode = OpCode(readUint8(code,cur)); - // validate instruction in range, otherwise NOOP - // temp[count] = makeInstruction(instructionOpcode); - // count++; - break; // unimplemented + if (cur + 1 <= endPos && (cur + 1) <= code.length) { + uint8 opcode = readUint8(code,cur); + if (opcode < OPCODE_INTEGER_OFFSET || opcode > OPCODE_LAST) opcode = uint8(0); // NOOP + temp[count] = makeInstruction(OpCode(opcode)); + count++; + } else { + break; + } } else if (tokenType == 0x02) { // INT_LITERAL => 4 bytes -- 2.34.1 From 3d7cf547238f7824e615d6fe5a5893e37faa4519 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 16:01:04 +0300 Subject: [PATCH 08/17] added error ReadUintOutOfRange --- onchain/src/Push3Interpreter.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index db5e4e9..93727f3 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -9,6 +9,8 @@ import "forge-std/console.sol"; * a token-based bytecode approach. */ contract Push3Interpreter { + // ReadUint`numBits` out of the code range. + error ReadUintOutOfRange(uint256 numBits); // ----------------------------------------------------- // 0. CONSTANTS // ----------------------------------------------------- @@ -137,14 +139,15 @@ contract Push3Interpreter { * @dev Read `x` bytes from `code` at `start` as uint. */ function readUint(bytes calldata code, uint32 start, uint256 numBytes) internal pure returns (uint256 word) { - if (start + bytesNum) > code.length) revert ReadUintOutOfRange(); + uint256 numBits = numBytes * 8; + if (start + bytesNum) > code.length) revert ReadUintOutOfRange(numBits); assembly { let buf := mload(0x40) // free memory // copy exactly x bytes from code into buf calldatacopy(buf, add(code.offset, start), numBytes) word := mload(buf) } - word >>= 256 - numBytes * 8; + word >>= 256 - numBits; } /** -- 2.34.1 From 7f259c8a073ec344e2a6224719d93c482b905a30 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 16:13:38 +0300 Subject: [PATCH 09/17] fix opcode offsets in runInterpreter --- onchain/src/Push3Interpreter.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index 93727f3..aa7bcb4 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -341,10 +341,10 @@ contract Push3Interpreter { if (tag == CodeTag.INSTRUCTION) { OpCode op = getOpCode(topDesc); - if (op == OpCode.NOOP) { - // do nothing + if (op < OPCODE_INTEGER_OFFSET) { + // NOOP, do nothing } - else if (uint8(op) <= OPCODE_INTEGER_OFFSET) { + else if (uint8(op) < OPCODE_BOOL_OFFSET) { // INTEGER OPCODES if (op == OpCode.INTEGER_PLUS) { // pop top 2 => sum @@ -389,7 +389,7 @@ contract Push3Interpreter { } } } - else if (uint8(op) <= OPCODE_BOOL_OFFSET) { + else if (uint8(op) <= OPCODE_LAST) { // BOOL OPCODES } } -- 2.34.1 From e220ff39fc72facf0811177a874ea46d22625738 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 16:20:00 +0300 Subject: [PATCH 10/17] add bool stack return and fix adapter --- onchain/src/Push3IntAdapter.sol | 18 +++++++++--------- onchain/src/Push3Interpreter.sol | 7 ++++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/onchain/src/Push3IntAdapter.sol b/onchain/src/Push3IntAdapter.sol index 40f4c39..394d485 100644 --- a/onchain/src/Push3IntAdapter.sol +++ b/onchain/src/Push3IntAdapter.sol @@ -28,9 +28,9 @@ contract Push3IntAdapter { view returns (uint256) { - // CodeTag.SUBLIST is typically enum = 3, but - // let's fetch it via - // (uint8)Push3Interpreter.CodeTag.SUBLIST + // CodeTag.SUBLIST is typically enum = 4, 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: @@ -70,7 +70,7 @@ contract Push3IntAdapter { initIntStack[0] = in1; // 4) Run - (, , int256[] memory finalIntStack) = + (, , int256[] memory finalIntStack,) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); // 5) Return top of final int stack @@ -96,11 +96,11 @@ contract Push3IntAdapter { initIntStack[0] = in1; initIntStack[1] = in2; - (, , int256[] memory finalIntStack) = + (, , int256[] memory finalIntStack,) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); require(finalIntStack.length > 0, "No integer output"); - + out1 = finalIntStack[finalIntStack.length - 1]; } @@ -121,7 +121,7 @@ contract Push3IntAdapter { int256[] memory initIntStack = new int256[](1); initIntStack[0] = in1; - (, , int256[] memory finalIntStack) = + (, , int256[] memory finalIntStack,) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); require(finalIntStack.length >= 2, "Not enough outputs"); @@ -130,7 +130,7 @@ contract Push3IntAdapter { } /** - * @notice Zero int input, returns entire final int stack + * @notice Zero int input, returns entire final int stack */ function run0InNOut(bytes calldata code) external @@ -144,7 +144,7 @@ contract Push3IntAdapter { uint256[] memory initCodeStack = new uint256[](0); int256[] memory initIntStack = new int256[](0); - (, , int256[] memory finalIntStack) = + (, , int256[] memory finalIntStack,) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); return finalIntStack; diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index aa7bcb4..9b5e572 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -293,7 +293,7 @@ contract Push3Interpreter { uint256[] calldata initCodeStack, // empty uint256[] calldata initExecStack, // descriptors int256[] calldata initIntStack, // 32bit word - bool[] calldata initBoolStack + bool[] calldata initBoolStack // bool array ) external pure @@ -436,5 +436,10 @@ contract Push3Interpreter { for (uint256 i = 0; i < intTop; i++) { finalIntStack[i] = intStack[i]; } + + finalBoolStack = new int256[](boolTop); + for (uint256 i = 0; i < boolTop; i++) { + finalBoolStack[i] = boolStack[i]; + } } } -- 2.34.1 From 79c8b3bd6d108280e5f08055c6fd12c50b167d00 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 16:27:21 +0300 Subject: [PATCH 11/17] add bool stack into adapter --- onchain/src/Push3IntAdapter.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/onchain/src/Push3IntAdapter.sol b/onchain/src/Push3IntAdapter.sol index 394d485..5929de7 100644 --- a/onchain/src/Push3IntAdapter.sol +++ b/onchain/src/Push3IntAdapter.sol @@ -69,9 +69,12 @@ contract Push3IntAdapter { int256[] memory initIntStack = new int256[](1); initIntStack[0] = in1; + // No bool stack + bool[] memory initBoolStack = new bool[](0); + // 4) Run (, , int256[] memory finalIntStack,) = - interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // 5) Return top of final int stack require(finalIntStack.length > 0, "No integer output produced"); @@ -96,8 +99,10 @@ contract Push3IntAdapter { initIntStack[0] = in1; initIntStack[1] = in2; + bool[] memory initBoolStack = new bool[](0); + (, , int256[] memory finalIntStack,) = - interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); require(finalIntStack.length > 0, "No integer output"); @@ -121,8 +126,10 @@ contract Push3IntAdapter { int256[] memory initIntStack = new int256[](1); initIntStack[0] = in1; + bool[] memory initBoolStack = new bool[](0); + (, , int256[] memory finalIntStack,) = - interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); require(finalIntStack.length >= 2, "Not enough outputs"); out1 = finalIntStack[finalIntStack.length - 1]; @@ -143,9 +150,10 @@ contract Push3IntAdapter { uint256[] memory initCodeStack = new uint256[](0); int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); (, , int256[] memory finalIntStack,) = - interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); return finalIntStack; } -- 2.34.1 From 18063377fbc6f77ee45ac1e9feff5e2f01cb471b Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 17:54:07 +0300 Subject: [PATCH 12/17] fix interpeter unit tests --- onchain/src/Push3Interpreter.sol | 6 +- onchain/test/Push3Interpreter.t.sol | 114 ++++++++++++++++------------ 2 files changed, 69 insertions(+), 51 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index 9b5e572..c632ce8 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -140,7 +140,7 @@ contract Push3Interpreter { */ function readUint(bytes calldata code, uint32 start, uint256 numBytes) internal pure returns (uint256 word) { uint256 numBits = numBytes * 8; - if (start + bytesNum) > code.length) revert ReadUintOutOfRange(numBits); + if (start + numBytes > code.length) revert ReadUintOutOfRange(numBits); assembly { let buf := mload(0x40) // free memory // copy exactly x bytes from code into buf @@ -341,7 +341,7 @@ contract Push3Interpreter { if (tag == CodeTag.INSTRUCTION) { OpCode op = getOpCode(topDesc); - if (op < OPCODE_INTEGER_OFFSET) { + if (uint8(op) < OPCODE_INTEGER_OFFSET) { // NOOP, do nothing } else if (uint8(op) < OPCODE_BOOL_OFFSET) { @@ -437,7 +437,7 @@ contract Push3Interpreter { finalIntStack[i] = intStack[i]; } - finalBoolStack = new int256[](boolTop); + finalBoolStack = new bool[](boolTop); for (uint256 i = 0; i < boolTop; i++) { finalBoolStack[i] = boolStack[i]; } diff --git a/onchain/test/Push3Interpreter.t.sol b/onchain/test/Push3Interpreter.t.sol index c18526b..110b2a4 100644 --- a/onchain/test/Push3Interpreter.t.sol +++ b/onchain/test/Push3Interpreter.t.sol @@ -23,30 +23,30 @@ contract Push3InterpreterTest is Test { * 3) Calls INTEGER_PLUS * * We'll store everything in a single SUBLIST: - * 0x03 => SUBLIST + * 0x04 => 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 + * 0x05 => 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 + * => 0x04 00 0B 02 00 00 00 0A 02 00 00 00 20 05 */ function test_LiteralsAndPlus() public view { // The final code array (14 bytes): - // [0] = 0x03 + // [0] = 0x04 // [1..2] = 0x000B // [3] = 0x02 // [4..7] = 0x0000000A => 10 // [8] = 0x02 // [9..12] = 0x00000020 => 32 - // [13] = 0x01 => plus + // [13] = 0x05 => plus - bytes memory code = hex"03000B020000000A020000002001"; + bytes memory code = hex"04000B020000000A020000002005"; // Or fully concatenated: - // 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01 + // 0x04 00 0B 02 00 00 00 0A 02 00 00 00 20 05 // We'll parse offset=0, length=14 uint256 sublistDesc = interpreter.makeDescriptor( @@ -61,13 +61,15 @@ contract Push3InterpreterTest is Test { initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); // Run it ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // Should produce finalIntStack = [42] assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); @@ -78,25 +80,25 @@ contract Push3InterpreterTest is Test { * @notice Test pushing literal 10, literal 3, then INTEGER_MINUS => (10 - 3) = 7 * * The plan: - * 0x03 => SUBLIST token + * 0x04 => 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) + * 0x06 => INTEGER_MINUS (assuming 0x06 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] + // Format: [0x04, 0x000B, 0x02(10), 0x03(3), 0x06 => minus] - bytes memory code = hex"03000B020000000A020000000304"; + bytes memory code = hex"04000B020000000A020000000306"; // Breaking it down: - // 0x03 => SUBLIST + // 0x04 => SUBLIST // 0x00 0x0B => length=11 // 0x02 0000000A => INT_LITERAL(10) // 0x02 00000003 => INT_LITERAL(3) - // 0x04 => INTEGER_MINUS + // 0x06 => INTEGER_MINUS // We'll parse offset=0, length=14 uint256 sublistDesc = interpreter.makeDescriptor( @@ -111,13 +113,15 @@ contract Push3InterpreterTest is Test { initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); // Run ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // Check final result => should be [7] assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); @@ -128,25 +132,25 @@ contract Push3InterpreterTest is Test { * @notice Test pushing literal 10, literal 3, then INTEGER_MULT => (10 * 3) = 30 * * The plan: - * 0x03 => SUBLIST token + * 0x04 => 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 + * 0x07 => 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] + // Format: [0x04, 0x000B, 0x02(10), 0x02(3), 0x07 => mul] - bytes memory code = hex"03000B020000000A020000000305"; + bytes memory code = hex"04000B020000000A020000000307"; // Breaking it down: - // 0x03 => SUBLIST + // 0x04 => SUBLIST // 0x00 0x0B => length=11 // 0x02 0000000A => INT_LITERAL(10) // 0x02 00000003 => INT_LITERAL(3) - // 0x05 => INTEGER_MUL + // 0x07 => INTEGER_MUL // We'll parse offset=0, length=14 uint256 sublistDesc = interpreter.makeDescriptor( @@ -161,13 +165,15 @@ contract Push3InterpreterTest is Test { initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); // Run ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // Check final result => should be [30] assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); @@ -176,12 +182,12 @@ contract Push3InterpreterTest is Test { function test_Dup() public view { - bytes memory code = hex"030006020000000A06"; + bytes memory code = hex"040006020000000A08"; uint256 sublistDesc = interpreter.makeDescriptor( Push3Interpreter.CodeTag.SUBLIST, - 0, - 9, + 0, + 9, 0 ); @@ -190,13 +196,15 @@ contract Push3InterpreterTest is Test { initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); // Run ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); assertEq(finalIntStack.length, 2, "finalIntStack should have length 1"); assertEq(finalIntStack[0], finalIntStack[1], "The top 2 values on the stack should be equal."); @@ -204,12 +212,12 @@ contract Push3InterpreterTest is Test { function test_Pop() public view { - bytes memory code = hex"030006020000000A07"; + bytes memory code = hex"040006020000000A09"; uint256 sublistDesc = interpreter.makeDescriptor( Push3Interpreter.CodeTag.SUBLIST, - 0, - 9, + 0, + 9, 0 ); @@ -218,33 +226,35 @@ contract Push3InterpreterTest is Test { initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); // Run ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); 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" + * 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 + // 0x04 => SUBLIST // 0x00 0x01 => length=1 // 0xF0 => unknown token - bytes memory code = hex"030001F0"; + bytes memory code = hex"040001F0"; // We'll parse offset=0, length=4 uint256 sublistDesc = interpreter.makeDescriptor( - Push3Interpreter.CodeTag.SUBLIST, + Push3Interpreter.CodeTag.SUBLIST, 0, // offset 4, // length 0 @@ -255,12 +265,14 @@ contract Push3InterpreterTest is Test { uint256[] memory initExecStack = new uint256[](1); initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // finalIntStack should remain empty => no literal was pushed assertEq(finalIntStack.length, 0, "Expected finalIntStack to remain empty with unknown token => NOOP"); @@ -268,18 +280,18 @@ contract Push3InterpreterTest is Test { /** * @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 + * 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 + // 0x04 => 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"; + bytes memory code = hex"04000402FF"; // parse offset=0, length=5 uint256 sublistDesc = interpreter.makeDescriptor( @@ -293,12 +305,14 @@ contract Push3InterpreterTest is Test { uint256[] memory initExecStack = new uint256[](1); initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // 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"); @@ -310,12 +324,12 @@ contract Push3InterpreterTest is Test { * In the main loop, it sees intTop < 2 => it does nothing. */ function test_NotEnoughIntArgs() public view { - bytes memory code = hex"030006020000000305"; + bytes memory code = hex"040006020000000307"; // Breaking it down: - // 0x03 => SUBLIST + // 0x04 => SUBLIST // 0x00 0x06 => length=6 // 0x02 00000003 => INT_LITERAL(3) - // 0x05 => INTEGER_MUL + // 0x07 => INTEGER_MUL uint256 sublistDesc = interpreter.makeDescriptor( Push3Interpreter.CodeTag.SUBLIST, @@ -329,14 +343,16 @@ contract Push3InterpreterTest is Test { uint256[] memory initExecStack = new uint256[](1); initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); - // We expect finalIntStack => [5], + // 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"); @@ -344,12 +360,12 @@ contract Push3InterpreterTest is Test { /** * @notice Test sublist length overflow: - * subLen claims bigger than the code array. Should trigger else { break; } + * 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"; + + bytes memory code = hex"050010FFFFFF"; uint256 sublistDesc = interpreter.makeDescriptor( Push3Interpreter.CodeTag.SUBLIST, @@ -363,12 +379,14 @@ contract Push3InterpreterTest is Test { uint256[] memory initExecStack = new uint256[](1); initExecStack[0] = sublistDesc; int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); ( , , int256[] memory finalIntStack - ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack); + , + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // Expect no items pushed => finalIntStack empty assertEq(finalIntStack.length, 0, "Expected empty stack if sublist length is out of range => parse break"); -- 2.34.1 From 2e214f95d37fb601a46b81b3e3464fb387c9d982 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 17:59:31 +0300 Subject: [PATCH 13/17] fix adapter unit tests --- onchain/test/Push3IntAdapter.t.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/onchain/test/Push3IntAdapter.t.sol b/onchain/test/Push3IntAdapter.t.sol index 4ecd13c..664e008 100644 --- a/onchain/test/Push3IntAdapter.t.sol +++ b/onchain/test/Push3IntAdapter.t.sol @@ -23,12 +23,12 @@ contract Push3IntAdapterTest is Test { } /** - * @notice Test run1In1Out with a minimal code array. - * For example, we might do a program that just leaves its input unchanged - * (like a NOOP). + * @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"; + bytes memory code = hex"04000100"; int256 inputVal = 123; int256 result = adapter.run1In1Out(code, inputVal); @@ -41,21 +41,21 @@ contract Push3IntAdapterTest is Test { * @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"; + bytes memory code = hex"04000105"; 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. + * For now, we do a placeholder code. */ function test_run1In2Out() public view { - bytes memory code = hex"0300050200000003"; + bytes memory code = hex"0400050200000003"; int256 inVal = 42; (int256 top, int256 secondTop) = adapter.run1In2Out(code, inVal); @@ -65,12 +65,12 @@ contract Push3IntAdapterTest is Test { } /** - * @notice Test run0InNOut. + * @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"; + + bytes memory code = hex"04000A02000000030200000005"; int256[] memory stackResult = adapter.run0InNOut(code); -- 2.34.1 From dfb7b933cc139926553ede0703203cf9d0f288ee Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 19:09:34 +0300 Subject: [PATCH 14/17] more clear offset values --- onchain/src/Push3Interpreter.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index c632ce8..fd48714 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -14,9 +14,9 @@ contract Push3Interpreter { // ----------------------------------------------------- // 0. CONSTANTS // ----------------------------------------------------- - uint8 internal constant OPCODE_INTEGER_OFFSET = 0x05; - uint8 internal constant OPCODE_BOOL_OFFSET = 0x0a; - uint8 internal constant OPCODE_LAST = OPCODE_BOOL_OFFSET + 0x10; + uint8 internal constant OPCODE_INTEGER_OFFSET = uint8(OpCode.INTEGER_PLUS); + uint8 internal constant OPCODE_BOOL_OFFSET = uint8(OpCode.BOOL_DUP); + uint8 internal constant OPCODE_LAST = int8(OpCode.BOOL_RAND); // ----------------------------------------------------- // 1. ENUMS -- 2.34.1 From 818f702b3282cdfe07db26606e241dc4719e3abe Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sat, 4 Jan 2025 19:33:28 +0300 Subject: [PATCH 15/17] basic bool operations implementation --- onchain/src/Push3Interpreter.sol | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index fd48714..ff78a17 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -391,6 +391,73 @@ contract Push3Interpreter { } else if (uint8(op) <= OPCODE_LAST) { // BOOL OPCODES + if (op == OpCode.BOOL_DUP) { + // dup top + if (boolTop >= 1) { + bool a = boolStack[boolTop - 1]; + boolStack[boolTop] = a; + boolTop++; + } + } + else if (op == OpCode.BOOL_POP) { + // pop top + if (boolTop >= 1) { + boolTop -= 1; + } + } + else if (op == OpCode.BOOL_SWAP) { + // swap top 2 + if (boolTop >= 2) { + bool a = boolStack[boolTop - 1]; + boolStack[boolTop - 1] = boolStack[boolTop - 2]; + boolStack[boolTop - 2] = a; + } + } + else if (op == OpCode.BOOL_FLUSH) { + // empty stack + boolTop = 0; + } + else if (op == OpCode.BOOL_STACKDEPTH) { + // push bool depth onto int stack + intStack[intTop] = boolTop; + intTop++; + } + else if (op == OpCode.BOOL_NOT) { + + } + else if (op == OpCode.BOOL_AND) { + + } + else if (op == OpCode.BOOL_OR) { + + } + else if (op == OpCode.BOOL_EQ) { + + } + else if (op == OpCode.BOOL_FROMFLOAT) { + + } + else if (op == OpCode.BOOL_FROMINTEGER) { + + } + else if (op == OpCode.BOOL_ROT) { + + } + else if (op == OpCode.BOOL_SHOVE) { + + } + else if (op == OpCode.BOOL_YANK) { + + } + else if (op == OpCode.BOOL_YANKDUP) { + + } + else if (op == OpCode.BOOL_DEFINE) { + + } + else if (op == OpCode.BOOL_RAND) { + + } } } else if (tag == CodeTag.INT_LITERAL) { -- 2.34.1 From c86111206ff595ba77ddcadb1ff3657619901fdf Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sun, 5 Jan 2025 04:28:48 +0300 Subject: [PATCH 16/17] implemented and tested majority of bool opcodes --- onchain/src/Push3Interpreter.sol | 98 +++++-- onchain/test/Push3Interpreter.t.sol | 430 +++++++++++++++++++++++++++- 2 files changed, 495 insertions(+), 33 deletions(-) diff --git a/onchain/src/Push3Interpreter.sol b/onchain/src/Push3Interpreter.sol index ff78a17..61d5644 100644 --- a/onchain/src/Push3Interpreter.sol +++ b/onchain/src/Push3Interpreter.sol @@ -16,7 +16,7 @@ contract Push3Interpreter { // ----------------------------------------------------- uint8 internal constant OPCODE_INTEGER_OFFSET = uint8(OpCode.INTEGER_PLUS); uint8 internal constant OPCODE_BOOL_OFFSET = uint8(OpCode.BOOL_DUP); - uint8 internal constant OPCODE_LAST = int8(OpCode.BOOL_RAND); + uint8 internal constant OPCODE_LAST = uint8(OpCode.BOOL_RAND); // ----------------------------------------------------- // 1. ENUMS @@ -296,7 +296,7 @@ contract Push3Interpreter { bool[] calldata initBoolStack // bool array ) external - pure + view returns ( uint256[] memory finalCodeStack, uint256[] memory finalExecStack, @@ -393,24 +393,21 @@ contract Push3Interpreter { // BOOL OPCODES if (op == OpCode.BOOL_DUP) { // dup top - if (boolTop >= 1) { - bool a = boolStack[boolTop - 1]; - boolStack[boolTop] = a; + if (boolTop > 0) { + boolStack[boolTop] = boolStack[boolTop - 1]; boolTop++; } } else if (op == OpCode.BOOL_POP) { // pop top - if (boolTop >= 1) { - boolTop -= 1; + if (boolTop > 0) { + boolTop--; } } else if (op == OpCode.BOOL_SWAP) { // swap top 2 - if (boolTop >= 2) { - bool a = boolStack[boolTop - 1]; - boolStack[boolTop - 1] = boolStack[boolTop - 2]; - boolStack[boolTop - 2] = a; + if (boolTop > 1) { + (boolStack[boolTop - 1], boolStack[boolTop - 2]) = (boolStack[boolTop - 2], boolStack[boolTop - 1]); } } else if (op == OpCode.BOOL_FLUSH) { @@ -419,44 +416,97 @@ contract Push3Interpreter { } else if (op == OpCode.BOOL_STACKDEPTH) { // push bool depth onto int stack - intStack[intTop] = boolTop; + intStack[intTop] = int256(boolTop); intTop++; } else if (op == OpCode.BOOL_NOT) { - + // push NOT of top + if (boolTop > 0) { + boolStack[boolTop - 1] = !boolStack[boolTop - 1]; + } } else if (op == OpCode.BOOL_AND) { - + // push AND of top 2 + if (boolTop > 1) { + boolStack[boolTop - 2] = boolStack[boolTop - 1] && boolStack[boolTop - 2]; + boolTop--; + } } else if (op == OpCode.BOOL_OR) { - + // push OR of top 2 + if (boolTop > 1) { + boolStack[boolTop - 2] = boolStack[boolTop - 1] || boolStack[boolTop - 2]; + boolTop--; + } } else if (op == OpCode.BOOL_EQ) { - + // push True if top 2 equal + if (boolTop > 1) { + boolStack[boolTop - 2] = boolStack[boolTop - 1] == boolStack[boolTop - 2]; + boolTop--; + } } else if (op == OpCode.BOOL_FROMFLOAT) { - + // FLOAT is not implemented yet } else if (op == OpCode.BOOL_FROMINTEGER) { - + // push True if top int not eq 0 + if (intTop > 0) { + boolStack[boolTop] = intStack[intTop - 1] != int256(0); + boolTop++; + intTop--; + } } else if (op == OpCode.BOOL_ROT) { - + // rotate [top - 3, top - 2, top - 1] into -> [top - 2, top - 1, top - 3] + if (boolTop > 2) { + (boolStack[boolTop - 1], boolStack[boolTop - 2], boolStack[boolTop - 3]) = (boolStack[boolTop - 3], boolStack[boolTop - 1], boolStack[boolTop - 2]); + } } else if (op == OpCode.BOOL_SHOVE) { - + // Task: Inserts the top BOOLEAN "deep" in the stack, at the position indexed by the top INTEGER. + // Questions: what does "inserts" mean? does it mean that we pop top value and then insert it + // at index? does it mean that array size does not change? + // what happens with the previous element at this index? + // is it correct that this element stays in array? + // should it be moved in top or bottom direction from index (index + || -)? + // does it mean that we need to reindex the whole stack? + // is the simplest solution to reindex to iterate through the whole stack? + // do we really want have such loops? } else if (op == OpCode.BOOL_YANK) { - + // Task: Removes an indexed item from "deep" in the stack and pushes it on top of the stack. The index is taken from the INTEGER stack. + // Same question as for SHOVE is valid here. + // For example, in our implementation currently we are not really poping + // element from an array in POP, we only adjusting the counter. + // For removing index we would need to loop over the array to reindex. } else if (op == OpCode.BOOL_YANKDUP) { - + // Task: Pushes a copy of an indexed item "deep" in the stack onto the top of the stack, without removing the deep item. The index is taken from the INTEGER stack. + uint256 index = uint256(intStack[intTop - 1]); + if (boolTop > 2) { + // pop int stack + intTop--; + // index in range (reverse array indexes) + index++; // adjust as boolTop is length, highest index is boolTop - 1 + if (index > boolTop) { + index = 0; + } else { + index = boolTop - index; + } + // push value on bool stack + boolStack[boolTop] = boolStack[index]; + boolTop++; + } } else if (op == OpCode.BOOL_DEFINE) { - + // NAME is not implemented yet } else if (op == OpCode.BOOL_RAND) { - + // Pushes a random BOOLEAN. + uint256 randomNum = uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, msg.sender))); + boolStack[boolTop] = extractBool(randomNum); + boolTop++; } } } diff --git a/onchain/test/Push3Interpreter.t.sol b/onchain/test/Push3Interpreter.t.sol index 110b2a4..b72cf8b 100644 --- a/onchain/test/Push3Interpreter.t.sol +++ b/onchain/test/Push3Interpreter.t.sol @@ -182,12 +182,12 @@ contract Push3InterpreterTest is Test { function test_Dup() public view { - bytes memory code = hex"040006020000000A08"; + bytes memory code = hex"040009020000000A0803010A"; uint256 sublistDesc = interpreter.makeDescriptor( Push3Interpreter.CodeTag.SUBLIST, 0, - 9, + 12, 0 ); @@ -202,22 +202,24 @@ contract Push3InterpreterTest is Test { ( , , - int256[] memory finalIntStack - , + int256[] memory finalIntStack, + bool[] memory finalBoolStack ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); assertEq(finalIntStack.length, 2, "finalIntStack should have length 1"); - assertEq(finalIntStack[0], finalIntStack[1], "The top 2 values on the stack should be equal."); + assertEq(finalIntStack[0], finalIntStack[1], "The top 2 values on the int stack should be equal."); + assertEq(finalBoolStack.length, 2, "finalBoolStack should have length 2"); + assertEq(finalBoolStack[0], finalBoolStack[1], "The top 2 values on the bool stack should be equal."); } function test_Pop() public view { - bytes memory code = hex"040006020000000A09"; + bytes memory code = hex"040009020000000A0903010B"; uint256 sublistDesc = interpreter.makeDescriptor( Push3Interpreter.CodeTag.SUBLIST, 0, - 9, + 12, 0 ); @@ -232,11 +234,12 @@ contract Push3InterpreterTest is Test { ( , , - int256[] memory finalIntStack - , + int256[] memory finalIntStack, + bool[] memory finalBoolStack ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); assertEq(finalIntStack.length, 0, "finalIntStack should be empty"); + assertEq(finalBoolStack.length, 0, "finalBoolStack should be empty"); } /** @@ -391,4 +394,413 @@ contract Push3InterpreterTest is Test { // Expect no items pushed => finalIntStack empty assertEq(finalIntStack.length, 0, "Expected empty stack if sublist length is out of range => parse break"); } + + // BOOL STACK + + /** + * @notice Test pushing literal True, literal False, then BOOL_SWAP => [False, True] + * + * The plan: + * 0x04 => SUBLIST token + * 0x00 0x05 => subLen=5 + * + * Then 5 bytes: + * 0x03 + (01) => BOOL_LITERAL(True) + * 0x03 + (00) => BOOL_LITERAL(False) + * 0x0C => BOOL_SWAP + */ + function test_BoolSwap() public view { + // We'll produce 8 total bytes, same structure + // Format: [0x04, 0x0005, 0x03(01), 0x03(00), 0x0C => bool_swap] + + bytes memory code = hex"040005030103000C"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x05 => length=5 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x00 => BOOL_LITERAL(False) + // 0x0C => BOOL_SWAP + + // We'll parse offset=0, length=8 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 8, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be [True, False] swapped into [False, True] + assertEq(finalBoolStack.length, 2, "finalBoolStack should have length 2"); + assertEq(finalBoolStack[0], false, "Should be False"); + assertEq(finalBoolStack[1], true, "Should be True"); + } + + /** + * @notice Test pushing literal True, literal False, then BOOL_FLUSH => [] + * Result should be empty stack. + */ + function test_BoolFlush() public view { + // We'll produce 8 total bytes, same structure + // Format: [0x04, 0x0005, 0x03(01), 0x03(00), 0x0D => bool_flush] + + bytes memory code = hex"040005030103000D"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x05 => length=5 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x00 => BOOL_LITERAL(False) + // 0x0D => BOOL_FLUSH + + // We'll parse offset=0, length=8 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 8, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be empty bool stack + assertEq(finalBoolStack.length, 0, "finalBoolStack should have length 0"); + } + + /** + * @notice Test pushing literal True, literal False, then BOOL_STACKDEPTH => intStack[2] + * Result should be number of pushed literals as intStack top. + */ + function test_BoolStackDepth() public view { + // We'll produce 8 total bytes, same structure + // Format: [0x04, 0x0005, 0x03(01), 0x03(00), 0x0E => bool_stackdepth] + + bytes memory code = hex"040005030103000E"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x05 => length=5 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x00 => BOOL_LITERAL(False) + // 0x0E => BOOL_STACKDEPTH + + // We'll parse offset=0, length=8 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 8, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + int256[] memory finalIntStack, + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be empty bool stack + assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); + assertEq(finalBoolStack.length, 2, "finalBoolStack should have length 2"); + assertEq(finalIntStack[0], 2, "IntStack top value should be 2, as we pushed 2 literals onto bool stack"); + } + + /** + * @notice Test pushing literal True, then BOOL_NOT => boolStack[False] + * Result should be False on top of bool stack. + */ + function test_BoolNot() public view { + // We'll produce 6 total bytes, same structure + // Format: [0x04, 0x0003, 0x03(01), 0x0F => bool_not] + + bytes memory code = hex"04000303010F"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x03 => length=3 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x0F => BOOL_NOT + + // We'll parse offset=0, length=6 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 6, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be False on top of bool stack + assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], false, "Expected to be False after logical NOT on True"); + } + + /** + * @notice Test pushing literal True, literal True, then BOOL_AND => boolStack[True] + * Result should be True on top of bool stack. + */ + function test_BoolAnd() public view { + // We'll produce 8 total bytes, same structure + // Format: [0x04, 0x0005, 0x03(01), 0x03(01) 0x10 => bool_and] + + bytes memory code = hex"0400050301030110"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x05 => length=5 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x10 => BOOL_AND + + // We'll parse offset=0, length=8 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 8, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be True on top of bool stack + assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], true, "Expected to be True after logical AND on True and True"); + } + + /** + * @notice Test pushing literal True, literal False, then BOOL_OR => boolStack[True] + * Result should be True on top of bool stack. + */ + function test_BoolOr() public view { + // We'll produce 8 total bytes, same structure + // Format: [0x04, 0x0005, 0x03(01), 0x03(00) 0x11 => bool_or] + + bytes memory code = hex"0400050301030011"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x05 => length=5 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x00 => BOOL_LITERAL(False) + // 0x11 => BOOL_OR + + // We'll parse offset=0, length=8 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 8, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be True on top of bool stack + assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], true, "Expected to be True after logical OR on True and False"); + } + + /** + * @notice Test pushing literal True, literal False, then BOOL_EQ => boolStack[False] + * Result should be False on top of bool stack. + */ + function test_BoolEq() public view { + // We'll produce 8 total bytes, same structure + // Format: [0x04, 0x0005, 0x03(01), 0x03(00) 0x12 => bool_eq] + + bytes memory code = hex"0400050301030012"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x05 => length=5 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x00 => BOOL_LITERAL(False) + // 0x12 => BOOL_EQ + + // We'll parse offset=0, length=8 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 8, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be False on top of bool stack + assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], false, "Expected to be False as True is not equal False"); + } + + /** + * @notice Test pushing literal 0x2145, then BOOL_FROMINTEGER => boolStack[True] + * Result should be True on top of bool stack. + */ + function test_BoolFromInteger() public view { + // We'll produce 9 total bytes, same structure + // Format: [0x04, 0x0004, 0x02(0x00002145), 0x14 => bool_frominteger] + + bytes memory code = hex"040006020000214514"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x04 => length=6 + // 0x02 0x00002145 => INT_LITERAL(0x2145) + // 0x14 => BOOL_FROMINTEGER + + // We'll parse offset=0, length=9 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 9, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + int256[] memory finalIntStack, + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be True on bool stack + assertEq(finalIntStack.length, 0, "finalIntStack should have length 0"); + assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], true, "boolStack top value should be True, as we pushed non-zero literal onto int stack"); + } + + /** + * @notice Test pushing literal True, literal False, literal True, then BOOL_ROT => boolStack[False, True, True] + * Result should be rotated bool stack from [True, False, True] into [False, True, True]. + */ + function test_BoolRot() public view { + // We'll produce 10 total bytes, same structure + // Format: [0x04, 0x0007, 0x03(01), 0x03(00), 0x03(01), 0x15 => bool_rot] + + bytes memory code = hex"04000703010300030115"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x07 => length=7 + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x00 => BOOL_LITERAL(False) + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x15 => BOOL_ROT + + // We'll parse offset=0, length=10 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 10, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be [False, True, True] bool stack + assertEq(finalBoolStack.length, 3, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], false, "Expected to be False as value from middle position moved to bottom of the stack"); + assertEq(finalBoolStack[1], true, "Expected to be True as value from top position moved to middle of the stack"); + assertEq(finalBoolStack[2], true, "Expected to be True as value from bottom position moved to top of the stack"); + } } -- 2.34.1 From 39de432af4fe6479f252180ae7fab5a3cdd90f99 Mon Sep 17 00:00:00 2001 From: navelbuffle Date: Sun, 5 Jan 2025 08:46:39 +0300 Subject: [PATCH 17/17] completed positive unit test coverage for available bool opcodes --- onchain/test/Push3Interpreter.t.sol | 103 +++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/onchain/test/Push3Interpreter.t.sol b/onchain/test/Push3Interpreter.t.sol index b72cf8b..7caa2ef 100644 --- a/onchain/test/Push3Interpreter.t.sol +++ b/onchain/test/Push3Interpreter.t.sol @@ -798,9 +798,110 @@ contract Push3InterpreterTest is Test { ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); // Check final result => should be [False, True, True] bool stack - assertEq(finalBoolStack.length, 3, "finalBoolStack should have length 1"); + assertEq(finalBoolStack.length, 3, "finalBoolStack should have length 3"); assertEq(finalBoolStack[0], false, "Expected to be False as value from middle position moved to bottom of the stack"); assertEq(finalBoolStack[1], true, "Expected to be True as value from top position moved to middle of the stack"); assertEq(finalBoolStack[2], true, "Expected to be True as value from bottom position moved to top of the stack"); } + + /** + * @notice Test pushing literal 0x02, literal True, literal False, literal True, then BOOL_YANKDUP => boolStack[True, False, True, True] + * Result should be copied True on top of bool stack [True, False, True, True] + */ + function test_BoolYankDup() public view { + // We'll produce 15 total bytes, same structure + // Format: [0x04, 0x000C, 0x02(00 00 00 02) 0x03(01), 0x03(00), 0x03(01), 0x18 => bool_yankdup] + + bytes memory code = hex"04000C020000000203010300030118"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x0C => length=12 + // 0x02 0x00000002 => INT_LITERAL(0x02) + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x03 0x00 => BOOL_LITERAL(False) + // 0x03 0x01 => BOOL_LITERAL(True) + // 0x18 => BOOL_YANKDUP + + // We'll parse offset=0, length=15 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 15, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be [True, False, True, True] bool stack + assertEq(finalBoolStack.length, 4, "finalBoolStack should have length 4"); + assertEq(finalBoolStack[0], true, "Expected to be True as first value from bool literal"); + assertEq(finalBoolStack[1], false, "Expected to be False as second value from bool literal"); + assertEq(finalBoolStack[2], true, "Expected to be True as third value from bool literal"); + assertEq(finalBoolStack[3], true, "Expected to be True as copied value at index 2, which is index 0 for an array"); + } + + /** + * @notice Test pushing BOOL_RAND => boolStack[True] or boolStack[False] + * Result should be either False or True on top of bool stack. + */ + function test_BoolRand() public { + // We'll produce 4 total bytes, same structure + // Format: [0x04, 0x0001, 0x1A => bool_rand] + + bytes memory code = hex"0400011A"; + // Breaking it down: + // 0x04 => SUBLIST + // 0x00 0x01 => length=1 + // 0x1A => BOOL_RAND + + // We'll parse offset=0, length=4 + uint256 sublistDesc = interpreter.makeDescriptor( + Push3Interpreter.CodeTag.SUBLIST, + 0, // offset + 4, // length + 0 + ); + + uint256[] memory initCodeStack = new uint256[](0); + uint256[] memory initExecStack = new uint256[](1); + initExecStack[0] = sublistDesc; + + int256[] memory initIntStack = new int256[](0); + bool[] memory initBoolStack = new bool[](0); + + // Run + ( + , + , + , + bool[] memory finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + + // Check final result => should be random bool on top of bool stack + assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], false, "Expected to be False for default prevrandao, timestamp and msg.sender"); + // move to another timestamp to get different random result + vm.warp(1736055577); + ( + , + , + , + finalBoolStack + ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); + assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1"); + assertEq(finalBoolStack[0], true, "Expected to be True for configured timestamp and default prevrandao, msg.sender"); + } } -- 2.34.1