diff --git a/src/compiler.cairo b/src/compiler.cairo index 6db1d479..306f2702 100644 --- a/src/compiler.cairo +++ b/src/compiler.cairo @@ -36,12 +36,16 @@ pub impl CompilerTraitImpl of CompilerTrait { opcodes.insert('OP_14', Opcode::OP_14); opcodes.insert('OP_15', Opcode::OP_15); opcodes.insert('OP_16', Opcode::OP_16); + opcodes.insert('OP_IF', Opcode::OP_IF); + opcodes.insert('OP_NOTIF', Opcode::OP_NOTIF); + opcodes.insert('OP_ELSE', Opcode::OP_ELSE); + opcodes.insert('OP_ENDIF', Opcode::OP_ENDIF); opcodes.insert('OP_DEPTH', Opcode::OP_DEPTH); opcodes.insert('OP_1ADD', Opcode::OP_1ADD); + opcodes.insert('OP_NOT', Opcode::OP_NOT); opcodes.insert('OP_ADD', Opcode::OP_ADD); - + opcodes.insert('OP_SUB', Opcode::OP_SUB); opcodes.insert('OP_MAX', Opcode::OP_MAX); - opcodes.insert('OP_NOT', Opcode::OP_NOT); Compiler { opcodes } } diff --git a/src/cond_stack.cairo b/src/cond_stack.cairo new file mode 100644 index 00000000..db78fc55 --- /dev/null +++ b/src/cond_stack.cairo @@ -0,0 +1,43 @@ +#[derive(Destruct)] +pub struct ConditionalStack { + stack: Felt252Dict, + len: usize, +} + +#[generate_trait()] +pub impl ConditionalStackImpl of ConditionalStackTrait { + fn new() -> ConditionalStack { + ConditionalStack { stack: Default::default(), len: 0, } + } + + fn push(ref self: ConditionalStack, value: u8) { + self.stack.insert(self.len.into(), value); + self.len += 1; + } + + fn pop(ref self: ConditionalStack) { + self.len -= 1; + } + + fn branch_executing(ref self: ConditionalStack) -> bool { + if self.len == 0 { + return true; + } else { + return self.stack[self.len.into() - 1] == 1; + } + } + + fn len(ref self: ConditionalStack) -> usize { + self.len + } + + fn swap_condition(ref self: ConditionalStack) { + let cond_idx = self.len() - 1; + match self.stack.get(cond_idx.into()) { + 0 => self.stack.insert(cond_idx.into(), 1), + 1 => self.stack.insert(cond_idx.into(), 0), + 2 => self.stack.insert(cond_idx.into(), 2), + _ => panic!("Invalid condition") + } + } +} diff --git a/src/engine.cairo b/src/engine.cairo index 2149287e..acfc2ade 100644 --- a/src/engine.cairo +++ b/src/engine.cairo @@ -1,5 +1,6 @@ use shinigami::stack::{ScriptStack, ScriptStackImpl}; -use shinigami::opcodes::opcodes::Opcode::execute; +use shinigami::cond_stack::{ConditionalStack, ConditionalStackImpl}; +use shinigami::opcodes::opcodes::Opcode::{execute, is_branching_opcode}; // Represents the VM that executes Bitcoin scripts #[derive(Destruct)] @@ -12,6 +13,8 @@ pub struct Engine { pub dstack: ScriptStack, // Alternate data stack pub astack: ScriptStack, + // Tracks conditonal execution state supporting nested conditionals + pub cond_stack: ConditionalStack, // TODO // ... } @@ -34,6 +37,7 @@ pub impl EngineTraitImpl of EngineTrait { opcode_idx: 0, dstack: ScriptStackImpl::new(), astack: ScriptStackImpl::new(), + cond_stack: ConditionalStackImpl::new(), } } @@ -50,6 +54,12 @@ pub impl EngineTraitImpl of EngineTrait { return false; } + if !self.cond_stack.branch_executing() + && !is_branching_opcode(self.script[self.opcode_idx]) { + self.opcode_idx += 1; + return true; + } + let opcode = self.script[self.opcode_idx]; execute(opcode, ref self); self.opcode_idx += 1; @@ -61,6 +71,11 @@ pub impl EngineTraitImpl of EngineTrait { .opcode_idx < self .script .len() { + if !self.cond_stack.branch_executing() + && !is_branching_opcode(self.script[self.opcode_idx]) { + self.opcode_idx += 1; + continue; + } let opcode = self.script[self.opcode_idx]; execute(opcode, ref self); self.opcode_idx += 1; diff --git a/src/lib.cairo b/src/lib.cairo index 9770432e..b31ffc77 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,4 +1,5 @@ pub mod compiler; +pub mod cond_stack; pub mod opcodes { pub mod opcodes; mod tests { @@ -9,5 +10,6 @@ pub mod opcodes { } pub mod engine; pub mod stack; +pub mod utils; mod main; diff --git a/src/opcodes/opcodes.cairo b/src/opcodes/opcodes.cairo index 90d8b116..6c6fe860 100644 --- a/src/opcodes/opcodes.cairo +++ b/src/opcodes/opcodes.cairo @@ -17,16 +17,20 @@ pub mod Opcode { pub const OP_14: u8 = 94; pub const OP_15: u8 = 95; pub const OP_16: u8 = 96; + pub const OP_IF: u8 = 99; + pub const OP_NOTIF: u8 = 100; + pub const OP_ELSE: u8 = 103; + pub const OP_ENDIF: u8 = 104; pub const OP_DEPTH: u8 = 116; pub const OP_1ADD: u8 = 139; + pub const OP_NOT: u8 = 145; pub const OP_ADD: u8 = 147; + pub const OP_SUB: u8 = 148; pub const OP_MAX: u8 = 164; - pub const OP_NOT: u8 = 145; - - - use shinigami::engine::Engine; + use shinigami::engine::{Engine, EngineTrait}; use shinigami::stack::ScriptStackTrait; + use shinigami::cond_stack::ConditionalStackTrait; pub fn execute(opcode: u8, ref engine: Engine) { match opcode { 0 => opcode_false(ref engine), @@ -128,12 +132,12 @@ pub mod Opcode { 96 => opcode_n(16, ref engine), 97 => not_implemented(ref engine), 98 => not_implemented(ref engine), - 99 => not_implemented(ref engine), - 100 => not_implemented(ref engine), + 99 => opcode_if(ref engine), + 100 => opcode_notif(ref engine), 101 => not_implemented(ref engine), 102 => not_implemented(ref engine), - 103 => not_implemented(ref engine), - 104 => not_implemented(ref engine), + 103 => opcode_else(ref engine), + 104 => opcode_endif(ref engine), 105 => not_implemented(ref engine), 106 => not_implemented(ref engine), 107 => not_implemented(ref engine), @@ -177,7 +181,7 @@ pub mod Opcode { 145 => opcode_not(ref engine), 146 => not_implemented(ref engine), 147 => opcode_add(ref engine), - 148 => not_implemented(ref engine), + 148 => opcode_sub(ref engine), 149 => not_implemented(ref engine), 150 => not_implemented(ref engine), 151 => not_implemented(ref engine), @@ -198,6 +202,13 @@ pub mod Opcode { } } + pub fn is_branching_opcode(opcode: u8) -> bool { + if opcode == OP_IF || opcode == OP_NOTIF || opcode == OP_ELSE || opcode == OP_ENDIF { + return true; + } + return false; + } + fn opcode_false(ref engine: Engine) { engine.dstack.push_byte_array(""); } @@ -206,6 +217,53 @@ pub mod Opcode { engine.dstack.push_int(n); } + // TODO: MOve to cond_stack + const op_cond_false: u8 = 0; + const op_cond_true: u8 = 1; + const op_cond_skip: u8 = 2; + fn opcode_if(ref engine: Engine) { + let mut cond = op_cond_false; + // TODO: Pop if bool + if engine.cond_stack.branch_executing() { + let ok = engine.dstack.pop_bool(); + if ok { + cond = op_cond_true; + } + } else { + cond = op_cond_skip; + } + engine.cond_stack.push(cond); + } + + fn opcode_notif(ref engine: Engine) { + let mut cond = op_cond_false; + if engine.cond_stack.branch_executing() { + let ok = engine.dstack.pop_bool(); + if !ok { + cond = op_cond_true; + } + } else { + cond = op_cond_skip; + } + engine.cond_stack.push(cond); + } + + fn opcode_else(ref engine: Engine) { + if engine.cond_stack.len() == 0 { + panic!("No matching if"); + } + + engine.cond_stack.swap_condition(); + } + + fn opcode_endif(ref engine: Engine) { + if engine.cond_stack.len() == 0 { + panic!("No matching if"); + } + + engine.cond_stack.pop(); + } + fn opcode_add(ref engine: Engine) { // TODO: Error handling let a = engine.dstack.pop_int(); @@ -213,6 +271,12 @@ pub mod Opcode { engine.dstack.push_int(a + b); } + fn opcode_sub(ref engine: Engine) { + // TODO: Error handling + let a = engine.dstack.pop_int(); + let b = engine.dstack.pop_int(); + engine.dstack.push_int(b - a); + } fn opcode_depth(ref engine: Engine) { let depth: i64 = engine.dstack.len().into(); diff --git a/src/opcodes/tests/test_opcodes.cairo b/src/opcodes/tests/test_opcodes.cairo index 52ef3372..4e7f0f0a 100644 --- a/src/opcodes/tests/test_opcodes.cairo +++ b/src/opcodes/tests/test_opcodes.cairo @@ -1,5 +1,6 @@ use shinigami::compiler::CompilerTraitImpl; use shinigami::engine::EngineTraitImpl; +use shinigami::utils::int_to_bytes; #[test] fn test_op_0() { @@ -30,7 +31,7 @@ fn test_op_1() { assert_eq!(dstack.len(), 1, "Stack length is not 1"); // TODO: Is this the correct representation of 1? - let expected_stack = array!["\0\0\0\0\0\0\0\x01"]; + let expected_stack = array!["\x01"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -48,7 +49,55 @@ fn test_op_add() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x02"]; + let expected_stack = array!["\x02"]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); +} + +#[test] +fn test_op_sub() { + let program = "OP_1 OP_1 OP_SUB"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 1, "Stack length is not 1"); + + let expected_stack = array![""]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); + + let program = "OP_2 OP_1 OP_SUB"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 1, "Stack length is not 1"); + + let expected_stack = array!["\x01"]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); + + let program = "OP_1 OP_2 OP_SUB"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 1, "Stack length is not 1"); + + let expected_stack: Array = array![int_to_bytes(-1)]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -66,7 +115,63 @@ fn test_op_max() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x01"]; + let expected_stack = array!["\x01"]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); +} + +fn test_op_if_false() { + let program = "OP_0 OP_IF OP_1 OP_ENDIF"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 0, "Stack length is not 0"); + + let expected_stack = array![]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); +} + +#[test] +fn test_op_if_true() { + let program = "OP_1 OP_IF OP_1 OP_ENDIF"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 1, "Stack length is not 1"); + + let expected_stack = array!["\x01"]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); +} + +#[test] +fn test_op_notif_false() { + let program = "OP_0 OP_NOTIF OP_1 OP_ENDIF"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 1, "Stack length is not 1"); + + let expected_stack = array!["\x01"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -82,7 +187,7 @@ fn test_op_depth_empty_stack() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\0"]; + let expected_stack = array!["\0"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected for empty stack"); } @@ -97,7 +202,7 @@ fn test_op_2() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x02"]; + let expected_stack = array!["\x02"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -114,7 +219,7 @@ fn test_op_not() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\0"]; + let expected_stack = array![""]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -132,7 +237,7 @@ fn test_op_depth_one_item() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 2, "Stack length is not 2"); - let expected_stack = array!["\0\0\0\0\0\0\0\x01", "\0\0\0\0\0\0\0\x01"]; + let expected_stack = array!["\x01", "\x01"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected for one item"); } @@ -148,12 +253,12 @@ fn test_op_depth_multiple_items() { let _ = engine.step(); let _ = engine.step(); let res = engine.step(); - assert!(res, "Execution of step failed"); + assert!(res, "Execution of run failed"); let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 3, "Stack length is not 3"); - let expected_stack = array!["\0\0\0\0\0\0\0\x02", "\0\0\0\0\0\0\0\x01", "\0\0\0\0\0\0\0\x02"]; + let expected_stack = array!["\x02", "\x01", "\x02"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected for multiple items"); } @@ -170,7 +275,47 @@ fn test_op_TRUE() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x01"]; + let expected_stack = array!["\x01"]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); +} + +#[test] +fn test_op_notif_true() { + let program = "OP_1 OP_NOTIF OP_1 OP_ENDIF"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 0, "Stack length is not 0"); + + let expected_stack = array![]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); +} + +#[test] +fn test_op_else_false() { + let program = "OP_0 OP_IF OP_0 OP_ELSE OP_1 OP_ENDIF"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 1, "Stack length is not 1"); + + let expected_stack = array!["\x01"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -188,11 +333,34 @@ fn test_op_1add() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x02"]; + let expected_stack = array!["\x02"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } #[test] +fn test_op_else_true() { + let program = "OP_1 OP_IF OP_0 OP_ELSE OP_1 OP_ENDIF"; + let mut compiler = CompilerTraitImpl::new(); + let bytecode = compiler.compile(program); + let mut engine = EngineTraitImpl::new(bytecode); + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let _ = engine.step(); + let res = engine.step(); + assert!(res, "Execution of run failed"); + + let dstack = engine.get_dstack(); + assert_eq!(dstack.len(), 1, "Stack length is not 1"); + + let expected_stack = array![""]; + assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); +} + +// TODO: No end_if, ... +// TODO: Nested if statements tests + fn test_op_3() { let program = "OP_3"; let mut compiler = CompilerTraitImpl::new(); @@ -204,7 +372,7 @@ fn test_op_3() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x03"]; + let expected_stack = array!["\x03"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -220,7 +388,7 @@ fn test_op_4() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x04"]; + let expected_stack = array!["\x04"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -236,7 +404,7 @@ fn test_op_5() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x05"]; + let expected_stack = array!["\x05"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -252,7 +420,7 @@ fn test_op_6() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x06"]; + let expected_stack = array!["\x06"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -268,7 +436,7 @@ fn test_op_7() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x07"]; + let expected_stack = array!["\x07"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -284,7 +452,7 @@ fn test_op_8() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x08"]; + let expected_stack = array!["\x08"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -300,7 +468,7 @@ fn test_op_9() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x09"]; + let expected_stack = array!["\x09"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -316,7 +484,7 @@ fn test_op_10() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x0a"]; + let expected_stack = array!["\x0a"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -332,7 +500,7 @@ fn test_op_11() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x0b"]; + let expected_stack = array!["\x0b"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -348,7 +516,7 @@ fn test_op_12() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x0c"]; + let expected_stack = array!["\x0c"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -364,7 +532,7 @@ fn test_op_13() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x0d"]; + let expected_stack = array!["\x0d"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -380,7 +548,7 @@ fn test_op_14() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x0e"]; + let expected_stack = array!["\x0e"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -396,7 +564,7 @@ fn test_op_15() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x0f"]; + let expected_stack = array!["\x0f"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } @@ -412,6 +580,6 @@ fn test_op_16() { let dstack = engine.get_dstack(); assert_eq!(dstack.len(), 1, "Stack length is not 1"); - let expected_stack = array!["\0\0\0\0\0\0\0\x10"]; + let expected_stack = array!["\x10"]; assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected"); } diff --git a/src/stack.cairo b/src/stack.cairo index da878909..3464d0d2 100644 --- a/src/stack.cairo +++ b/src/stack.cairo @@ -1,5 +1,5 @@ -use core::nullable::NullableTrait; use core::dict::Felt252DictEntryTrait; +use shinigami::utils; #[derive(Destruct)] pub struct ScriptStack { @@ -19,8 +19,7 @@ pub impl ScriptStackImpl of ScriptStackTrait { } fn push_int(ref self: ScriptStack, value: i64) { - let mut bytes = ""; - bytes.append_word(value.into(), 8); + let mut bytes = utils::int_to_bytes(value); self.push_byte_array(bytes); } @@ -59,6 +58,27 @@ pub impl ScriptStackImpl of ScriptStackTrait { } } + fn pop_bool(ref self: ScriptStack) -> bool { + let bytes = self.pop_byte_array(); + + let mut i = 0; + let mut ret_bool = false; + while i < bytes + .len() { + if bytes.at(i).unwrap() != 0 { + // Can be negative zero + if i == bytes.len() - 1 && bytes.at(i).unwrap() == 0x80 { + ret_bool = false; + break; + } + ret_bool = true; + break; + } + i += 1; + }; + return ret_bool; + } + fn len(ref self: ScriptStack) -> usize { self.len } diff --git a/src/utils.cairo b/src/utils.cairo new file mode 100644 index 00000000..139bd45b --- /dev/null +++ b/src/utils.cairo @@ -0,0 +1,53 @@ +const TWO_POW_EIGHT: u256 = 0x100; +const SIGN_BIT: u8 = 0x80; + + +// `int_to_bytes` returns the number serialized as a little endian with a sign bit +pub fn int_to_bytes(mut value: i64) -> ByteArray { + let mut bytes: ByteArray = ""; + if value == 0 { + return bytes; + } + + let is_negative: bool = value < 0; + if is_negative { + value = -value; + } + + let value_felt: felt252 = value.into(); + let mut value_u256: u256 = value_felt.into(); + let mask: u256 = 0xff; + + while value_u256 > 0 { + let item: u256 = value_u256 & mask; + let byte: u8 = item.try_into().unwrap(); + bytes.append_byte(byte); + + value_u256 = value_u256 / TWO_POW_EIGHT; + }; + + if bytes.at((bytes.len() - 1)).unwrap() & SIGN_BIT != 0 { + let mut extra_byte: u8 = 0; + if is_negative { + extra_byte = 0x80; + } + bytes.append_byte(SIGN_BIT); + + return bytes; + } else if is_negative { + let mut last_byte: u8 = bytes.at((bytes.len() - 1)).unwrap(); + last_byte = last_byte | SIGN_BIT; + + let mut new_bytes: ByteArray = ""; + let mut i = 0; + while i < bytes.len() - 1 { + new_bytes.append_byte(bytes.at(i).unwrap()); + }; + + new_bytes.append_byte(last_byte); + + return new_bytes; + } + + return bytes; +}