From b3b851c29e63ccc89dcea0975b687020d370d0da Mon Sep 17 00:00:00 2001 From: varun-doshi Date: Tue, 10 Sep 2024 01:10:19 +0530 Subject: [PATCH 1/5] added 6 opcodes --- src/script/engine.zig | 87 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/script/engine.zig b/src/script/engine.zig index b38f750..8a49d96 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -112,6 +112,12 @@ pub const Engine = struct { 0x61 => try self.opNop(), 0x69 => try self.opVerify(), 0x6a => try self.opReturn(), + 0x6d => try self.op2Drop(), + 0x6e => try self.op2Dup(), + 0x6f => try self.op3Dup(), + 0x73 => self.opIfDup(), + 0x74 => self.opDepth(), + 0x75 => try self.opDrop(), 0x76 => try self.opDup(), 0x87 => try self.opEqual(), 0x88 => try self.opEqualVerify(), @@ -234,6 +240,9 @@ pub const Engine = struct { _ = self; } + /// OP_IF: If the top stack value is not False, the statements are executed. The top stack value is removed. + /// + /// # Returns /// OP_VERIFY: Verify the top stack value /// /// # Returns @@ -255,6 +264,84 @@ pub const Engine = struct { return error.EarlyReturn; } + /// OP_2DROP: Drops top 2 stack items + /// + /// # Returns + /// - "EngineError.StackUnderflow": if initial stack length < 2 + fn op2Drop(self: *Engine) !void { + if (self.stack.len() < 2) { + return error.StackUnderflow; + } + try self.stack.pop(); + try self.stack.pop(); + } + + /// OP_2DUP: Duplicates top 2 stack item + /// + /// # Returns + /// - "EngineError.StackUnderflow": if initial stack length < 2 + fn op2Dup(self: *Engine) !void { + if (self.stack.len() < 2) { + return error.StackUnderflow; + } + const second_item = try self.stack.peek(1); + const first_item = try self.stack.peek(0); + try self.stack.pushByteArray(second_item); + try self.stack.pushByteArray(first_item); + } + + /// OP_3DUP: Duplicates top 3 stack item + /// + /// # Returns + /// - "EngineError.StackUnderflow": if initial stack length < 3 + fn op3Dup(self: *Engine) !void { + if (self.stack.len() < 3) { + return error.StackUnderflow; + } + const third_item = try self.stack.peek(2); + const second_item = try self.stack.peek(1); + const first_item = try self.stack.peek(0); + try self.stack.pushByteArray(third_item); + try self.stack.pushByteArray(second_item); + try self.stack.pushByteArray(first_item); + } + + /// OP_DEPTH: Puts the number of stack items onto the stack. + /// + /// # Returns + /// - "EngineError.StackUnderflow": if initial stack length == 0 + fn opDepth(self: *Engine) !void { + if (self.stack.len() == 0) { + return error.StackUnderflow; + } + try self.stack.pushInt(try self.stack.len()); + } + + /// OP_IFDUP: If the top stack value is not 0, duplicate it. + /// + /// # Returns + /// - "EngineError.StackUnderflow": if initial stack length < 1 + fn opIfDup(self: *Engine) !void { + if (self.stack.len() < 1) { + return error.StackUnderflow; + } + const value = try self.stack.peek(0); + if (value != 0) { + try self.stack.pushByteArray(value); + } + } + + /// OP_DROP: Drops top stack item + /// + /// # Returns + /// - "EngineError.StackUnderflow": if initial stack length < 1 + fn opDrop(self: *Engine) !void { + if (self.stack.len() < 3) { + return error.StackUnderflow; + } + try self.stack.pop(); + } + /// OP_DUP: Duplicate the top stack item /// /// # Returns From 29395e2b246efa6d0bf05aa5e75f74f6fdc77bc1 Mon Sep 17 00:00:00 2001 From: varun-doshi Date: Tue, 10 Sep 2024 18:46:57 +0530 Subject: [PATCH 2/5] update:added tests --- .gitignore | 1 + src/script/engine.zig | 156 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 145 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 14dd6ef..fc3fc59 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ zig-out .bitcoin +.vscode \ No newline at end of file diff --git a/src/script/engine.zig b/src/script/engine.zig index 8a49d96..4543874 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -102,7 +102,7 @@ pub const Engine = struct { /// - `EngineError`: If an error occurs during execution fn executeOpcode(self: *Engine, opcode: u8) !void { self.log("Executing opcode: 0x{x:0>2}\n", .{opcode}); - switch (opcode) { + try switch (opcode) { 0x00...0x4b => try self.pushData(opcode), 0x4c => try self.opPushData1(), 0x4d => try self.opPushData2(), @@ -119,6 +119,8 @@ pub const Engine = struct { 0x74 => self.opDepth(), 0x75 => try self.opDrop(), 0x76 => try self.opDup(), + 0x77 => try self.opNip(), + 0x78 => try self.opOver(), 0x87 => try self.opEqual(), 0x88 => try self.opEqualVerify(), 0x8b => try arithmetic.op1Add(self), @@ -144,7 +146,7 @@ pub const Engine = struct { 0xa9 => try self.opHash160(), 0xac => try self.opCheckSig(), else => return error.UnknownOpcode, - } + }; } // Opcode implementations @@ -272,8 +274,8 @@ pub const Engine = struct { if (self.stack.len() < 2) { return error.StackUnderflow; } - try self.stack.pop(); - try self.stack.pop(); + _ = try self.stack.pop(); + _ = try self.stack.pop(); } /// OP_2DUP: Duplicates top 2 stack item @@ -284,10 +286,11 @@ pub const Engine = struct { if (self.stack.len() < 2) { return error.StackUnderflow; } - const second_item = try self.stack.peek(1); - const first_item = try self.stack.peek(0); - try self.stack.pushByteArray(second_item); + + const second_item = try self.stack.peek(0); + const first_item = try self.stack.peek(1); try self.stack.pushByteArray(first_item); + try self.stack.pushByteArray(second_item); } /// OP_3DUP: Duplicates top 3 stack item @@ -314,10 +317,12 @@ pub const Engine = struct { if (self.stack.len() == 0) { return error.StackUnderflow; } - try self.stack.pushInt(try self.stack.len()); + const stack_length = self.stack.len(); + const u8_stack_length: u8 = @intCast(stack_length); + try self.stack.pushByteArray(&[_]u8{u8_stack_length}); } - /// OP_IFDUP: If the top stack value is not 0, duplicate it. + /// OP_IFDUP: If the top stack value is not 0, duplicate itp /// /// # Returns /// - "EngineError.StackUnderflow": if initial stack length < 1 @@ -326,7 +331,7 @@ pub const Engine = struct { return error.StackUnderflow; } const value = try self.stack.peek(0); - if (value != 0) { + if (value.len != 1 or value[0] != 0) { try self.stack.pushByteArray(value); } } @@ -336,10 +341,10 @@ pub const Engine = struct { /// # Returns /// - "EngineError.StackUnderflow": if initial stack length < 1 fn opDrop(self: *Engine) !void { - if (self.stack.len() < 3) { + if (self.stack.len() < 1) { return error.StackUnderflow; } - try self.stack.pop(); + _ = try self.stack.pop(); } /// OP_DUP: Duplicate the top stack item @@ -447,3 +452,130 @@ test "Script execution - OP_RETURN" { try std.testing.expectEqualSlices(u8, &[_]u8{1}, item); } } + +test "Script execution - OP_1 OP_1 OP_1 OP_2Drop" { + const allocator = std.testing.allocator; + + // Simple script: OP_1 OP_1 OP_EQUAL + const script_bytes = [_]u8{ 0x51, 0x51, 0x51, 0x6d }; + const script = Script.init(&script_bytes); + + var engine = Engine.init(allocator, script, .{}); + defer engine.deinit(); + + try engine.execute(); + + // Ensure the stack is empty after popping the result + try std.testing.expectEqual(@as(usize, 1), engine.stack.len()); +} + +test "Script execution - OP_1 OP_2 OP_2Dup" { + const allocator = std.testing.allocator; + + // Simple script: OP_1 OP_1 OP_EQUAL + const script_bytes = [_]u8{ 0x51, 0x52, 0x6e }; + const script = Script.init(&script_bytes); + + var engine = Engine.init(allocator, script, .{}); + defer engine.deinit(); + + try engine.execute(); + const element0 = try engine.stack.peek(0); + const element1 = try engine.stack.peek(1); + const element2 = try engine.stack.peek(2); + const element3 = try engine.stack.peek(3); + + // Ensure the stack is empty after popping the result + try std.testing.expectEqual(@as(usize, 4), engine.stack.len()); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element0); + try std.testing.expectEqualSlices(u8, &[_]u8{1}, element1); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element2); + try std.testing.expectEqualSlices(u8, &[_]u8{1}, element3); +} + +test "Script execution - OP_1 OP_2 OP_3 OP_4 OP_3Dup" { + const allocator = std.testing.allocator; + + // Simple script: OP_1 OP_1 OP_EQUAL + const script_bytes = [_]u8{ 0x51, 0x52, 0x53, 0x54, 0x6f }; + const script = Script.init(&script_bytes); + + var engine = Engine.init(allocator, script, .{}); + defer engine.deinit(); + + try engine.execute(); + const element0 = try engine.stack.peek(0); + const element1 = try engine.stack.peek(1); + const element2 = try engine.stack.peek(2); + const element3 = try engine.stack.peek(3); + const element4 = try engine.stack.peek(4); + const element5 = try engine.stack.peek(5); + const element6 = try engine.stack.peek(6); + + // Ensure the stack is empty after popping the result + try std.testing.expectEqual(@as(usize, 7), engine.stack.len()); + try std.testing.expectEqualSlices(u8, &[_]u8{4}, element0); + try std.testing.expectEqualSlices(u8, &[_]u8{3}, element1); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element2); + try std.testing.expectEqualSlices(u8, &[_]u8{4}, element3); + try std.testing.expectEqualSlices(u8, &[_]u8{3}, element4); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element5); + try std.testing.expectEqualSlices(u8, &[_]u8{1}, element6); +} + +test "Script execution - OP_1 OP_2 OP_IFDUP" { + const allocator = std.testing.allocator; + + // Simple script: OP_1 OP_1 OP_EQUAL + const script_bytes = [_]u8{ 0x51, 0x52, 0x73 }; + const script = Script.init(&script_bytes); + + var engine = Engine.init(allocator, script, .{}); + defer engine.deinit(); + + try engine.execute(); + const element0 = try engine.stack.peek(0); + const element1 = try engine.stack.peek(1); + + // Ensure the stack is empty after popping the result + try std.testing.expectEqual(@as(usize, 3), engine.stack.len()); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element0); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element1); +} + +test "Script execution - OP_1 OP_2 OP_DEPTH" { + const allocator = std.testing.allocator; + + // Simple script: OP_1 OP_1 OP_EQUAL + const script_bytes = [_]u8{ 0x51, 0x52, 0x74 }; + const script = Script.init(&script_bytes); + + var engine = Engine.init(allocator, script, .{}); + defer engine.deinit(); + + try engine.execute(); + const element0 = try engine.stack.peek(0); + const element1 = try engine.stack.peek(1); + + // Ensure the stack is empty after popping the result + try std.testing.expectEqual(@as(usize, 3), engine.stack.len()); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element0); + try std.testing.expectEqualSlices(u8, &[_]u8{2}, element1); +} +test "Script execution - OP_1 OP_2 OP_DROP" { + const allocator = std.testing.allocator; + + // Simple script: OP_1 OP_1 OP_EQUAL + const script_bytes = [_]u8{ 0x51, 0x52, 0x75 }; + const script = Script.init(&script_bytes); + + var engine = Engine.init(allocator, script, .{}); + defer engine.deinit(); + + try engine.execute(); + const element0 = try engine.stack.peek(0); + + // Ensure the stack is empty after popping the result + try std.testing.expectEqual(@as(usize, 1), engine.stack.len()); + try std.testing.expectEqualSlices(u8, &[_]u8{1}, element0); +} From df96a48ae7cefc7eb38b76c388e9e4f3056325c5 Mon Sep 17 00:00:00 2001 From: varun-doshi Date: Tue, 10 Sep 2024 18:54:31 +0530 Subject: [PATCH 3/5] clean up --- src/script/engine.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/script/engine.zig b/src/script/engine.zig index 4543874..59a45ad 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -102,7 +102,7 @@ pub const Engine = struct { /// - `EngineError`: If an error occurs during execution fn executeOpcode(self: *Engine, opcode: u8) !void { self.log("Executing opcode: 0x{x:0>2}\n", .{opcode}); - try switch (opcode) { + switch (opcode) { 0x00...0x4b => try self.pushData(opcode), 0x4c => try self.opPushData1(), 0x4d => try self.opPushData2(), @@ -119,8 +119,6 @@ pub const Engine = struct { 0x74 => self.opDepth(), 0x75 => try self.opDrop(), 0x76 => try self.opDup(), - 0x77 => try self.opNip(), - 0x78 => try self.opOver(), 0x87 => try self.opEqual(), 0x88 => try self.opEqualVerify(), 0x8b => try arithmetic.op1Add(self), @@ -146,7 +144,7 @@ pub const Engine = struct { 0xa9 => try self.opHash160(), 0xac => try self.opCheckSig(), else => return error.UnknownOpcode, - }; + } } // Opcode implementations From 3282a87a9373f5cbac58dd20c2d05c6810ec4bd9 Mon Sep 17 00:00:00 2001 From: varun-doshi Date: Tue, 10 Sep 2024 19:06:50 +0530 Subject: [PATCH 4/5] failing CI test fix --- src/script/engine.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/engine.zig b/src/script/engine.zig index 59a45ad..85817fe 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -102,7 +102,7 @@ pub const Engine = struct { /// - `EngineError`: If an error occurs during execution fn executeOpcode(self: *Engine, opcode: u8) !void { self.log("Executing opcode: 0x{x:0>2}\n", .{opcode}); - switch (opcode) { + try switch (opcode) { 0x00...0x4b => try self.pushData(opcode), 0x4c => try self.opPushData1(), 0x4d => try self.opPushData2(), @@ -144,7 +144,7 @@ pub const Engine = struct { 0xa9 => try self.opHash160(), 0xac => try self.opCheckSig(), else => return error.UnknownOpcode, - } + }; } // Opcode implementations From 26682576f9564ece02401ec580dc544d7096f262 Mon Sep 17 00:00:00 2001 From: varun-doshi Date: Tue, 10 Sep 2024 20:14:04 +0530 Subject: [PATCH 5/5] fixed:CI build fail --- src/script/engine.zig | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/script/engine.zig b/src/script/engine.zig index 85817fe..b0a5afd 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -272,8 +272,11 @@ pub const Engine = struct { if (self.stack.len() < 2) { return error.StackUnderflow; } - _ = try self.stack.pop(); - _ = try self.stack.pop(); + const a = try self.stack.pop(); + const b = try self.stack.pop(); + + defer self.allocator.free(a); + defer self.allocator.free(b); } /// OP_2DUP: Duplicates top 2 stack item @@ -342,7 +345,8 @@ pub const Engine = struct { if (self.stack.len() < 1) { return error.StackUnderflow; } - _ = try self.stack.pop(); + const a = try self.stack.pop(); + defer self.allocator.free(a); } /// OP_DUP: Duplicate the top stack item @@ -560,6 +564,7 @@ test "Script execution - OP_1 OP_2 OP_DEPTH" { try std.testing.expectEqualSlices(u8, &[_]u8{2}, element0); try std.testing.expectEqualSlices(u8, &[_]u8{2}, element1); } + test "Script execution - OP_1 OP_2 OP_DROP" { const allocator = std.testing.allocator; @@ -571,9 +576,10 @@ test "Script execution - OP_1 OP_2 OP_DROP" { defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(@as(usize, 1), engine.stack.len()); + const element0 = try engine.stack.peek(0); // Ensure the stack is empty after popping the result - try std.testing.expectEqual(@as(usize, 1), engine.stack.len()); try std.testing.expectEqualSlices(u8, &[_]u8{1}, element0); }