From 315a8a2fd150ae6b8090bf564311496b92ef65c4 Mon Sep 17 00:00:00 2001 From: Varun Doshi <61531351+varun-doshi@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:28:21 +0530 Subject: [PATCH] feat: create a ScriptBuilder (#113) --- src/script/engine.zig | 364 ++++++++++++++++-------------- src/script/opcodes/arithmetic.zig | 271 +++++++++++----------- src/script/scriptBuilder.zig | 318 ++++++++++++++++++++++++++ 3 files changed, 639 insertions(+), 314 deletions(-) create mode 100644 src/script/scriptBuilder.zig diff --git a/src/script/engine.zig b/src/script/engine.zig index a38dcd2..9460d4b 100644 --- a/src/script/engine.zig +++ b/src/script/engine.zig @@ -8,6 +8,7 @@ const arithmetic = @import("opcodes/arithmetic.zig"); const Opcode = @import("opcodes/constant.zig").Opcode; const isUnnamedPushNDataOpcode = @import("opcodes/constant.zig").isUnnamedPushNDataOpcode; const EngineError = @import("lib.zig").EngineError; +const ScriptBuilder = @import("scriptBuilder.zig").ScriptBuilder; const sha1 = std.crypto.hash.Sha1; const ripemd160 = @import("bitcoin-primitives").hashes.Ripemd160; const Sha256 = std.crypto.hash.sha2.Sha256; @@ -319,10 +320,10 @@ pub const Engine = struct { fn op2Rot(self: *Engine) !void { const start = self.stack.items.items.len - 1; - try self.stack.swap(start-5, start-3); - try self.stack.swap(start-4, start-2); - try self.stack.swap(start-3, start-1); - try self.stack.swap(start-2, start); + try self.stack.swap(start - 5, start - 3); + try self.stack.swap(start - 4, start - 2); + try self.stack.swap(start - 3, start - 1); + try self.stack.swap(start - 2, start); } // OP_2OVER: Copies the pair of items two spaces back in the stack to the front @@ -369,9 +370,9 @@ pub const Engine = struct { /// OP_ROT: The top three items on the stack are rotated to the left fn opRot(self: *Engine) !void { const start = self.stack.items.items.len - 1; - - try self.stack.swap(start-2, start-1); - try self.stack.swap(start-1, start); + + try self.stack.swap(start - 2, start - 1); + try self.stack.swap(start - 1, start); } /// OP_NIP: Removes the second-to-top stack item @@ -608,17 +609,16 @@ test "opSha1 function test" { }; for (test_cases) |case| { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - const script_bytes = [_]u8{Opcode.OP_SHA1.toBytes()}; - const script = Script.init(&script_bytes); + // Push the input onto the stack + _ = try sb.addData(case.input); + _ = try sb.addOpcode(Opcode.OP_SHA1); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input onto the stack - try engine.stack.pushByteArray(case.input); - // Call opSha1 try engine.execute(); @@ -636,13 +636,15 @@ test "opSha1 function test" { } test "Script execution - OP_RETURN" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Script: OP_1 OP_RETURN OP_2 - const script_bytes = [_]u8{ 0x51, 0x6a, 0x52 }; - const script = Script.init(&script_bytes); + _ = try sb.addInt(1); + _ = try sb.addOpcode(Opcode.OP_RETURN); + _ = try sb.addInt(2); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try std.testing.expectError(error.EarlyReturn, engine.execute()); @@ -653,25 +655,20 @@ test "Script execution - OP_RETURN" { // Check the item on the stack (should be 1) { const item = try engine.stack.pop(); - defer allocator.free(item); + defer engine.allocator.free(item); try std.testing.expectEqualSlices(u8, &[_]u8{1}, item); } } test "Script execution - OP_TOALTSTACK OP_FROMALTSTACK" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_TOALTSTACK OP_FROMALTSTACK - const script_bytes = [_]u8{ - Opcode.OP_1.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_TOALTSTACK.toBytes(), - Opcode.OP_TOALTSTACK.toBytes(), - Opcode.OP_FROMALTSTACK.toBytes(), - }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[2]i32{ 1, 2 }); + _ = try sb.addOpcodes(&[3]Opcode{ Opcode.OP_TOALTSTACK, Opcode.OP_TOALTSTACK, Opcode.OP_FROMALTSTACK }); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); @@ -681,13 +678,14 @@ test "Script execution - OP_TOALTSTACK OP_FROMALTSTACK" { } test "Script execution - OP_1 OP_1 OP_1 OP_2Drop" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_1 OP_EQUAL - const script_bytes = [_]u8{ 0x51, 0x51, 0x51, 0x6d }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[3]i32{ 1, 1, 1 }); + _ = try sb.addOpcode(Opcode.OP_2DROP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); @@ -697,13 +695,14 @@ test "Script execution - OP_1 OP_1 OP_1 OP_2Drop" { } test "Script execution - OP_1 OP_2 OP_2Dup" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - // Simple script: OP_1 OP_1 OP_EQUAL - const script_bytes = [_]u8{ 0x51, 0x52, 0x6e }; - const script = Script.init(&script_bytes); + // Simple script: OP_1 OP_2 OP_2DUP + _ = try sb.addInts(&[2]i32{ 1, 2 }); + _ = try sb.addOpcode(Opcode.OP_2DUP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); @@ -721,16 +720,18 @@ test "Script execution - OP_1 OP_2 OP_2Dup" { } test "Script execution - OP_1 OP_2 OP_3 OP_4 OP_3Dup" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - // Simple script: OP_1 OP_1 OP_EQUAL - const script_bytes = [_]u8{ 0x51, 0x52, 0x53, 0x54, 0x6f }; - const script = Script.init(&script_bytes); + // Simple script: OP_1 OP_1 OP_3DUP + _ = try sb.addInts(&[4]i32{ 1, 2, 3, 4 }); + _ = try sb.addOpcode(Opcode.OP_3DUP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + const element0 = try engine.stack.peekInt(0); const element1 = try engine.stack.peekInt(1); const element2 = try engine.stack.peekInt(2); @@ -751,21 +752,13 @@ test "Script execution - OP_1 OP_2 OP_3 OP_4 OP_3Dup" { } test "Script execution - OP_2ROT" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - const script_bytes = [_]u8{ - Opcode.OP_0.toBytes(), - Opcode.OP_1.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_3.toBytes(), - Opcode.OP_4.toBytes(), - Opcode.OP_5.toBytes(), - Opcode.OP_6.toBytes(), - Opcode.OP_2ROT.toBytes(), - }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[7]i32{ 0, 1, 2, 3, 4, 5, 6 }); + _ = try sb.addOpcode(Opcode.OP_2ROT); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); @@ -789,22 +782,31 @@ test "Script execution - OP_2ROT" { } test "Script execution - OP_1 OP_2 OP_3 OP_4 OP_2OVER" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - // Simple script: OP_1 OP_2 OP_3 OP_4 OP_2OVER - const script_bytes = [_]u8{ - Opcode.OP_1.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_3.toBytes(), - Opcode.OP_4.toBytes(), - Opcode.OP_2OVER.toBytes(), - }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[4]i32{ 1, 2, 3, 4 }); + _ = try sb.addOpcode(Opcode.OP_2OVER); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + + // Simple script: OP_1 OP_2 OP_3 OP_4 OP_2OVER + // const script_bytes = [_]u8{ + // Opcode.OP_1.toBytes(), + // Opcode.OP_2.toBytes(), + // Opcode.OP_3.toBytes(), + // Opcode.OP_4.toBytes(), + // Opcode.OP_2OVER.toBytes(), + // }; + // const script = Script.init(&script_bytes); + + // var engine = Engine.init(allocator, script, .{}); + // defer engine.deinit(); + + // try engine.execute(); try std.testing.expectEqual(6, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -823,22 +825,18 @@ test "Script execution - OP_1 OP_2 OP_3 OP_4 OP_2OVER" { } test "Script execution - OP_1 OP_2 OP_3 OP_4 OP_2SWAP" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_4 OP_2SWAP - const script_bytes = [_]u8{ - Opcode.OP_1.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_3.toBytes(), - Opcode.OP_4.toBytes(), - Opcode.OP_2SWAP.toBytes(), - }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[4]i32{ 1, 2, 3, 4 }); + _ = try sb.addOpcode(Opcode.OP_2SWAP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(4, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -853,16 +851,18 @@ test "Script execution - OP_1 OP_2 OP_3 OP_4 OP_2SWAP" { } test "Script execution - OP_1 OP_2 OP_IFDUP" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OOP_1 OP_2 OP_IFDUP - const script_bytes = [_]u8{ 0x51, 0x52, 0x73 }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[2]i32{ 1, 2 }); + _ = try sb.addOpcode(Opcode.OP_IFDUP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + const element0 = try engine.stack.peekInt(0); const element1 = try engine.stack.peekInt(1); @@ -872,18 +872,14 @@ test "Script execution - OP_1 OP_2 OP_IFDUP" { } test "Script execution - OP_OVER" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_OVER - const script_bytes = [_]u8{ - Opcode.OP_1.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_3.toBytes(), - Opcode.OP_OVER.toBytes(), - }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[3]i32{ 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_OVER); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); @@ -904,16 +900,18 @@ test "Script execution - OP_OVER" { } test "Script execution - OP_1 OP_2 OP_DEPTH" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - // Simple script: OP_1 OP_2 OP_DEPTH - const script_bytes = [_]u8{ 0x51, 0x52, 0x74 }; - const script = Script.init(&script_bytes); + // Simple script: OP_1 OP_2 OP_3 OP_4 OP_2SWAP + _ = try sb.addInts(&[2]i32{ 1, 2 }); + _ = try sb.addOpcode(Opcode.OP_DEPTH); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + const element0 = try engine.stack.peekInt(0); const element1 = try engine.stack.peekInt(1); @@ -923,16 +921,18 @@ test "Script execution - OP_1 OP_2 OP_DEPTH" { } test "Script execution - OP_1 OP_2 OP_DROP" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_DROP - const script_bytes = [_]u8{ 0x51, 0x52, 0x75 }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[2]i32{ 1, 2 }); + _ = try sb.addOpcode(Opcode.OP_DROP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(1, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -941,18 +941,13 @@ test "Script execution - OP_1 OP_2 OP_DROP" { } test "Script execution - OP_ROT" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - const script_bytes = [_]u8{ - Opcode.OP_0.toBytes(), - Opcode.OP_1.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_3.toBytes(), - Opcode.OP_ROT.toBytes(), - }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[4]i32{ 0, 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_ROT); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); @@ -970,22 +965,30 @@ test "Script execution - OP_ROT" { } test "Script execution - OP_PICK" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_2 OP_PICK - const script_bytes = [_]u8{ - Opcode.OP_1.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_3.toBytes(), - Opcode.OP_2.toBytes(), - Opcode.OP_PICK.toBytes(), - }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[4]i32{ 1, 2, 3, 2 }); + _ = try sb.addOpcode(Opcode.OP_PICK); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + // const script_bytes = [_]u8{ + // Opcode.OP_1.toBytes(), + // Opcode.OP_2.toBytes(), + // Opcode.OP_3.toBytes(), + // Opcode.OP_2.toBytes(), + // Opcode.OP_PICK.toBytes(), + // }; + // const script = Script.init(&script_bytes); + + // var engine = Engine.init(allocator, script, .{}); + // defer engine.deinit(); + + // try engine.execute(); try std.testing.expectEqual(4, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -1000,44 +1003,48 @@ test "Script execution - OP_PICK" { } test "Script execution - OP_DISABLED" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 1); + defer sb.deinit(); - // Simple script to run a disabled opcode - const script_bytes = [_]u8{0x95}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[1]u8{0x95}); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); + try engine.execute(); + // Expect an error when running a disabled opcode try std.testing.expectError(error.DisabledOpcode, engine.opDisabled()); } test "Script execution - OP_INVALID" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 1); + defer sb.deinit(); - // Simple script to run an invalid opcode - const script_bytes = [_]u8{0xff}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[1]u8{0xff}); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); + try engine.execute(); + // Expect an error when running an invalid opcode try std.testing.expectError(error.UnknownOpcode, engine.opInvalid()); } test "Script execution OP_1 OP_2 OP_3 OP_NIP" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_NIP - const script_bytes = [_]u8{ 0x51, 0x52, 0x53, 0x77 }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[3]i32{ 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_NIP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(2, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -1049,16 +1056,18 @@ test "Script execution OP_1 OP_2 OP_3 OP_NIP" { } test "Script execution OP_1 OP_2 OP_3 OP_OVER" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_OVER - const script_bytes = [_]u8{ 0x51, 0x52, 0x53, 0x78 }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[3]i32{ 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_OVER); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(4, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -1069,16 +1078,19 @@ test "Script execution OP_1 OP_2 OP_3 OP_OVER" { } test "Script execution OP_1 OP_2 OP_3 OP_2 OP_ROLL" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_2 OP_ROLL - const script_bytes = [_]u8{ Opcode.OP_1.toBytes(), Opcode.OP_2.toBytes(), Opcode.OP_3.toBytes(), Opcode.OP_2.toBytes(), Opcode.OP_ROLL.toBytes() }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[4]i32{ 1, 2, 3, 2 }); - var engine = Engine.init(allocator, script, .{}); + _ = try sb.addOpcode(Opcode.OP_ROLL); + + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(3, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -1091,16 +1103,18 @@ test "Script execution OP_1 OP_2 OP_3 OP_2 OP_ROLL" { } test "Script execution OP_1 OP_2 OP_3 OP_SWAP" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_SWAP - const script_bytes = [_]u8{ 0x51, 0x52, 0x53, 0x7c }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[3]i32{ 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_SWAP); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(3, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -1111,16 +1125,18 @@ test "Script execution OP_1 OP_2 OP_3 OP_SWAP" { } test "Script execution OP_1 OP_2 OP_3 OP_TUCK" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); // Simple script: OP_1 OP_2 OP_3 OP_TUCK - const script_bytes = [_]u8{ Opcode.OP_1.toBytes(), Opcode.OP_2.toBytes(), Opcode.OP_3.toBytes(), Opcode.OP_TUCK.toBytes() }; - const script = Script.init(&script_bytes); + _ = try sb.addInts(&[3]i32{ 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_TUCK); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(4, engine.stack.len()); const element0 = try engine.stack.peekInt(0); @@ -1135,16 +1151,18 @@ test "Script execution OP_1 OP_2 OP_3 OP_TUCK" { } test "Script execution OP_1 OP_2 OP_3 OP_SIZE" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - // Simple script: OP_1 OP_1 OP_EQUAL - const script_bytes = [_]u8{ 0x51, 0x52, 0x53, 0x82 }; - const script = Script.init(&script_bytes); + // Simple script: OP_1 OP_2 OP_3 OP_SIZE + _ = try sb.addInts(&[3]i32{ 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_SIZE); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); try engine.execute(); + try std.testing.expectEqual(4, engine.stack.len()); const element0 = try engine.stack.popInt(); @@ -1166,18 +1184,15 @@ test "Script execution OP_RIPEMD160" { }; for (test_cases) |case| { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - const script_bytes = [_]u8{Opcode.OP_RIPEMD160.toBytes()}; - const script = Script.init(&script_bytes); + _ = try sb.addData(case.input); + _ = try sb.addOpcode(Opcode.OP_RIPEMD160); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input onto the stack - try engine.stack.pushByteArray(case.input); - - // Call opRipemd160 try engine.execute(); // Pop the result from the stack @@ -1194,13 +1209,15 @@ test "Script execution OP_RIPEMD160" { } test "Script execution - OP_SHA256" { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - const script_bytes = [_]u8{ Opcode.OP_1.toBytes(), Opcode.OP_SHA256.toBytes() }; - const script = Script.init(&script_bytes); + // Simple script: OP_1 OP_2 OP_3 OP_SIZE + _ = try sb.addInt(1); + _ = try sb.addOpcode(Opcode.OP_SHA256); - var engine = Engine.init(allocator, script, .{}); - defer engine.deinit(); // Ensure engine is deinitialized and memory is freed + var engine = try sb.build(); + defer engine.deinit(); try engine.execute(); @@ -1228,17 +1245,16 @@ test "Script execution = OP_HASH160" { }; for (test_cases) |case| { - const allocator = std.testing.allocator; + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - const script_bytes = [_]u8{Opcode.OP_HASH160.toBytes()}; - const script = Script.init(&script_bytes); + // Push the input onto the stack + _ = try sb.addData(case.input); + _ = try sb.addOpcode(Opcode.OP_HASH160); - var engine = Engine.init(allocator, script, .{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input onto the stack - try engine.stack.pushByteArray(case.input); - // Call opHash160 try engine.execute(); diff --git a/src/script/opcodes/arithmetic.zig b/src/script/opcodes/arithmetic.zig index a28aa13..7b33cd8 100644 --- a/src/script/opcodes/arithmetic.zig +++ b/src/script/opcodes/arithmetic.zig @@ -5,6 +5,7 @@ const Script = @import("../lib.zig").Script; const ScriptNum = @import("../lib.zig").ScriptNum; const ScriptFlags = @import("../lib.zig").ScriptFlags; const StackError = @import("../stack.zig").StackError; +const ScriptBuilder = @import("../scriptBuilder.zig").ScriptBuilder; /// Add 1 to the top stack item pub fn op1Add(engine: *Engine) !void { @@ -171,7 +172,6 @@ pub fn opWithin(engine: *Engine) !void { } test "OP_1ADD operation" { - const allocator = testing.allocator; // Test cases const normalTestCases = [_]struct { @@ -192,13 +192,14 @@ test "OP_1ADD operation" { }; for (normalTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input value onto the stack try engine.stack.pushInt(tc.input); @@ -214,13 +215,13 @@ test "OP_1ADD operation" { } for (overflowTestCases) |tc| { // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); - var engine = Engine.init(allocator, script, ScriptFlags{}); - defer engine.deinit(); + _ = try sb.addData(&[_]u8{0x00}); - // Push the input values onto the stack + var engine = try sb.build(); + defer engine.deinit(); try engine.stack.pushInt(tc.input); // Execute OP_1ADD @@ -237,7 +238,6 @@ test "OP_1ADD operation" { } test "OP_1SUB operation" { - const allocator = testing.allocator; // Test cases const normalTestCases = [_]struct { @@ -259,13 +259,14 @@ test "OP_1SUB operation" { }; for (normalTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input value onto the stack try engine.stack.pushInt(tc.input); @@ -280,14 +281,15 @@ test "OP_1SUB operation" { try testing.expectEqual(0, engine.stack.len()); } for (overflowTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.input); // Execute OP_1SUB @@ -304,8 +306,6 @@ test "OP_1SUB operation" { } test "OP_NEGATE operation" { - const allocator = testing.allocator; - // Test cases const normalTestCases = [_]struct { input: i32, @@ -321,13 +321,14 @@ test "OP_NEGATE operation" { }; for (normalTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input value onto the stack try engine.stack.pushInt(tc.input); @@ -344,8 +345,6 @@ test "OP_NEGATE operation" { } test "OP_ABS operation" { - const allocator = testing.allocator; - // Test cases const normalTestCases = [_]struct { input: i32, @@ -360,13 +359,14 @@ test "OP_ABS operation" { .{ .input = ScriptNum.MIN, .expected = ScriptNum.MAX }, }; for (normalTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input value onto the stack try engine.stack.pushInt(tc.input); @@ -383,7 +383,6 @@ test "OP_ABS operation" { } test "OP_NOT operation" { - const allocator = testing.allocator; // Test cases const testCases = [_]struct { @@ -400,13 +399,14 @@ test "OP_NOT operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input value onto the stack try engine.stack.pushInt(tc.input); @@ -423,8 +423,6 @@ test "OP_NOT operation" { } test "OP_0NOTEQUAL operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { input: i32, @@ -440,13 +438,14 @@ test "OP_0NOTEQUAL operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - // Push the input value onto the stack try engine.stack.pushInt(tc.input); @@ -463,8 +462,6 @@ test "OP_0NOTEQUAL operation" { } test "OP_ADD operation" { - const allocator = testing.allocator; - // Test cases const normalTestCases = [_]struct { a: i32, @@ -491,14 +488,15 @@ test "OP_ADD operation" { }; for (normalTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -513,14 +511,15 @@ test "OP_ADD operation" { try testing.expectEqual(0, engine.stack.len()); } for (overflowTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -538,7 +537,6 @@ test "OP_ADD operation" { } test "OP_SUB operation" { - const allocator = testing.allocator; // Test cases const normalTestCases = [_]struct { @@ -567,14 +565,15 @@ test "OP_SUB operation" { }; for (normalTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -589,14 +588,15 @@ test "OP_SUB operation" { try testing.expectEqual(0, engine.stack.len()); } for (overflowTestCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -614,7 +614,6 @@ test "OP_SUB operation" { } test "OP_BOOLOR operation" { - const allocator = testing.allocator; // Test cases const testCases = [_]struct { @@ -634,14 +633,15 @@ test "OP_BOOLOR operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -658,8 +658,6 @@ test "OP_BOOLOR operation" { } test "OP_NUMEQUAL operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -678,14 +676,15 @@ test "OP_NUMEQUAL operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -702,8 +701,6 @@ test "OP_NUMEQUAL operation" { } test "OP_NUMNOTEQUAL operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -722,14 +719,15 @@ test "OP_NUMNOTEQUAL operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -746,8 +744,6 @@ test "OP_NUMNOTEQUAL operation" { } test "OP_LESSTHAN operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -766,14 +762,15 @@ test "OP_LESSTHAN operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -790,8 +787,6 @@ test "OP_LESSTHAN operation" { } test "OP_GREATERTHAN operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -810,14 +805,15 @@ test "OP_GREATERTHAN operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -834,8 +830,6 @@ test "OP_GREATERTHAN operation" { } test "OP_LESSTHANOREQUAL operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -854,14 +848,15 @@ test "OP_LESSTHANOREQUAL operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -878,8 +873,6 @@ test "OP_LESSTHANOREQUAL operation" { } test "OP_GREATERTHANOREQUAL operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -898,14 +891,15 @@ test "OP_GREATERTHANOREQUAL operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -922,8 +916,6 @@ test "OP_GREATERTHANOREQUAL operation" { } test "OP_MIN operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -942,14 +934,15 @@ test "OP_MIN operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -966,8 +959,6 @@ test "OP_MIN operation" { } test "OP_MAX operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -986,14 +977,15 @@ test "OP_MAX operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); @@ -1010,8 +1002,6 @@ test "OP_MAX operation" { } test "OP_WITHIN operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { x: i32, @@ -1028,11 +1018,13 @@ test "OP_WITHIN operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); // Push the input values onto the stack @@ -1053,8 +1045,6 @@ test "OP_WITHIN operation" { } test "OP_NUMEQUALVERIFY operation" { - const allocator = testing.allocator; - // Test cases const testCases = [_]struct { a: i32, @@ -1073,14 +1063,15 @@ test "OP_NUMEQUALVERIFY operation" { }; for (testCases) |tc| { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + // Create a dummy script (content doesn't matter for this test) - const script_bytes = [_]u8{0x00}; - const script = Script.init(&script_bytes); + _ = try sb.addData(&[_]u8{0x00}); - var engine = Engine.init(allocator, script, ScriptFlags{}); + var engine = try sb.build(); defer engine.deinit(); - - // Push the input values onto the stack + // Push the input value onto the stack try engine.stack.pushInt(tc.a); try engine.stack.pushInt(tc.b); diff --git a/src/script/scriptBuilder.zig b/src/script/scriptBuilder.zig new file mode 100644 index 0000000..122e753 --- /dev/null +++ b/src/script/scriptBuilder.zig @@ -0,0 +1,318 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Script = @import("lib.zig").Script; +const StackError = @import("stack.zig").StackError; +pub const engine = @import("engine.zig"); +const Opcode = @import("./opcodes/constant.zig").Opcode; +const isUnnamedPushNDataOpcode = @import("opcodes/constant.zig").isUnnamedPushNDataOpcode; +const ScriptNum = @import("lib.zig").ScriptNum; +const asBool = Script.asBool; +const asInt = Script.asInt; +const testing = std.testing; + +/// Maximum script length in bytes +const MAX_SCRIPT_SIZE = 10000; + +/// ScriptBuilder Errors +const ScriptBuilderError = error{ ScriptTooLong, ScriptTooShort }; + +///ScriptBuilder is a library to generate easier scripts, useful for faster testing +pub const ScriptBuilder = struct { + /// Dynamic array holding the opcodes + script: std.ArrayList(u8), + + ///Memory Allocator + allocator: Allocator, + + /// Initialize a new ScriptBuilder + /// + /// # Arguments + /// - `allocator`: Memory allocator for managing engine resources + /// + /// # Returns + /// - `!ScriptBuilder`: use with `try` + pub fn new(allocator: Allocator, capacity: usize) !ScriptBuilder { + return ScriptBuilder{ + .script = try std.ArrayList(u8).initCapacity(allocator, capacity), + .allocator = allocator, + }; + } + + ///Push an OPcode to the ScriptBuilder + pub fn addOpcode(self: *ScriptBuilder, op: Opcode) !*ScriptBuilder { + if (self.script.items.len >= MAX_SCRIPT_SIZE) { + return ScriptBuilderError.ScriptTooLong; + } + try self.script.append(op.toBytes()); + return self; + } + ///Push an array of OPcodes to the ScriptBuilder + pub fn addOpcodes(self: *ScriptBuilder, ops: []const Opcode) !*ScriptBuilder { + if (self.script.items.len + ops.len >= MAX_SCRIPT_SIZE) { + return ScriptBuilderError.ScriptTooLong; + } + for (ops) |op| { + try self.script.append(op.toBytes()); + } + return self; + } + + ///Push an Int to the SciptBuilder + pub fn addInt(self: *ScriptBuilder, num: i32) !*ScriptBuilder { + if (self.script.items.len + 1 >= MAX_SCRIPT_SIZE) { + return ScriptBuilderError.ScriptTooLong; + } + switch (num) { + 0 => try self.script.append(Opcode.OP_0.toBytes()), + -1, 1...16 => try self.script.append(@intCast(Opcode.OP_1.toBytes() - 1 + num)), + else => _ = try self.addData(try ScriptNum.new(num).toBytes(self.allocator)), + } + + return self; + } + /// Push an array of Ints to the SciptBuilder + pub fn addInts(self: *ScriptBuilder, nums: []const i32) !*ScriptBuilder { + for (nums) |num| { + _ = try self.addInt(num); + } + return self; + } + + pub fn addData(self: *ScriptBuilder, data: []const u8) !*ScriptBuilder { + const bytes_length: usize = @intCast(canonicalDataSize(data)); + if (bytes_length + self.script.items.len >= MAX_SCRIPT_SIZE) { + return ScriptBuilderError.ScriptTooLong; + } + + return self.addDataUnchecked(data); + } + + // Deallocate all resources used by the Engine + pub fn deinit(self: *ScriptBuilder) void { + self.script.deinit(); + } + + /// Build the script. It creates and returns an engine instance. + /// + /// Returns + /// Instance of the engine + pub fn build(self: *ScriptBuilder) !engine.Engine { + const script = Script.init(self.script.items); + + const script_engine = engine.Engine.init(self.allocator, script, .{}); + return script_engine; + } + + /// Returns the Size in Bytes of the data to be added + fn canonicalDataSize(data: []const u8) usize { + const dataLen = data.len; + + if (dataLen == 0) { + return 1; + } else if (dataLen == 1 and data[0] <= 16) { + return 1; + } else if (dataLen == 1 and data[0] == 0x81) { + return 1; + } + + if (dataLen < Opcode.OP_PUSHDATA1.toBytes()) { + return 1 + dataLen; + } else if (dataLen <= 0xff) { + return 2 + dataLen; + } else if (dataLen <= 0xffff) { + return 3 + dataLen; + } + + return 5 + dataLen; + } + + /// Private Function to addData without checking MAX_SCRIPT_SIZE + /// Only to be used for testing soundess of the ScriptBuilder struct + /// Cannot be called from other files + fn addDataUnchecked(self: *ScriptBuilder, data: []const u8) !*ScriptBuilder { + if (data.len == 0 or data.len == 1 and data[0] == 0) { + try self.script.append(Opcode.OP_0.toBytes()); + } else if (data.len == 1 and data[0] <= 16) { + const op = Opcode.OP_1.toBytes() - 1 + data[0]; + try self.script.append(op); + } else if (data.len == 1 and data[0] == 0x81) { + try self.script.append(Opcode.OP_1NEGATE.toBytes()); + } else if (data.len < Opcode.OP_PUSHDATA1.toBytes()) { + try self.script.append(@intCast(data.len)); + try self.script.appendSlice(data); + } else if (data.len <= 0xff) { + try self.script.append(Opcode.OP_PUSHDATA1.toBytes()); + try self.script.append(@intCast(data.len)); + + try self.script.appendSlice(data); + } else if (data.len <= 0xffff) { + try self.script.append(Opcode.OP_PUSHDATA2.toBytes()); + try self.script.appendSlice(std.mem.asBytes(&std.mem.nativeToLittle(u16, @intCast(data.len)))); + + try self.script.appendSlice(data); + } else { + try self.script.append(Opcode.OP_PUSHDATA4.toBytes()); + try self.script.appendSlice(std.mem.asBytes(&std.mem.nativeToLittle(u32, @intCast(data.len)))); + try self.script.appendSlice(data); + } + return self; + } +}; + +test "ScriptBuilder Smoke test" { + var sb = try ScriptBuilder.new(std.testing.allocator, 3); + defer sb.deinit(); + + var e = try (try (try (try sb.addInt(1)).addInt(2)).addOpcode(Opcode.OP_ADD)).build(); + defer e.deinit(); + + const expected = [_]u8{ Opcode.OP_1.toBytes(), Opcode.OP_2.toBytes(), Opcode.OP_ADD.toBytes() }; + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} + +//METHOD 1 +test "ScriptBuilder OP_SWAP METHOD 1" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + var e = try (try (try (try (try sb.addInt(1)).addInt(2)).addInt(3)).addOpcode(Opcode.OP_SWAP)).build(); + defer e.deinit(); + + const expected = [_]u8{ Opcode.OP_1.toBytes(), Opcode.OP_2.toBytes(), Opcode.OP_3.toBytes(), Opcode.OP_SWAP.toBytes() }; + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} +//METHOD 2 +test "ScriptBuilder OP_SWAP METHOD 2" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + _ = try sb.addInts(&[3]i32{ 1, 2, 3 }); + _ = try sb.addOpcode(Opcode.OP_SWAP); + + var e = try sb.build(); + defer e.deinit(); + + const expected = [_]u8{ Opcode.OP_1.toBytes(), Opcode.OP_2.toBytes(), Opcode.OP_3.toBytes(), Opcode.OP_SWAP.toBytes() }; + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} + +test "ScriptBuilder addData 0" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + const data = [_]u8{0}; + + _ = try sb.addData(&data); + var e = try sb.build(); + defer e.deinit(); + + try std.testing.expectEqualSlices(u8, &[_]u8{Opcode.OP_0.toBytes()}, e.script.data); +} + +test "ScriptBuilder addData PUSHDATA data.len == 1 and data[0] = 1..16" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + const data = [_]u8{12}; + const expected = [_]u8{Opcode.OP_12.toBytes()}; + _ = try sb.addData(&data); + + var e = try sb.build(); + defer e.deinit(); + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} +test "ScriptBuilder addData PUSHDATA data.len == 1 and data[0] = Opcode.OP_negate" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + const data = [_]u8{0x81}; + const expected = [_]u8{Opcode.OP_1NEGATE.toBytes()}; + _ = try sb.addData(&data); + + var e = try sb.build(); + defer e.deinit(); + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} +test "ScriptBuilder addData PUSHDATA data.len < =opcode.pushdata1.toBytes()" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + const data = [_]u8{ 97, 98, 99, 100 }; + const expected = [_]u8{ Opcode.OP_DATA_4.toBytes(), 97, 98, 99, 100 }; + _ = try sb.addData(&data); + + var e = try sb.build(); + defer e.deinit(); + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} + +test "ScriptBuilder addData data.len <= 0xFF" { + var sb = try ScriptBuilder.new(std.testing.allocator, 250); + defer sb.deinit(); + + var array: [250]u8 = [_]u8{42} ** 250; + var expected: [252]u8 = [_]u8{Opcode.OP_PUSHDATA1.toBytes()} ++ [_]u8{250} ++ ([_]u8{42} ** 250); + + _ = try sb.addData(&array); + + var e = try sb.build(); + defer e.deinit(); + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} +test "ScriptBuilder addData data.len <= 0xFFFF" { + var sb = try ScriptBuilder.new(std.testing.allocator, 65000); + defer sb.deinit(); + + var array: [65000]u8 = [_]u8{42} ** 65000; + + const result_failed = sb.addData(&array); + try std.testing.expectError(ScriptBuilderError.ScriptTooLong, result_failed); +} +test "ScriptBuilder addData data.len <= 0xFFFFFFFF" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + var array: [70000]u8 = undefined; + + const result_failed = sb.addData(&array); + try std.testing.expectError(ScriptBuilderError.ScriptTooLong, result_failed); +} + +test "ScriptBuilder UNCHECKED_ADD_DATA data.len <= 0xFFFF" { + var sb = try ScriptBuilder.new(std.testing.allocator, 65000); + defer sb.deinit(); + + var array: [65000]u8 = [_]u8{42} ** 65000; + const n = std.mem.asBytes(&std.mem.nativeToLittle(u16, 65000)); + const expected: [65003]u8 = [_]u8{Opcode.OP_PUSHDATA2.toBytes()} ++ n.* ++ ([_]u8{42} ** 65000); + + _ = try sb.addDataUnchecked(&array); + + var e = try sb.build(); + defer e.deinit(); + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +} + +test "ScriptBuilder UNCHECKED_ADD_DATA data.len <= 0xFFFFFFFF" { + var sb = try ScriptBuilder.new(std.testing.allocator, 4); + defer sb.deinit(); + + var array: [70000]u8 = [_]u8{42} ** 70000; + const n = std.mem.asBytes(&std.mem.nativeToLittle(u32, 70000)); + const expected: [70005]u8 = [_]u8{Opcode.OP_PUSHDATA4.toBytes()} ++ n.* ++ ([_]u8{42} ** 70000); + + _ = try sb.addDataUnchecked(&array); + + var e = try sb.build(); + defer e.deinit(); + + try std.testing.expectEqualSlices(u8, &expected, e.script.data); +}