Here is a how-to, using the hint GetSegmentArenaIndex
as an example:
Find the signature of the hint in the Cairo compiler: GetSegmentArenaIndex
/// 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.
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:
"GetSegmentArenaIndex": {
"dict_end_ptr": {
"Deref": {
"register": "FP",
"offset": -3
}
},
"dict_index": {
"register": "FP",
"offset": 0
}
}
-
Add
GetSegmentArenaIndex
to theHintName
enum insrc/hints/hintName.ts
. It is used to identify the hint before executing it in a run. Hints are ordered in an ascending alphabetical order.// hintName.ts export enum HintName { // ... GetSegmentArenaIndex = 'GetSegmentArenaIndex', }
-
Create the file
src/hints/dict/getSegmentArenaIndex.ts
. Place the file in the appropriate sub-folder category, heredict
because the hint is dedicated to dictionaries. -
Create and export a Zod object
getSegmentArenaIndexParser
which follows the hint signature:// 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:
- Enforce camelCase in fields name
- Add a field
type
which takes the corresponding value of theHintName
enum.
-
Add the parser to the Zod union
hint
insrc/hints/hintSchema.ts
:// hintSchema.ts const hint = z.union([ // ... getSegmentArenaIndexParser, ]);
Now, we can implement the core logic of the hint.
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
and the
cairo-lang-runner
from the Cairo compiler.
The last step is adding the hint to the handler object.
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.
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.
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