From de1192be7e64396755dc4c9755b09282977728cf Mon Sep 17 00:00:00 2001 From: juan518munoz <62400508+juan518munoz@users.noreply.github.com> Date: Wed, 19 Jun 2024 14:57:11 -0300 Subject: [PATCH] Mul and div opcodes (#26) * impl mul * add div * refactor makefile and programs dir * add missing flag for test * add flag setting for mul * fix mul not following spec * impl div set flags * div set flags test * more tests * clippy * add mul conditional test * add div conditional tests * wrap address_operands * justify unwrap * remove makefile changes --- programs/add_sets_overflow.zasm | 2 +- programs/div.zasm | 14 ++ programs/div_codepage.zasm | 20 +++ programs/div_conditional.zasm | 14 ++ programs/div_conditional_gt.zasm | 16 ++ programs/div_set_eq_flag.zasm | 15 ++ programs/div_set_gt_flag.zasm | 15 ++ programs/div_stack.zasm | 19 +++ programs/div_zero.zasm | 13 ++ programs/mul.zasm | 15 ++ programs/mul_big.zasm | 13 ++ programs/mul_codepage.zasm | 21 +++ programs/mul_conditional_gt.zasm | 14 ++ programs/mul_sets_overflow.zasm | 14 ++ programs/mul_stack.zasm | 22 +++ programs/mul_zero.zasm | 13 ++ src/address_operands.rs | 74 +++++++-- src/lib.rs | 7 +- src/op_handlers/div.rs | 17 +++ src/op_handlers/mod.rs | 2 + src/op_handlers/mul.rs | 32 ++++ tests/integration_test.rs | 252 ++++++++++++++++++++++++++----- 22 files changed, 565 insertions(+), 59 deletions(-) create mode 100644 programs/div.zasm create mode 100644 programs/div_codepage.zasm create mode 100644 programs/div_conditional.zasm create mode 100644 programs/div_conditional_gt.zasm create mode 100644 programs/div_set_eq_flag.zasm create mode 100644 programs/div_set_gt_flag.zasm create mode 100644 programs/div_stack.zasm create mode 100644 programs/div_zero.zasm create mode 100644 programs/mul.zasm create mode 100644 programs/mul_big.zasm create mode 100644 programs/mul_codepage.zasm create mode 100644 programs/mul_conditional_gt.zasm create mode 100644 programs/mul_sets_overflow.zasm create mode 100644 programs/mul_stack.zasm create mode 100644 programs/mul_zero.zasm create mode 100644 src/op_handlers/div.rs create mode 100644 src/op_handlers/mul.rs diff --git a/programs/add_sets_overflow.zasm b/programs/add_sets_overflow.zasm index 44527450..35520dd0 100644 --- a/programs/add_sets_overflow.zasm +++ b/programs/add_sets_overflow.zasm @@ -1,5 +1,5 @@ .text - .file "add_sets_overflow.zasm" + .file "mul_sets_overflow.zasm" .globl __entry __entry: diff --git a/programs/div.zasm b/programs/div.zasm new file mode 100644 index 00000000..3ecc5e75 --- /dev/null +++ b/programs/div.zasm @@ -0,0 +1,14 @@ + .text + .file "div.zasm" + .globl __entry +__entry: +.func_begin0: + add 25, r0, r1 + add 6, r0, r2 + div r1, r2, r3, r4 + sstore r0, r3 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/div_codepage.zasm b/programs/div_codepage.zasm new file mode 100644 index 00000000..3308c3c8 --- /dev/null +++ b/programs/div_codepage.zasm @@ -0,0 +1,20 @@ + .text + .file "div_codepage.zasm" + .globl __entry + +.rodata +datavar: + .cell 42 + .cell 3 +.text + +__entry: +.func_begin0: + add 3, r0, r1 + div @datavar[0], r1, r3, r4 + sstore r0, r0 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/div_conditional.zasm b/programs/div_conditional.zasm new file mode 100644 index 00000000..3ecc5e75 --- /dev/null +++ b/programs/div_conditional.zasm @@ -0,0 +1,14 @@ + .text + .file "div.zasm" + .globl __entry +__entry: +.func_begin0: + add 25, r0, r1 + add 6, r0, r2 + div r1, r2, r3, r4 + sstore r0, r3 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/div_conditional_gt.zasm b/programs/div_conditional_gt.zasm new file mode 100644 index 00000000..b488c63a --- /dev/null +++ b/programs/div_conditional_gt.zasm @@ -0,0 +1,16 @@ + .text + .file "div_conditional_gt.zasm" + .globl __entry +__entry: +.func_begin0: + add 1, r0, r3 + add 1, r0, r4 + add 42, r0, r1 + add 3, r0, r2 + div.gt r1, r2, r3, r4 + sstore r0, r3 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/div_set_eq_flag.zasm b/programs/div_set_eq_flag.zasm new file mode 100644 index 00000000..e05ec1fb --- /dev/null +++ b/programs/div_set_eq_flag.zasm @@ -0,0 +1,15 @@ + .text + .file "div_set_eq_flag.zasm" + .globl __entry +__entry: +.func_begin0: + ; EQ is set if the quotient is not zero + add 25, r0, r1 + add 6, r0, r2 + div! r1, r2, r3, r4 + sstore r0, r3 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/div_set_gt_flag.zasm b/programs/div_set_gt_flag.zasm new file mode 100644 index 00000000..873903e6 --- /dev/null +++ b/programs/div_set_gt_flag.zasm @@ -0,0 +1,15 @@ + .text + .file "div_set_gt_flag.zasm" + .globl __entry +__entry: +.func_begin0: + ; GT is set if the reminder is not zero. + add 25, r0, r1 + add 6, r0, r2 + div! r1, r2, r3, r4 + sstore r0, r3 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/div_stack.zasm b/programs/div_stack.zasm new file mode 100644 index 00000000..c6de787c --- /dev/null +++ b/programs/div_stack.zasm @@ -0,0 +1,19 @@ + .text + .file "div_stack.zasm" + .globl __entry +__entry: + +.func_begin0: + add 3, r0, r2 + ; grow stack + add 1, r0, stack+=[1] + ; set stack values + add 42, r0, stack=[0] + ; divide stack of absolute index 0 + div stack=[0],r2, r3, r4 + sstore r0, r0 + ret + +.func_end0: + .note.GNU-stack + .rodata diff --git a/programs/div_zero.zasm b/programs/div_zero.zasm new file mode 100644 index 00000000..94067ffe --- /dev/null +++ b/programs/div_zero.zasm @@ -0,0 +1,13 @@ + .text + .file "div_zero.zasm" + .globl __entry +__entry: +.func_begin0: + add 1, r0, r1 + div r1, r0, r3, r4 + sstore r0, r0 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/mul.zasm b/programs/mul.zasm new file mode 100644 index 00000000..a70fc6ac --- /dev/null +++ b/programs/mul.zasm @@ -0,0 +1,15 @@ + .text + .file "mul.zasm" + .globl __entry +__entry: +.func_begin0: + add 3, r0, r1 + add 2, r0, r2 + mul r1, r2, r1, r0 + mul 1, r1, r3, r4 + sstore r0, r3 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/mul_big.zasm b/programs/mul_big.zasm new file mode 100644 index 00000000..35d65eb8 --- /dev/null +++ b/programs/mul_big.zasm @@ -0,0 +1,13 @@ + .text + .file "mul_big.zasm" + .globl __entry +__entry: +.func_begin0: + ; test sets r1 = 2**(256) - 1, r2 = 2**(256) + mul r1, r2, r3, r4 + sstore r0, r1 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/mul_codepage.zasm b/programs/mul_codepage.zasm new file mode 100644 index 00000000..7d1a0c69 --- /dev/null +++ b/programs/mul_codepage.zasm @@ -0,0 +1,21 @@ + .text + .file "mul_codepage.zasm" + .globl __entry + +.rodata +datavar: + .cell 42 + .cell 3 +.text + +__entry: +.func_begin0: + add 1, r0, r1 + mul @datavar[0], r1, r1, r0 + mul @datavar[1], r1, r1, r0 + sstore r0, r1 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/mul_conditional_gt.zasm b/programs/mul_conditional_gt.zasm new file mode 100644 index 00000000..adcfaaa4 --- /dev/null +++ b/programs/mul_conditional_gt.zasm @@ -0,0 +1,14 @@ + .text + .file "mul_conditional_gt.zasm" + .globl __entry +__entry: +.func_begin0: + add 3, r0, r1 + add 14, r0, r2 + mul.gt r1, r2, r3, r4 + sstore r0, r3 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/programs/mul_sets_overflow.zasm b/programs/mul_sets_overflow.zasm new file mode 100644 index 00000000..8bdfabab --- /dev/null +++ b/programs/mul_sets_overflow.zasm @@ -0,0 +1,14 @@ + .text + .file "add_sets_overflow.zasm" + .globl __entry +__entry: + +.func_begin0: + ; test sets r1 = 2**(256) - 1, r2 = 2**(256) - 1 + mul! r1, r2, r3, r4 + sstore r0, r1 + ret + +.func_end0: + .note.GNU-stack + .rodata diff --git a/programs/mul_stack.zasm b/programs/mul_stack.zasm new file mode 100644 index 00000000..7597d821 --- /dev/null +++ b/programs/mul_stack.zasm @@ -0,0 +1,22 @@ + .text + .file "mul_stack.zasm" + .globl __entry +__entry: + +.func_begin0: + add 1, r0, r2 + ; grow stack + add 1, r0, stack+=[2] + ; set stack values + add 2, r0, stack=[0] + add 3, r0, stack=[1] + ; multiply by stack of absolute index 0 + mul stack=[0],r2, r1, r0 + ; pop stack and multiply by the topmost value + mul stack-=[2],r1, r1, r0 + sstore r0, r1 + ret + +.func_end0: + .note.GNU-stack + .rodata diff --git a/programs/mul_zero.zasm b/programs/mul_zero.zasm new file mode 100644 index 00000000..6fdfbf26 --- /dev/null +++ b/programs/mul_zero.zasm @@ -0,0 +1,13 @@ + .text + .file "mul_zero.zasm" + .globl __entry +__entry: +.func_begin0: + add 1, r0, r1 + mul r1, r0, r1, r0 + sstore r0, r1 + ret +.func_end0: + + .note.GNU-stack + .rodata diff --git a/src/address_operands.rs b/src/address_operands.rs index 63efb9e2..b85d9b9a 100644 --- a/src/address_operands.rs +++ b/src/address_operands.rs @@ -72,25 +72,65 @@ pub fn address_operands_read(vm: &mut VMState, opcode: &Opcode) -> (U256, U256) } } } -fn only_reg_write(vm: &mut VMState, opcode: &Opcode, res: U256) { - vm.set_register(opcode.dst0_index, res); + +/// The first operand is used most of the times +/// the second operand is used only for div and mul +enum OutputOperandPosition { + First, + Second, } -fn reg_and_imm_write(vm: &mut VMState, opcode: &Opcode) -> U256 { - let dst0 = vm.get_register(opcode.dst0_index); - let offset = opcode.imm1; +fn only_reg_write( + vm: &mut VMState, + opcode: &Opcode, + output_op_pos: OutputOperandPosition, + res: U256, +) { + match output_op_pos { + OutputOperandPosition::First => vm.set_register(opcode.dst0_index, res), + OutputOperandPosition::Second => vm.set_register(opcode.dst1_index, res), + } +} - dst0 + U256::from(offset) +fn reg_and_imm_write( + vm: &mut VMState, + output_op_pos: OutputOperandPosition, + opcode: &Opcode, +) -> U256 { + match output_op_pos { + OutputOperandPosition::First => { + let dst0 = vm.get_register(opcode.dst0_index); + let offset = opcode.imm1; + let res = dst0 + U256::from(offset); + vm.set_register(opcode.dst0_index, res); + res + } + OutputOperandPosition::Second => { + let dst1 = vm.get_register(opcode.dst1_index); + let offset = opcode.imm1; + let res = dst1 + U256::from(offset); + vm.set_register(opcode.dst1_index, res); + res + } + } } pub fn address_operands_store(vm: &mut VMState, opcode: &Opcode, res: U256) { + address_operands(vm, opcode, (res, None)) +} + +pub fn address_operands_div_mul(vm: &mut VMState, opcode: &Opcode, res: (U256, U256)) { + address_operands(vm, opcode, (res.0, Some(res.1))) +} + +fn address_operands(vm: &mut VMState, opcode: &Opcode, res: (U256, Option)) { match opcode.dst0_operand_type { Operand::RegOnly => { - only_reg_write(vm, opcode, res); + only_reg_write(vm, opcode, OutputOperandPosition::First, res.0); } Operand::RegOrImm(variant) => match variant { RegOrImmFlags::UseRegOnly => { - only_reg_write(vm, opcode, res); + only_reg_write(vm, opcode, OutputOperandPosition::First, res.0); } RegOrImmFlags::UseImm16Only => { panic!("dest cannot be imm16 only"); @@ -99,38 +139,38 @@ pub fn address_operands_store(vm: &mut VMState, opcode: &Opcode, res: U256) { Operand::Full(variant) => { match variant { ImmMemHandlerFlags::UseRegOnly => { - only_reg_write(vm, opcode, res); + only_reg_write(vm, opcode, OutputOperandPosition::First, res.0); } ImmMemHandlerFlags::UseStackWithPushPop => { // stack+=[src0 + offset] + src1 - let src0 = reg_and_imm_write(vm, opcode); + let src0 = reg_and_imm_write(vm, OutputOperandPosition::First, opcode); vm.current_frame.stack.fill_with_zeros(src0 + 1); vm.current_frame.stack.store_with_offset( 1, TaggedValue { - value: res, + value: res.0, is_pointer: false, }, ); } ImmMemHandlerFlags::UseStackWithOffset => { // stack[src0 + offset] + src1 - let src0 = reg_and_imm_write(vm, opcode); + let src0 = reg_and_imm_write(vm, OutputOperandPosition::First, opcode); vm.current_frame.stack.store_with_offset( src0.as_usize(), TaggedValue { - value: res, + value: res.0, is_pointer: false, }, ); } ImmMemHandlerFlags::UseAbsoluteOnStack => { // stack=[src0 + offset] + src1 - let src0 = reg_and_imm_write(vm, opcode); + let src0 = reg_and_imm_write(vm, OutputOperandPosition::First, opcode); vm.current_frame.stack.store_absolute( src0.as_usize(), TaggedValue { - value: res, + value: res.0, is_pointer: false, }, ); @@ -144,4 +184,8 @@ pub fn address_operands_store(vm: &mut VMState, opcode: &Opcode, res: U256) { } } } + if let Some(res) = res.1 { + // Second operand can only be a register + only_reg_write(vm, opcode, OutputOperandPosition::Second, res); + } } diff --git a/src/lib.rs b/src/lib.rs index 6f0984ee..23d0d264 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,8 @@ pub mod state; mod value; use op_handlers::add::_add; +use op_handlers::div::_div; +use op_handlers::mul::_mul; use op_handlers::sub::_sub; pub use opcode::Opcode; use state::VMState; @@ -43,6 +45,7 @@ pub fn run_program_with_custom_state(bin_path: &str, mut vm: VMState) -> (U256, loop { let opcode = vm.get_opcode(&opcode_table); + if vm.predicate_holds(&opcode.predicate) { match opcode.variant { // TODO: Properly handle what happens @@ -54,8 +57,8 @@ pub fn run_program_with_custom_state(bin_path: &str, mut vm: VMState) -> (U256, _add(&mut vm, &opcode); } Variant::Sub(_) => _sub(&mut vm, &opcode), - Variant::Mul(_) => todo!(), - Variant::Div(_) => todo!(), + Variant::Mul(_) => _mul(&mut vm, &opcode), + Variant::Div(_) => _div(&mut vm, &opcode), Variant::Jump(_) => todo!(), Variant::Context(_) => todo!(), Variant::Shift(_) => todo!(), diff --git a/src/op_handlers/div.rs b/src/op_handlers/div.rs new file mode 100644 index 00000000..2971beec --- /dev/null +++ b/src/op_handlers/div.rs @@ -0,0 +1,17 @@ +use crate::address_operands::{address_operands_div_mul, address_operands_read}; +use crate::{opcode::Opcode, state::VMState}; + +pub fn _div(vm: &mut VMState, opcode: &Opcode) { + let (src0, src1) = address_operands_read(vm, opcode); + let (quotient, remainder) = src0.div_mod(src1); + if opcode.alters_vm_flags { + // Lt overflow is cleared + vm.flag_lt_of = false; + // Eq is set if quotient is not zero + vm.flag_eq = !quotient.is_zero(); + // Gt is set if the remainder is not zero + vm.flag_gt = !remainder.is_zero(); + } + + address_operands_div_mul(vm, opcode, (quotient, remainder)); +} diff --git a/src/op_handlers/mod.rs b/src/op_handlers/mod.rs index 42f1cf02..3907206d 100644 --- a/src/op_handlers/mod.rs +++ b/src/op_handlers/mod.rs @@ -1,2 +1,4 @@ pub mod add; +pub mod div; +pub mod mul; pub mod sub; diff --git a/src/op_handlers/mul.rs b/src/op_handlers/mul.rs new file mode 100644 index 00000000..5fd8a946 --- /dev/null +++ b/src/op_handlers/mul.rs @@ -0,0 +1,32 @@ +use u256::{U256, U512}; + +use crate::address_operands::{address_operands_div_mul, address_operands_read}; +use crate::{opcode::Opcode, state::VMState}; + +pub fn _mul(vm: &mut VMState, opcode: &Opcode) { + let (src0, src1) = address_operands_read(vm, opcode); + let src0 = U512::from(src0); + let src1 = U512::from(src1); + let res = src0 * src1; + + let u256_mask = U512::from(U256::MAX); + let low_bits = res & u256_mask; + let high_bits = res >> 256 & u256_mask; + + if opcode.alters_vm_flags { + // Lt overflow, is set if + // src0 * src1 >= 2^256 + let overflow = res >= U512::from(U256::MAX); + vm.flag_lt_of |= overflow; + // Eq is set if res_low == 0 + vm.flag_eq |= low_bits.is_zero(); + // Gt is set if both of lt_of and eq are cleared. + vm.flag_gt |= !vm.flag_lt_of && !vm.flag_eq; + } + + address_operands_div_mul( + vm, + opcode, + (low_bits.try_into().unwrap(), high_bits.try_into().unwrap()), // safe to unwrap, as we have applied the mask + ); +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 32a51366..aa303bd0 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -38,27 +38,6 @@ fn test_add_asm() { assert_eq!(result, U256::from_dec_str("3").unwrap()); } -#[test] -fn test_sub_asm_simple() { - let bin_path = make_bin_path_asm("sub_simple"); - let (result, _) = run_program(&bin_path); - assert_eq!(result, U256::from_dec_str("3").unwrap()); -} - -#[test] -fn test_sub_asm() { - let bin_path = make_bin_path_asm("sub_should_be_zero"); - let (result, _) = run_program(&bin_path); - assert_eq!(result, U256::from_dec_str("0").unwrap()); -} - -#[test] -fn test_sub_and_add() { - let bin_path = make_bin_path_asm("sub_and_add"); - let (result, _) = run_program(&bin_path); - assert_eq!(result, U256::from_dec_str("1").unwrap()); -} - #[test] fn test_add_registers() { let bin_path = make_bin_path_asm("add_registers"); @@ -80,6 +59,20 @@ fn test_add_stack_out_of_bounds() { run_program(&bin_path); } +#[test] +fn test_sub_asm_simple() { + let bin_path = make_bin_path_asm("sub_simple"); + let (result, _) = run_program(&bin_path); + assert_eq!(result, U256::from_dec_str("3").unwrap()); +} + +#[test] +fn test_sub_asm() { + let bin_path = make_bin_path_asm("sub_should_be_zero"); + let (result, _) = run_program(&bin_path); + assert_eq!(result, U256::from_dec_str("0").unwrap()); +} + #[test] fn test_add_stack_with_pop() { let bin_path = make_bin_path_asm("add_stack_with_pop"); @@ -148,14 +141,6 @@ fn test_add_does_not_run_if_gt_is_not_set() { assert_eq!(result, U256::from_dec_str("0").unwrap()); } -#[test] -fn test_more_complex_program_with_conditionals() { - let bin_path = make_bin_path_asm("add_and_sub_with_conditionals"); - let vm_with_custom_flags = VMState::new_with_flag_state(false, true, false); - let (result, _final_vm_state) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); - assert_eq!(result, U256::from_dec_str("10").unwrap()); -} - #[test] fn test_add_sets_overflow_flag() { let bin_path = make_bin_path_asm("add_sets_overflow"); @@ -199,6 +184,26 @@ fn test_add_sets_gt_flag_keeps_other_flags_clear() { assert!(result == U256::from(2)); } +#[test] +fn test_add_does_not_modify_set_flags() { + let bin_path = make_bin_path_asm("add_sub_do_not_modify_flags"); + // Trigger overflow on first add, so this sets the lt_of flag. Then a + // non-overflowing add should leave the flag set. + let r1 = U256::MAX; + let r2 = fake_rand().into(); + let r3 = U256::from(1_usize); + let r4 = U256::from(1_usize); + let mut registers: [U256; 15] = [U256::zero(); 15]; + registers[0] = r1; + registers[1] = r2; + registers[2] = r3; + registers[3] = r4; + let vm_with_custom_flags = VMState::new_with_registers(registers); + let (_result, final_vm_state) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + assert!(final_vm_state.flag_lt_of); + assert!(final_vm_state.flag_eq); +} + #[test] fn test_sub_flags_r1_rs_keeps_other_flags_clear() { let bin_path = make_bin_path_asm("sub_flags_r1_r2"); @@ -245,23 +250,188 @@ fn test_sub_sets_gt_flag_keeps_other_flags_clear() { } #[test] -fn test_add_does_not_modify_set_flags() { - let bin_path = make_bin_path_asm("add_sub_do_not_modify_flags"); - // Trigger overflow on first add, so this sets the lt_of flag. Then a - // non-overflowing add should leave the flag set. +fn test_sub_and_add() { + let bin_path = make_bin_path_asm("sub_and_add"); + let (result, _) = run_program(&bin_path); + assert_eq!(result, U256::from_dec_str("1").unwrap()); +} + +#[test] +fn test_mul_asm() { + let bin_path = make_bin_path_asm("mul"); + let (_, vm) = run_program(&bin_path); + let low = vm.get_register(3); + let high = vm.get_register(4); + + assert_eq!(low, U256::from_dec_str("6").unwrap()); + assert_eq!(high, U256::zero()); +} + +#[test] +fn test_mul_big_asm() { + let bin_path = make_bin_path_asm("mul_big"); let r1 = U256::MAX; - let r2 = fake_rand().into(); - let r3 = U256::from(1_usize); - let r4 = U256::from(1_usize); + let r2 = U256::from(2); let mut registers: [U256; 15] = [U256::zero(); 15]; registers[0] = r1; registers[1] = r2; - registers[2] = r3; - registers[3] = r4; let vm_with_custom_flags = VMState::new_with_registers(registers); - let (_result, final_vm_state) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); - assert!(final_vm_state.flag_lt_of); - assert!(final_vm_state.flag_eq); + + let (_, vm) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + + let low = vm.get_register(3); + let high = vm.get_register(4); + + assert_eq!(low, U256::MAX - 1); + assert_eq!(high, U256::from(1)); // multiply by 2 == shift left by 1 +} + +#[test] +fn test_mul_zero_asm() { + let bin_path = make_bin_path_asm("mul_zero"); + let (result, _) = run_program(&bin_path); + assert_eq!(result, U256::from_dec_str("0").unwrap()); +} + +#[test] +fn test_mul_codepage() { + let bin_path = make_bin_path_asm("mul_codepage"); + let (result, _) = run_program(&bin_path); + assert_eq!(result, U256::from_dec_str("126").unwrap()); +} + +#[test] +fn test_mul_sets_overflow_flag() { + let bin_path = make_bin_path_asm("mul_sets_overflow"); + let r1 = U256::MAX; + let r2 = U256::MAX; + let mut registers: [U256; 15] = [U256::zero(); 15]; + registers[0] = r1; + registers[1] = r2; + + let vm_with_custom_flags = VMState::new_with_registers(registers); + let (_, vm) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + assert!(vm.flag_lt_of); +} + +#[test] +fn test_mul_stack() { + let bin_path = make_bin_path_asm("mul_stack"); + let (result, _) = run_program(&bin_path); + assert_eq!(result, U256::from_dec_str("6").unwrap()); +} + +#[test] +fn test_mul_conditional_gt_set() { + let bin_path = make_bin_path_asm("mul_conditional_gt"); + + let vm_with_custom_flags = VMState::new_with_flag_state(false, false, true); + let (result, _) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + assert_eq!(result, U256::from_dec_str("42").unwrap()); +} + +#[test] +fn test_mul_conditional_gt_not_set() { + let bin_path = make_bin_path_asm("mul_conditional_gt"); + + let vm_with_custom_flags = VMState::new_with_flag_state(false, false, false); + let (result, _) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + assert_eq!(result, U256::from_dec_str("0").unwrap()); +} + +#[test] +fn test_div_asm() { + let bin_path = make_bin_path_asm("div"); + let (_, vm) = run_program(&bin_path); + let quotient_result = vm.get_register(3); + let remainder_result = vm.get_register(4); + + // 25 / 6 = 4 remainder 1 + assert_eq!(quotient_result, U256::from_dec_str("4").unwrap()); + assert_eq!(remainder_result, U256::from_dec_str("1").unwrap()); +} + +#[test] +#[should_panic] +fn test_div_zero_asm() { + let bin_path = make_bin_path_asm("div_zero"); + run_program(&bin_path); +} + +#[test] +fn test_div_set_eq_flag() { + let bin_path = make_bin_path_asm("div_set_eq_flag"); + let (_, vm) = run_program(&bin_path); + + assert!(vm.flag_eq); +} + +#[test] +fn test_div_set_gt_flag() { + let bin_path = make_bin_path_asm("div_set_gt_flag"); + run_program(&bin_path); + let (_, vm) = run_program(&bin_path); + + assert!(vm.flag_gt); +} + +#[test] +fn test_div_codepage() { + let bin_path = make_bin_path_asm("div_codepage"); + let (_, vm) = run_program(&bin_path); + let quotient_result = vm.get_register(3); + let remainder_result = vm.get_register(4); + + // 42 / 3 = 14 remainder 0 + assert_eq!(quotient_result, U256::from_dec_str("14").unwrap()); + assert_eq!(remainder_result, U256::from_dec_str("0").unwrap()); +} + +#[test] +fn test_div_stack() { + let bin_path = make_bin_path_asm("div_stack"); + let (_, vm) = run_program(&bin_path); + let quotient_result = vm.get_register(3); + let remainder_result = vm.get_register(4); + + // 42 / 3 = 14 remainder 0 + assert_eq!(quotient_result, U256::from_dec_str("14").unwrap()); + assert_eq!(remainder_result, U256::from_dec_str("0").unwrap()); +} + +#[test] +fn test_div_conditional_gt_set() { + let bin_path = make_bin_path_asm("div_conditional_gt"); + + let vm_with_custom_flags = VMState::new_with_flag_state(false, false, true); + let (_, vm) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + let quotient_result = vm.get_register(3); + let remainder_result = vm.get_register(4); + + assert_eq!(quotient_result, U256::from_dec_str("14").unwrap()); + assert_eq!(remainder_result, U256::from_dec_str("0").unwrap()); +} + +#[test] +fn test_div_conditional_gt_not_set() { + let bin_path = make_bin_path_asm("div_conditional_gt"); + + let vm_with_custom_flags = VMState::new_with_flag_state(false, false, false); + let (_, vm) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + let quotient_result = vm.get_register(3); + let remainder_result = vm.get_register(4); + + // program sets registers 3 and 4 at the beginning, and only changes them if the conditional is met + assert_eq!(quotient_result, U256::from_dec_str("1").unwrap()); + assert_eq!(remainder_result, U256::from_dec_str("1").unwrap()); +} + +#[test] +fn test_more_complex_program_with_conditionals() { + let bin_path = make_bin_path_asm("add_and_sub_with_conditionals"); + let vm_with_custom_flags = VMState::new_with_flag_state(false, true, false); + let (result, _final_vm_state) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); + assert_eq!(result, U256::from_dec_str("10").unwrap()); } #[test] // This test should run out of gas before