From c4897390f7faf752983cb711f15211aa1177d626 Mon Sep 17 00:00:00 2001 From: Malatrax Date: Tue, 30 Jul 2024 17:24:32 +0200 Subject: [PATCH] refactor: use named constants for dict and segment arena related offset in hints --- src/builtins/segmentArena.ts | 27 +++++++++++++++++++++++ src/errors/dictionary.ts | 10 +++++++++ src/errors/virtualMachine.ts | 7 ------ src/hints/dict/allocFelt252Dict.ts | 8 ++++--- src/hints/dict/felt252DictEntryInit.ts | 3 ++- src/hints/dict/felt252DictEntryUpdate.ts | 5 +++-- src/hints/dict/initSquashData.ts | 5 +++-- src/hints/dictionary.test.ts | 28 ++++++++++++++++++++++++ src/hints/dictionary.ts | 27 +++++++++++++++++++++++ src/vm/virtualMachine.test.ts | 28 ++---------------------- src/vm/virtualMachine.ts | 17 +++++--------- 11 files changed, 112 insertions(+), 53 deletions(-) create mode 100644 src/errors/dictionary.ts create mode 100644 src/hints/dictionary.test.ts create mode 100644 src/hints/dictionary.ts diff --git a/src/builtins/segmentArena.ts b/src/builtins/segmentArena.ts index e4b4b687..e67c06d7 100644 --- a/src/builtins/segmentArena.ts +++ b/src/builtins/segmentArena.ts @@ -1,3 +1,30 @@ import { BuiltinHandler } from './builtin'; +/** + * Offset to compute the address + * of the info segment pointer. + */ +export const INFO_PTR_OFFSET = 3; + +/** + * Offset to read the current number + * of allocated dictionaries. + */ +export const DICT_NUMBER_OFFSET = 2; + +/** + * The segment arena builtin manages Cairo dictionaries. + * + * It works by block of 3 cells: + * - The first cell contains the base address of the info pointer. + * - The second cell contains the current number of allocated dictionaries. + * - The third cell contains the current number of squashed dictionaries. + * + * The Info segment is tightly closed to the segment arena builtin. + * + * It also works by block of 3 cells: + * - The first cell is the base address of a dictionary + * - The second cell is the end address of a dictionary when squashed. + * - The third cell is the current number of squashed dictionaries (i.e. its squashing index). + */ export const segmentArenaHandler: BuiltinHandler = {}; diff --git a/src/errors/dictionary.ts b/src/errors/dictionary.ts new file mode 100644 index 00000000..ad83c3e2 --- /dev/null +++ b/src/errors/dictionary.ts @@ -0,0 +1,10 @@ +import { Relocatable } from 'primitives/relocatable'; + +class DictionaryError extends Error {} + +/** Cannot find Dictionary at `address` */ +export class DictNotFound extends DictionaryError { + constructor(address: Relocatable) { + super(`Cannot found any Dictionary at address ${address.toString()}`); + } +} diff --git a/src/errors/virtualMachine.ts b/src/errors/virtualMachine.ts index 9f2a2a52..4542882a 100644 --- a/src/errors/virtualMachine.ts +++ b/src/errors/virtualMachine.ts @@ -65,10 +65,3 @@ export class InvalidBufferResOp extends VirtualMachineError { super(`Cannot extract buffer from the given ResOperand: ${resOperand}`); } } - -/** Cannot find Dictionary at `address` */ -export class DictNotFound extends VirtualMachineError { - constructor(address: Relocatable) { - super(`Cannot found any Dictionary at address ${address.toString()}`); - } -} diff --git a/src/hints/dict/allocFelt252Dict.ts b/src/hints/dict/allocFelt252Dict.ts index fe5fc172..afebc058 100644 --- a/src/hints/dict/allocFelt252Dict.ts +++ b/src/hints/dict/allocFelt252Dict.ts @@ -3,10 +3,12 @@ import { z } from 'zod'; import { ExpectedFelt, ExpectedRelocatable } from 'errors/primitives'; import { isFelt, isRelocatable } from 'primitives/segmentValue'; -import { DICT_ACCESS_SIZE, VirtualMachine } from 'vm/virtualMachine'; +import { VirtualMachine } from 'vm/virtualMachine'; import { HintName } from 'hints/hintName'; import { resOperand, ResOperand } from 'hints/hintParamsSchema'; +import { DICT_ACCESS_SIZE } from 'hints/dictionary'; +import { DICT_NUMBER_OFFSET, INFO_PTR_OFFSET } from 'builtins/segmentArena'; /** Zod object to parse AllocFelt252Dict hint */ export const allocFelt252DictParser = z @@ -42,10 +44,10 @@ export const allocFelt252Dict = ( segmentArenaPtr: ResOperand ) => { const arenaPtr = vm.getPointer(...vm.extractBuffer(segmentArenaPtr)); - const dictNumber = vm.memory.get(arenaPtr.sub(2)); + const dictNumber = vm.memory.get(arenaPtr.sub(DICT_NUMBER_OFFSET)); if (!dictNumber || !isFelt(dictNumber)) throw new ExpectedFelt(dictNumber); - const infoPtr = vm.memory.get(arenaPtr.sub(3)); + const infoPtr = vm.memory.get(arenaPtr.sub(INFO_PTR_OFFSET)); if (!infoPtr || !isRelocatable(infoPtr)) throw new ExpectedRelocatable(infoPtr); diff --git a/src/hints/dict/felt252DictEntryInit.ts b/src/hints/dict/felt252DictEntryInit.ts index c6ec52e5..4a951ed2 100644 --- a/src/hints/dict/felt252DictEntryInit.ts +++ b/src/hints/dict/felt252DictEntryInit.ts @@ -5,6 +5,7 @@ import { VirtualMachine } from 'vm/virtualMachine'; import { resOperand, ResOperand } from 'hints/hintParamsSchema'; import { HintName } from 'hints/hintName'; +import { PREV_VALUE_OFFSET } from 'hints/dictionary'; /** Zod object to parse Felt252DictEntryInit hint */ export const felt252DictEntryInitParser = z @@ -47,5 +48,5 @@ export const felt252DictEntryInit = ( const dict = vm.getDict(address); const prevValue = dict.get(keyValue) || new Felt(0n); dict.set(keyValue, prevValue); - vm.memory.assertEq(address.add(1), prevValue); + vm.memory.assertEq(address.add(PREV_VALUE_OFFSET), prevValue); }; diff --git a/src/hints/dict/felt252DictEntryUpdate.ts b/src/hints/dict/felt252DictEntryUpdate.ts index 09609bf5..e106ab6e 100644 --- a/src/hints/dict/felt252DictEntryUpdate.ts +++ b/src/hints/dict/felt252DictEntryUpdate.ts @@ -3,10 +3,11 @@ import { z } from 'zod'; import { ExpectedFelt } from 'errors/primitives'; import { isFelt } from 'primitives/segmentValue'; -import { DICT_ACCESS_SIZE, VirtualMachine } from 'vm/virtualMachine'; +import { VirtualMachine } from 'vm/virtualMachine'; import { HintName } from 'hints/hintName'; import { Deref, OpType, resOperand, ResOperand } from 'hints/hintParamsSchema'; +import { KEY_OFFSET } from 'hints/dictionary'; /** Zod object to parse Felt252DictEntryUpdate hint */ export const felt252DictEntryUpdateParser = z @@ -45,7 +46,7 @@ export const felt252DictEntryUpdate = ( value: ResOperand ) => { const address = vm.getPointer(...vm.extractBuffer(dictPtr)); - const key = vm.memory.get(address.sub(DICT_ACCESS_SIZE)); + const key = vm.memory.get(address.sub(KEY_OFFSET)); if (!key || !isFelt(key)) throw new ExpectedFelt(key); const val = value.type === OpType.Deref diff --git a/src/hints/dict/initSquashData.ts b/src/hints/dict/initSquashData.ts index 129eefcc..35a7dcfc 100644 --- a/src/hints/dict/initSquashData.ts +++ b/src/hints/dict/initSquashData.ts @@ -1,10 +1,11 @@ import { z } from 'zod'; import { ExpectedFelt } from 'errors/primitives'; +import { InvalidDictAccessesNumber } from 'errors/hints'; import { Felt } from 'primitives/felt'; import { isFelt } from 'primitives/segmentValue'; -import { DICT_ACCESS_SIZE, VirtualMachine } from 'vm/virtualMachine'; +import { VirtualMachine } from 'vm/virtualMachine'; import { HintName } from 'hints/hintName'; import { @@ -13,7 +14,7 @@ import { resOperand, ResOperand, } from 'hints/hintParamsSchema'; -import { InvalidDictAccessesNumber } from 'errors/hints'; +import { DICT_ACCESS_SIZE } from 'hints/dictionary'; /** Zod object to parse InitSquashData hint */ export const initSquashDataParser = z diff --git a/src/hints/dictionary.test.ts b/src/hints/dictionary.test.ts new file mode 100644 index 00000000..aadf74d3 --- /dev/null +++ b/src/hints/dictionary.test.ts @@ -0,0 +1,28 @@ +import { describe, test, expect } from 'bun:test'; + +import { DictNotFound } from 'errors/dictionary'; + +import { Felt } from 'primitives/felt'; +import { Relocatable } from 'primitives/relocatable'; +import { VirtualMachine } from 'vm/virtualMachine'; +import { Dictionary } from './dictionary'; + +describe('Dictionary', () => { + test('should properly initialize the dict manager', () => { + const vm = new VirtualMachine(); + expect(vm.dictManager.size).toEqual(0); + }); + + test('should properly create a new dictionary', () => { + const vm = new VirtualMachine(); + const address = vm.newDict(); + expect(address).toEqual(new Relocatable(0, 0)); + expect(vm.getDict(address)).toEqual(new Dictionary(new Felt(0n))); + }); + + test('should throw DictNotFound when accessing a non-existing dictionary', () => { + const vm = new VirtualMachine(); + const address = new Relocatable(2, 3); + expect(() => vm.getDict(address)).toThrow(new DictNotFound(address)); + }); +}); diff --git a/src/hints/dictionary.ts b/src/hints/dictionary.ts new file mode 100644 index 00000000..57f35353 --- /dev/null +++ b/src/hints/dictionary.ts @@ -0,0 +1,27 @@ +import { Felt } from 'primitives/felt'; +import { SegmentValue } from 'primitives/segmentValue'; + +export const DICT_ACCESS_SIZE = 3; + +/** Offset to read the key of the entry to update. */ +export const KEY_OFFSET = 3; + +/** + * Offset to read the previous value + * of the entry to read or update. + */ +export const PREV_VALUE_OFFSET = 1; + +/** + * Helper class to implement Cairo dictionaries. + * + * The `id` attribute is needed to keep track + * of the multiple dictionaries and their + * corresponding segment in memory. + */ +export class Dictionary extends Map { + constructor(public readonly id: Felt) { + super(); + this.id = id; + } +} diff --git a/src/vm/virtualMachine.test.ts b/src/vm/virtualMachine.test.ts index e9cdfeb7..3718179b 100644 --- a/src/vm/virtualMachine.test.ts +++ b/src/vm/virtualMachine.test.ts @@ -1,11 +1,7 @@ import { test, expect, describe, spyOn } from 'bun:test'; import { ExpectedFelt, ExpectedRelocatable } from 'errors/primitives'; -import { - DictNotFound, - InvalidBufferResOp, - UnusedRes, -} from 'errors/virtualMachine'; +import { InvalidBufferResOp, UnusedRes } from 'errors/virtualMachine'; import { Felt } from 'primitives/felt'; import { Relocatable } from 'primitives/relocatable'; @@ -19,7 +15,7 @@ import { FpUpdate, Op1Src, } from './instruction'; -import { Dictionary, VirtualMachine } from './virtualMachine'; +import { VirtualMachine } from './virtualMachine'; import { CellRef, Operation, OpType, ResOperand } from 'hints/hintParamsSchema'; const instructions = { @@ -741,24 +737,4 @@ describe('VirtualMachine', () => { ); }); }); - - describe('Dictionary', () => { - test('should properly initialize the dict manager', () => { - const vm = new VirtualMachine(); - expect(vm.dictManager.size).toEqual(0); - }); - - test('should properly create a new dictionary', () => { - const vm = new VirtualMachine(); - const address = vm.newDict(); - expect(address).toEqual(new Relocatable(0, 0)); - expect(vm.getDict(address)).toEqual(new Dictionary(new Felt(0n))); - }); - - test('should throw DictNotFound when accessing a non-existing dictionary', () => { - const vm = new VirtualMachine(); - const address = new Relocatable(2, 3); - expect(() => vm.getDict(address)).toThrow(new DictNotFound(address)); - }); - }); }); diff --git a/src/vm/virtualMachine.ts b/src/vm/virtualMachine.ts index b4f5b8ff..f33ae157 100644 --- a/src/vm/virtualMachine.ts +++ b/src/vm/virtualMachine.ts @@ -9,15 +9,16 @@ import { UndefinedOp0, InvalidCallOp0Value, UndefinedOp1, - DictNotFound, InvalidBufferResOp, } from 'errors/virtualMachine'; +import { DictNotFound } from 'errors/dictionary'; import { InvalidCellRefRegister, UnknownHint } from 'errors/hints'; import { Felt } from 'primitives/felt'; import { Relocatable } from 'primitives/relocatable'; import { SegmentValue, isFelt, isRelocatable } from 'primitives/segmentValue'; import { Memory } from 'memory/memory'; + import { BinOp, CellRef, @@ -29,9 +30,10 @@ import { ResOperand, } from 'hints/hintParamsSchema'; import { Hint } from 'hints/hintSchema'; -import { ScopeManager } from 'hints/scopeManager'; -import { SquashedDictManager } from '../hints/squashedDictManager'; import { handlers, HintHandler } from 'hints/hintHandler'; +import { Dictionary } from 'hints/dictionary'; +import { ScopeManager } from 'hints/scopeManager'; +import { SquashedDictManager } from 'hints/squashedDictManager'; import { ApUpdate, @@ -61,15 +63,6 @@ export type RelocatedMemory = { value: Felt; }; -export class Dictionary extends Map { - constructor(public readonly id: Felt) { - super(); - this.id = id; - } -} - -export const DICT_ACCESS_SIZE = 3; - export class VirtualMachine { private currentStep: bigint; memory: Memory;