Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement dict hints primitives #106

Merged
merged 78 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
5d58b5e
feat: add Cairo program with array
zmalatrax Jul 16, 2024
9020816
feat: add segment_arena builtin
zmalatrax Jul 16, 2024
d345e14
feat: add ScopeManager
zmalatrax Jul 16, 2024
78054c2
feat: add DictManager
zmalatrax Jul 16, 2024
536d01c
feat: add SquashedDictManager
zmalatrax Jul 16, 2024
56ab2f3
feat: add dict Cairo programs
zmalatrax Jul 16, 2024
bd8b3f7
feat: add extractBuffer and unit tests hint utils
zmalatrax Jul 16, 2024
258636a
feat: add AllocFelt252Dict hint
zmalatrax Jul 17, 2024
76cbd3a
feat: add GetSegmentArenaIndex hint
zmalatrax Jul 17, 2024
957504d
refactor: inline pointer extraction
zmalatrax Jul 17, 2024
a5cc8b1
feat: add Felt252DictEntryInit
zmalatrax Jul 18, 2024
b759763
chore: enforce camelCase in hints
zmalatrax Jul 18, 2024
70a0edf
feat: add Felt252DictUpdate hint
zmalatrax Jul 18, 2024
19adaea
refactor: simplify felt252DictUpdate
zmalatrax Jul 19, 2024
5d9ed1a
fix: support Relocatable as dict value
zmalatrax Jul 19, 2024
c4d3b2a
fix: use proper Felt252DictEntryUpdate hint name
zmalatrax Jul 19, 2024
09ebd98
fix: fix getSegmentArenaIndex hint
zmalatrax Jul 19, 2024
feb3f82
feat: add InitSquashData hint
zmalatrax Jul 19, 2024
2faacfe
fix: handle relocatable addition to a felt
zmalatrax Jul 22, 2024
9ffd8d1
fix: add JSDoc
zmalatrax Jul 22, 2024
0ea457d
feat: add GetCurrentAccessIndex hint
zmalatrax Jul 22, 2024
c28b290
feat: add ShouldSkipSquashLoop hint
zmalatrax Jul 22, 2024
5a7422c
feat: add GetCurrentAccessDelta hint
zmalatrax Jul 22, 2024
28292e9
feat: add GetNextDictKey hint
zmalatrax Jul 22, 2024
1012ea9
feat: add Arc-related hints
zmalatrax Jul 22, 2024
5d38809
doc: add JSDoc to Arc-related hints
zmalatrax Jul 22, 2024
8236c84
chore: add Cairo program with two distinc dicts
zmalatrax Jul 22, 2024
c5c420d
fix: fix condition ShouldContinueSquashLoop hint
zmalatrax Jul 22, 2024
fd6a1bf
refactor: use Map with primitive types as key for dict managers
zmalatrax Jul 22, 2024
98de23b
fix: assert to the write range check address on AssertLeFindSmallArcs…
zmalatrax Jul 22, 2024
9fc63af
refactor: group hints by category
zmalatrax Jul 23, 2024
266ceef
refactor: export hint handlers to its own file
zmalatrax Jul 23, 2024
d5b1d4b
refactor: sort hints by ascending alphabetical order
zmalatrax Jul 23, 2024
732723d
feat: add unit test ShouldContinueSquashLoop hint
zmalatrax Jul 23, 2024
3c95ab0
feat: add unit test ShouldSkipSquashLoop
zmalatrax Jul 23, 2024
076d54f
feat: add unit test GetNextDictKey hint
zmalatrax Jul 23, 2024
04670e7
feat: add unit test GetCurrentAccessIndex
zmalatrax Jul 23, 2024
0a5ad6f
feat: add unit test GetCurrentAccessDelta hint
zmalatrax Jul 23, 2024
34f9d7a
feat: add unit test InitSquashData hint
zmalatrax Jul 23, 2024
594d06f
refactor: rename filename squashedDict to squashedDictManager and mov…
zmalatrax Jul 23, 2024
b19c6b7
chore: order imports
zmalatrax Jul 23, 2024
0ef9b07
chore: add missing JSDoc to errors
zmalatrax Jul 23, 2024
71c6489
chore: remove useless type assertion
zmalatrax Jul 23, 2024
fbd9210
doc: add JSDoc to SquashedDictManager
zmalatrax Jul 23, 2024
616a4d8
doc: add hint dev in progress
zmalatrax Jul 23, 2024
275885e
refactor: use forEach to execute hints
zmalatrax Jul 23, 2024
0f190ed
refactor: use number to add and sub felt when possible
zmalatrax Jul 23, 2024
767bc8d
chore: enforce camelCase in hint fields
zmalatrax Jul 23, 2024
883f94d
doc: add hint implementation how-to
zmalatrax Jul 23, 2024
c157c80
refactor: export how to guide to a specific file in hints folder
zmalatrax Jul 23, 2024
7c65ac8
fix: correct camelCase instead of snake_case in GetSegmentArenaIndex …
zmalatrax Jul 24, 2024
f60ac61
refactor: move how to to a docs folder
zmalatrax Jul 24, 2024
de8faf8
refactor: export indices to a global variable in squashedDictManager …
zmalatrax Jul 24, 2024
e47daab
refactor: rename ResOp to ResOperand
zmalatrax Jul 24, 2024
77e2b05
chore: trunk fmt
zmalatrax Jul 24, 2024
145f011
feat: enable trunk-fmt-pre-commit
zmalatrax Jul 24, 2024
c263709
chore: rephase JSDoc SquashedDictManager error
zmalatrax Jul 29, 2024
d629f3f
chore: remove unused methods
zmalatrax Jul 29, 2024
7c5a04d
doc: fix JSDoc typos
zmalatrax Jul 29, 2024
458b7de
chore: typo
zmalatrax Jul 30, 2024
f28151c
feat: overload Felt mul signature with multiplication by number
zmalatrax Jul 30, 2024
be29cf2
refactor: use named constant DICT_ACCESS_SIZE
zmalatrax Jul 30, 2024
89a398e
refactor: refactor initSquashData dict access size error
zmalatrax Jul 30, 2024
4a12cdf
chore: typo
zmalatrax Jul 30, 2024
60261a0
doc: improve JSDoc AllocFelt252Dict
zmalatrax Jul 30, 2024
1a6c36a
chore: fix dictionary typo
zmalatrax Jul 30, 2024
80e05ca
feat: add compare method to Felt class
zmalatrax Jul 30, 2024
c489739
refactor: use named constants for dict and segment arena related offs…
zmalatrax Jul 30, 2024
2991bcf
refactor: rename array of segment arena initial values
zmalatrax Jul 30, 2024
5f5f38e
doc: improve JSDoc Felt252DictEntryInit
zmalatrax Jul 30, 2024
9075b6b
doc: improve JSDoc Felt252DictEntryUpdate
zmalatrax Jul 30, 2024
6b8c219
doc: improve JSDoc GetCurrentAccessDelta
zmalatrax Jul 30, 2024
13130c3
doc: improve JSDoc GetCurrentAccessIndex
zmalatrax Jul 30, 2024
0ec3f16
doc: improve JSDoc GetNextDictKey
zmalatrax Jul 30, 2024
94a5d3d
doc: improve JSDoc GetSegmentArenaIndex
zmalatrax Jul 30, 2024
7c7b3d4
doc: improve JSDoc InitSquashData
zmalatrax Jul 30, 2024
a87335c
doc: improve JSDoc ShouldContinueSquashLoop
zmalatrax Jul 30, 2024
9ece7de
doc: improve JSDoc ShouldSkipSquashLoop
zmalatrax Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,5 @@ actions:
disabled:
- trunk-announce
- trunk-check-pre-push
- trunk-fmt-pre-commit
enabled:
- trunk-upgrade-available
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,20 @@ You can still add it as a dependency with a local copy:
| [Keccak](https://github.com/kkrt-labs/cairo-vm-ts/issues/69) | ☑ |
| [Poseidon](https://github.com/kkrt-labs/cairo-vm-ts/issues/71) | ☑ |
| [Range Check 96](https://github.com/kkrt-labs/cairo-vm-ts/issues/81) | ☑ |
| Segment Arena | ☐ |
| [Segment Arena](https://github.com/kkrt-labs/cairo-vm-ts/pull/106) | ☑ |
| AddMod | ☐ |
| MulMod | ☐ |

### Hints

<!-- Add a table with the hint list and state done/to be done -->
<!-- If the list is too long, maybe separate in chunks, put the list in an issue to track it and reference the issue here -->
Hints are currently being implemented.

Their development can be tracked
[here](https://github.com/kkrt-labs/cairo-vm-ts/issues/90).

#### How to implement a hint ?

A how-to guide has been written [here](/docs/howToImplementAHint.md).

### Differential Testing & Benchmark

Expand Down
3 changes: 3 additions & 0 deletions cairo_programs/cairo/hints/alloc_felt_dict.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
let mut _balances: Felt252Dict<u64> = Default::default();
}
6 changes: 6 additions & 0 deletions cairo_programs/cairo/hints/array_append.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
let mut a = ArrayTrait::new();
a.append(0);
a.append(1);
a.append(2);
}
5 changes: 5 additions & 0 deletions cairo_programs/cairo/hints/dict_get.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn main() {
let mut balances: Felt252Dict<u64> = Default::default();
balances.insert('Simon', 100);
balances.get('Simon');
}
6 changes: 6 additions & 0 deletions cairo_programs/cairo/hints/dict_insert.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
let mut balances: Felt252Dict<u64> = Default::default();
balances.insert('Simon', 100);
balances.insert('Alice', 500);
balances.insert('Bob', 30);
}
7 changes: 7 additions & 0 deletions cairo_programs/cairo/hints/dict_multiple_insert.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
let mut balances: Felt252Dict<u64> = Default::default();
balances.insert('Simon', 100);
balances.insert('Simon', 500);
balances.insert('Simon', 600);
balances.insert('Simon', 700);
}
7 changes: 7 additions & 0 deletions cairo_programs/cairo/hints/dict_squash.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
let mut dict = felt252_dict_new::<felt252>();
dict.insert(1, 64);
dict.insert(2, 75);
dict.insert(3, 75);
dict.squash();
}
6 changes: 6 additions & 0 deletions cairo_programs/cairo/hints/multiple_dict.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
let mut balances_1: Felt252Dict<u64> = Default::default();
let mut balances_2: Felt252Dict<u64> = Default::default();
balances_1.insert('Alice', 500);
balances_2.insert('Bob', 30);
}
155 changes: 155 additions & 0 deletions docs/howToImplementAHint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# How to implement a hint

