diff --git a/cairo_programs/cairo/hints/divmod.cairo b/cairo_programs/cairo/hints/divmod.cairo new file mode 100644 index 00000000..bee66af1 --- /dev/null +++ b/cairo_programs/cairo/hints/divmod.cairo @@ -0,0 +1,4 @@ +fn main() { + let _ = 17_u32 / 12_u32; + let _ = 17_u32 % 12_u32; +} \ No newline at end of file diff --git a/src/hints/hintHandler.ts b/src/hints/hintHandler.ts index 020aeeab..48fbc69b 100644 --- a/src/hints/hintHandler.ts +++ b/src/hints/hintHandler.ts @@ -48,6 +48,7 @@ import { shouldSkipSquashLoop, } from './dict/shouldSkipSquashLoop'; import { TestLessThan, testLessThan } from './math/testLessThan'; +import { DivMod, divMod } from './math/divMod'; /** * Map hint names to the function executing their logic. @@ -125,4 +126,8 @@ export const handlers: HintHandler = { const h = hint as TestLessThan; testLessThan(vm, h.lhs, h.rhs, h.dst); }, + [HintName.DivMod]: (vm, hint) => { + const h = hint as DivMod; + divMod(vm, h.lhs, h.rhs, h.quotient, h.remainder); + }, }; diff --git a/src/hints/hintName.ts b/src/hints/hintName.ts index f00a024a..d18bcac0 100644 --- a/src/hints/hintName.ts +++ b/src/hints/hintName.ts @@ -15,4 +15,5 @@ export enum HintName { ShouldContinueSquashLoop = 'ShouldContinueSquashLoop', ShouldSkipSquashLoop = 'ShouldSkipSquashLoop', TestLessThan = 'TestLessThan', + DivMod = 'DivMod', } diff --git a/src/hints/hintSchema.ts b/src/hints/hintSchema.ts index 892737db..d3675b50 100644 --- a/src/hints/hintSchema.ts +++ b/src/hints/hintSchema.ts @@ -15,6 +15,7 @@ import { initSquashDataParser } from './dict/initSquashData'; import { shouldContinueSquashLoopParser } from './dict/shouldContinueSquashLoop'; import { shouldSkipSquashLoopParser } from './dict/shouldSkipSquashLoop'; import { testLessThanParser } from './math/testLessThan'; +import { divModParser } from './math/divMod'; /** Zod object to parse any implemented hints */ const hint = z.union([ @@ -33,6 +34,7 @@ const hint = z.union([ shouldContinueSquashLoopParser, shouldSkipSquashLoopParser, testLessThanParser, + divModParser, ]); /** Zod object to parse an array of hints grouped on a given PC */ diff --git a/src/hints/math/divMod.test.ts b/src/hints/math/divMod.test.ts new file mode 100644 index 00000000..84914dad --- /dev/null +++ b/src/hints/math/divMod.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, test } from 'bun:test'; +import { Felt } from 'primitives/felt'; +import { VirtualMachine } from 'vm/virtualMachine'; +import { HintName } from 'hints/hintName'; +import { OpType } from 'hints/hintParamsSchema'; +import { divModParser } from './divMod'; +import { divMod } from './divMod'; +import { Register } from 'vm/instruction'; +import { CannotDivideByZero } from 'errors/primitives'; + +const DIV_MOD_HINT = { + DivMod: { + lhs: { + Deref: { + register: 'AP', + offset: 0, + }, + }, + rhs: { + Deref: { + register: 'AP', + offset: 1, + }, + }, + quotient: { + register: 'AP', + offset: 2, + }, + remainder: { + register: 'AP', + offset: 3, + }, + }, +}; + +describe('DivMod', () => { + test('should properly parse DivMod hint', () => { + const hint = divModParser.parse(DIV_MOD_HINT); + expect(hint).toEqual({ + type: HintName.DivMod, + lhs: { + type: OpType.Deref, + cell: { + register: Register.Ap, + offset: 0, + }, + }, + rhs: { + type: OpType.Deref, + cell: { + register: Register.Ap, + offset: 1, + }, + }, + quotient: { + register: Register.Ap, + offset: 2, + }, + remainder: { + register: Register.Ap, + offset: 3, + }, + }); + }); + + test.each([ + [new Felt(17n), new Felt(5n), new Felt(3n), new Felt(2n)], + [new Felt(10n), new Felt(3n), new Felt(3n), new Felt(1n)], + [new Felt(20n), new Felt(4n), new Felt(5n), new Felt(0n)], + [new Felt(15n), new Felt(6n), new Felt(2n), new Felt(3n)], + ])( + 'should properly execute DivMod hint', + (lhsValue, rhsValue, quotientExpected, remainderExpected) => { + const hint = divModParser.parse(DIV_MOD_HINT); + const vm = new VirtualMachine(); + vm.memory.addSegment(); + vm.memory.addSegment(); + + vm.memory.assertEq(vm.ap, lhsValue); + vm.memory.assertEq(vm.ap.add(1), rhsValue); + + divMod(vm, hint.lhs, hint.rhs, hint.quotient, hint.remainder); + + expect(vm.memory.get(vm.cellRefToRelocatable(hint.quotient))).toEqual( + quotientExpected + ); + expect(vm.memory.get(vm.cellRefToRelocatable(hint.remainder))).toEqual( + remainderExpected + ); + } + ); + + test.each([ + [new Felt(17n), new Felt(0n)], + [new Felt(100n), new Felt(0n)], + [new Felt(5n), new Felt(0n)], + [new Felt(0n), new Felt(0n)], + ])('should throw an error when rhs is zero', (lhsValue, rhsValue) => { + const hint = divModParser.parse(DIV_MOD_HINT); + const vm = new VirtualMachine(); + vm.memory.addSegment(); + vm.memory.addSegment(); + + vm.memory.assertEq(vm.ap, lhsValue); + vm.memory.assertEq(vm.ap.add(1), rhsValue); + + expect(() => { + divMod(vm, hint.lhs, hint.rhs, hint.quotient, hint.remainder); + }).toThrow(new CannotDivideByZero()); + }); +}); diff --git a/src/hints/math/divMod.ts b/src/hints/math/divMod.ts new file mode 100644 index 00000000..9e4ba9fb --- /dev/null +++ b/src/hints/math/divMod.ts @@ -0,0 +1,65 @@ +import { z } from 'zod'; + +import { VirtualMachine } from 'vm/virtualMachine'; + +import { + resOperand, + ResOperand, + cellRef, + CellRef, +} from 'hints/hintParamsSchema'; +import { HintName } from 'hints/hintName'; +import { Felt } from 'primitives/felt'; +import { CannotDivideByZero } from 'errors/primitives'; + +/** Zod object to parse DivMod hint */ +export const divModParser = z + .object({ + DivMod: z.object({ + lhs: resOperand, + rhs: resOperand, + quotient: cellRef, + remainder: cellRef, + }), + }) + .transform(({ DivMod: { lhs, rhs, quotient, remainder } }) => ({ + type: HintName.DivMod, + lhs, + rhs, + quotient, + remainder, + })); + +/** + * DivMod hint type + */ +export type DivMod = z.infer; + +/** + * Perform division and modulus operations. + * + * @param {VirtualMachine} vm - The virtual machine instance + * @param {ResOperand} lhs - The left-hand side operand + * @param {ResOperand} rhs - The right-hand side operand + * @param {CellRef} quotient - The address to store the quotient + * @param {CellRef} remainder - The address to store the remainder + */ + +export const divMod = ( + vm: VirtualMachine, + lhs: ResOperand, + rhs: ResOperand, + quotient: CellRef, + remainder: CellRef +) => { + const lhsValue = vm.getResOperandValue(lhs).toBigInt(); + const rhsValue = vm.getResOperandValue(rhs).toBigInt(); + + if (!rhsValue) throw new CannotDivideByZero(); + + const quotientValue = new Felt(lhsValue / rhsValue); + const remainderValue = new Felt(lhsValue % rhsValue); + + vm.memory.assertEq(vm.cellRefToRelocatable(quotient), quotientValue); + vm.memory.assertEq(vm.cellRefToRelocatable(remainder), remainderValue); +};