Here is a how-to, using the hint `GetSegmentArenaIndex` as an example:

## Find the signature

Find the signature of the hint in the
[Cairo compiler](https://github.com/starkware-libs/cairo/blob/b741c26c553fd9fa3246cee91fd5c637f225cdb9/crates/cairo-lang-casm/src/hints/mod.rs):
[GetSegmentArenaIndex](https://github.com/starkware-libs/cairo/blob/b741c26c553fd9fa3246cee91fd5c637f225cdb9/crates/cairo-lang-casm/src/hints/mod.rs#L203)

```rust
/// Retrieves the index of the given dict in the dict_infos segment.
GetSegmentArenaIndex { dict_end_ptr: ResOperand, dict_index: CellRef },
```

Here, `dict_end_ptr` is a `ResOperand` while `dict_index` is a `CellRef`.

The definitions of `Cellref` and `ResOperand` can be found in
`hintParamSchema.ts`. Hint arguments can only be one of these two types.

## Parsing

The Cairo VM takes the serialized compilation artifacts as input, we use Zod to
parse them. Each hint has its own parser object.

The GetSegmentArenaIndex hint can be found in a format similar to this one:

```json
"GetSegmentArenaIndex": {
"dict_end_ptr": {
"Deref": {
"register": "FP",
"offset": -3
}
},
"dict_index": {
"register": "FP",
"offset": 0
}
}
```

- Add `GetSegmentArenaIndex` to the `HintName` enum in `src/hints/hintName.ts`.
It is used to identify the hint before executing it in a run. Hints are
ordered in an ascending alphabetical order.

```typescript
// hintName.ts
export enum HintName {
// ...
GetSegmentArenaIndex = 'GetSegmentArenaIndex',
}
```

- Create the file `src/hints/dict/getSegmentArenaIndex.ts`. Place the file in
the appropriate sub-folder category, here `dict` because the hint is dedicated
to dictionaries.

- Create and export a Zod object `getSegmentArenaIndexParser` which follows the
hint signature:

```typescript
// getSegmentArenaIndex.ts
export const getSegmentArenaIndexParser = z
.object({
GetSegmentArenaIndex: z.object({
dict_end_ptr: resOperand,
dict_index: cellRef,
}),
})
.transform(({ GetSegmentArenaIndex: { dict_end_ptr, dict_index } }) => ({
type: HintName.GetSegmentArenaIndex,
dictEndPtr: dict_end_ptr,
dictIndex: dict_index,
}));
```

The parsed object must be transformed in two ways:

1. Enforce camelCase in fields name
2. Add a field `type` which takes the corresponding value of the `HintName`
enum.

- Add the parser to the Zod union `hint` in `src/hints/hintSchema.ts`:

```typescript
// hintSchema.ts
const hint = z.union([
// ...
getSegmentArenaIndexParser,
]);
```

Now, we can implement the core logic of the hint.

## Core Logic

The core logic of the hint will be implemented in the same file as the hint
parser, here `getSegmentArenaIndex.ts`. The function implementing this logic
must be named as the camelCase version of the hint: `getSegmentArenaIndex()`
(similar to its filename).

The parameters of the function are the virtual machine, as the hint must
interact with it, and the signature of the hint.

So, in our case, the function signature would be
`export getSegmentArenaIndex(vm: VirtualMachine, dictEndPtr: ResOperand, dictIndex: CellRef)`

To implement the logic, refer yourself to its implementation in the
[`cairo-vm`](https://github.com/lambdaclass/cairo-vm/blob/24c2349cc19832fd8c1552304fe0439765ed82c6/vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs#L427-L444)
and the
[`cairo-lang-runner`](https://github.com/starkware-libs/cairo/blob/b741c26c553fd9fa3246cee91fd5c637f225cdb9/crates/cairo-lang-runner/src/casm_run/mod.rs#L1873-L1880)
from the Cairo compiler.

The last step is adding the hint to the handler object.

## Handler

The handler is defined in `src/hints/hintHandler.ts`

It is a dictionary which maps a `HintName` value to a function executing the
corresponding core logic function.

```typescript
export const handlers: Record<
HintName,
(vm: VirtualMachine, hint: Hint) => void
> = {
[HintName.GetSegmentArenaIndex]: (vm, hint) => {
const h = hint as GetSegmentArenaIndex;
getSegmentArenaIndex(vm, h.dictEndptr, h.dictIndex);
},
};
```

- Set the key as the HintName value, `HintName.GetSegmentArenaIndex`
- Set the value to a function which takes `(vm, hint)` as parameters and execute
the core logic function of the corresponding hint.

To do so, we make a type assertion of the hint, matching the `HintName` value,
and we call the corresponding core logic function with the appropriate
arguments.

The hint has been implemented, the last thing to do is testing it.

## Testing

Unit tests must test the correct parsing of the hint and the execution of the
core logic. Those tests are done in a `.test.ts` file in the same folder as the
hint. In our example, it would be `src/hints/dict/getSegmentArenaIndex.test.ts`.

Integration test is done by creating a Cairo program in
`cairo_programs/cairo/hints`. We must verify its proper execution by compiling
it with `make compile` and executing it with the command
`cairo run path/to/my_program.json`
4 changes: 3 additions & 1 deletion src/builtins/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { poseidonHandler } from './poseidon';
import { keccakHandler } from './keccak';
import { outputHandler } from './output';
import { rangeCheckHandler } from './rangeCheck';
import { segmentArenaHandler } from './segmentArena';

/** Proxy handler to abstract validation & deduction rules off the VM */
export type BuiltinHandler = ProxyHandler<Array<SegmentValue>>;
Expand All @@ -20,7 +21,7 @@ export type BuiltinHandler = ProxyHandler<Array<SegmentValue>>;
* - Keccak: Builtin for keccak hash family
* - Pedersen: Builtin for pedersen hash family
* - Poseidon: Builtin for poseidon hash family
* - Segment Arena: Unknown usage, must investigate
* - Segment Arena: Builtin to manage the dictionaries
* - Output: Output builtin
*/
const BUILTIN_HANDLER: {
Expand All @@ -35,6 +36,7 @@ const BUILTIN_HANDLER: {
keccak: keccakHandler,
range_check: rangeCheckHandler(128n),
range_check96: rangeCheckHandler(96n),
segment_arena: segmentArenaHandler,
};

/** Getter of the object `BUILTIN_HANDLER` */
Expand Down
30 changes: 30 additions & 0 deletions src/builtins/segmentArena.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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 = {};
6 changes: 3 additions & 3 deletions src/errors/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export class RangeCheckOutOfBounds extends BuiltinError {
}
}

/** ECDSA signature cannot be retrieved from dictionnary at `offset` */
/** ECDSA signature cannot be retrieved from dictionary at `offset` */
export class UndefinedECDSASignature extends BuiltinError {
constructor(offset: number) {
super(
`ECDSA signature cannot be retrieved from dictionnary at offset ${offset}`
`ECDSA signature cannot be retrieved from dictionary at offset ${offset}`
);
}
}
Expand All @@ -48,7 +48,7 @@ public key (negative): ${pubKeyNegHex}
}
}

/** The signature dictionnary is undefined */
/** The signature dictionary is undefined */
export class UndefinedSignatureDict extends BuiltinError {}

/** An offset of type number is expected */
Expand Down
10 changes: 10 additions & 0 deletions src/errors/dictionary.ts
Original file line number Diff line number Diff line change
@@ -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()}`);
}
}
12 changes: 12 additions & 0 deletions src/errors/hints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,32 @@ import { Hint } from 'hints/hintSchema';

class HintError extends Error {}

/** The provided register at `cell` is incorrect. It must either be AP or FP. */
export class InvalidCellRefRegister extends HintError {
constructor(cell: CellRef) {
super(`Invalid register, expected AP or FP, got ${cell}`);
}
}

/** The operator of the BinOp operation `op` must either be `Add` or `Mul`. */
export class InvalidOperation extends HintError {
constructor(op: string) {
super(`Invalid BinOp operator - Expected Add or Mul, received ${op}`);
}
}

/** The hint to be executed is unknown. */
export class UnknownHint extends HintError {
constructor(hint: Hint) {
super(`Unknown hint: ${hint}`);
}
}

/** The number of dict accesses is invalid. */
export class InvalidDictAccessesNumber extends HintError {
constructor(ptrDiffValue: number, dictAccessSize: number) {
super(
`The number of dictionary accesses (${ptrDiffValue}) must be a multiple of the dictionary access size (${dictAccessSize})`
);
}
}
7 changes: 7 additions & 0 deletions src/errors/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ export class SegmentOutOfBounds extends MemoryError {
);
}
}

/** Expected a SegmentValue but read `undefined` */
export class UndefinedSegmentValue extends MemoryError {
constructor() {
super(`Expected a SegmentValue, read undefined`);
}
}
15 changes: 15 additions & 0 deletions src/errors/scopeManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class ScopeManagerError extends Error {}

/** The main scope cannot be removed, there must always be at least one scope. */
export class CannotExitMainScope extends ScopeManagerError {
constructor() {
super('Cannot exit the main scope');
}
}

/** The variable `name` is not accessible in the current scope. */
export class VariableNotInScope extends ScopeManagerError {
constructor(name: string) {
super(`Variable ${name} is not in scope`);
}
}
Loading
Loading