Skip to content

Commit

Permalink
feat: add layouts (#119)
Browse files Browse the repository at this point in the history
* feat: add layout option to CLI

* feat: add short flags for layout and function options in CLI

* chore: add JSDoc CairoRunner errors

* feat: add builtins validation against layout

* refactor: refactor layout names array

* fix: filter invalid builtins

* refactor: use includes method

* refactor: use a single error for builtin validation

* feat: add layout class attribute

* refactor: rename UnorderedBuiltins to InvalidBuiltins

* feat: add test on CairoRunner builtin validation against layout

* refactor: remove needles variables in builtins validation tests

* refactor: simplify isSubsequence to a recursive version

* feat: add builtin segment getter from name

* feat: add ratio

* refactor: remove entrypoint attribute in CairoRunner

* doc: check run Cairo programs box

* chore: remove Cairo program with arguments

* chore: update cairo vm zig

* chore: add Cairo programs to run-all target

* feat: add validation of allowed builtins not in layout (segment_arena, gas, system)

* chore: add more cases to layout builtin validation

* chore: update JSDoc getOutput

* chore: add JSDoc

* doc: improve Layout JSDoc for clarity
  • Loading branch information
zmalatrax authored Jul 30, 2024
1 parent dcaee45 commit 6b9bb03
Show file tree
Hide file tree
Showing 11 changed files with 677 additions and 52 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ $(CAIRO_VM_RS_CLI):

$(CAIRO_VM_ZIG_CLI):
@git submodule update --init ziggy-starkdust \
cd ziggy-starkdust; zig build
cd ziggy-starkdust; zig build -Doptimize=ReleaseFast

build:
@bun install; bun link
Expand Down Expand Up @@ -70,13 +70,13 @@ compile: compile-cairo-zero compile-cairo



run-all: $(VALID_COMPILED_CAIRO_0_FILES)
run-all: $(VALID_COMPILED_CAIRO_0_FILES) $(COMPILED_CAIRO_FILES)
@failed_tests_ctr=0; \
failed_tests=""; \
passed_tests_ctr=0; \
for file in $^; do \
echo "Running $$file..."; \
cairo run -s $$file; \
cairo run -s -l all_cairo $$file; \
exit_code=$$?; \
if [ $$exit_code -ne 0 ]; then \
failed_tests_ctr=$$((failed_tests_ctr + 1)); \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ You can still add it as a dependency with a local copy:
| Goals | Done? |
| ---------------------------- | ------- |
| Run basic Cairo Zero program | ☑ |
| Run basic Cairo program | ☐ |
| Run basic Cairo program | ☑ |
| Add [builtins](#builtins) | ☑ |
| Add [hints](#hints) | ☐ |
| Run StarkNet contracts | ☐ |
Expand Down
3 changes: 0 additions & 3 deletions cairo_programs/cairo/hints/test_less_than_args.cairo

This file was deleted.

7 changes: 6 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ program
)
.option('-s, --silent', 'silent all logs')
.addOption(
new Option('--fn <NAME>', 'Function to be executed').default('main')
new Option('-l, --layout <LAYOUT>', 'Layout to be used').default('plain')
)
.addOption(
new Option('-f, --function <NAME>', 'Function to be executed').default(
'main'
)
)
.option('--no-relocate', 'do not relocate memory')
.addOption(
Expand Down
24 changes: 18 additions & 6 deletions src/errors/cairoRunner.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
class CairoRunnerError extends Error {}

/** The relocated memory is empty. It cannot be exported. */
export class EmptyRelocatedMemory extends CairoRunnerError {
constructor() {
super('Relocated memory is empty');
}
}

/** The Cairo Zero hints are not supported. */
export class CairoZeroHintsNotSupported extends CairoRunnerError {
constructor() {
super('Cairo Zero hints are not supported yet.');
}
}

export class CairoOutputNotSupported extends CairoRunnerError {
constructor() {
super('The output serialization of Cairo programs is not supported yet.');
}
}

/** The given entrypoint is not in the program, it cannot be executed. */
export class UndefinedEntrypoint extends CairoRunnerError {
constructor(name: string) {
super(`The function to be executed doesn't exist: ${name}`);
}
}

/** The program builtins are not a subsequence of the builtins available in the chosen layout. */
export class InvalidBuiltins extends CairoRunnerError {
constructor(
programBuiltins: string[],
layoutBuiltins: string[],
layout: string
) {
super(
`The program builtins are not a subsequence of the '${layout}' layout builtins.
Program builtins: ${programBuiltins.join(', ')}
Layout builtins: ${layoutBuiltins.join(', ')}`
);
}
}
123 changes: 103 additions & 20 deletions src/runners/cairoRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

import { InvalidBuiltins } from 'errors/cairoRunner';
import { RangeCheckOutOfBounds } from 'errors/builtins';

import { Felt } from 'primitives/felt';
import { Relocatable } from 'primitives/relocatable';
import { parseProgram } from 'vm/program';
import { outputHandler } from 'builtins/output';
import { bitwiseHandler } from 'builtins/bitwise';
import { layouts } from './layout';
import { CairoRunner, RunOptions } from './cairoRunner';

const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cairo-vm-ts-'));
Expand Down Expand Up @@ -153,23 +157,32 @@ describe('cairoRunner', () => {

describe('builtins', () => {
describe('bitwise', () => {
test('should compute bitwise operations 12 & 10, 12 ^10 and 12 | 10', () => {
const runner = CairoRunner.fromProgram(BITWISE_PROGRAM);
test('should compute bitwise operations 12 & 10, 12 ^ 10 and 12 | 10', () => {
const runner = CairoRunner.fromProgram(BITWISE_PROGRAM, 'all_cairo');
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);

const expectedAnd = new Felt(8n);
const expectedXor = new Felt(6n);
const expectedOr = new Felt(14n);
const executionSize = runner.vm.memory.getSegmentSize(1);
const executionEnd = runner.executionBase.add(executionSize);
expect(runner.vm.memory.get(executionEnd.sub(4))).toEqual(new Felt(8n));
expect(runner.vm.memory.get(executionEnd.sub(3))).toEqual(new Felt(6n));
expect(runner.vm.memory.get(executionEnd.sub(2))).toEqual(
new Felt(14n)
expect(runner.vm.memory.get(executionEnd.sub(4))).toEqual(expectedAnd);
expect(runner.vm.memory.get(executionEnd.sub(3))).toEqual(expectedXor);
expect(runner.vm.memory.get(executionEnd.sub(2))).toEqual(expectedOr);

const bitwiseSegment = runner.getBuiltinSegment('bitwise');
const expectedBitwiseSegment = new Proxy(
[new Felt(12n), new Felt(10n), expectedAnd, expectedXor, expectedOr],
bitwiseHandler
);
expect(bitwiseSegment).toEqual(expectedBitwiseSegment);
});
});

describe('ec_op', () => {
test('should properly compute R = P + 34Q', () => {
const runner = CairoRunner.fromProgram(EC_OP_PROGRAM);
const runner = CairoRunner.fromProgram(EC_OP_PROGRAM, 'all_cairo');
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);

Expand All @@ -189,7 +202,7 @@ describe('cairoRunner', () => {

describe('pedersen', () => {
test('should properly compute Pedersen hashes of (0, 0), (0, 1), (1, 0) and (54, 1249832432) tuples', () => {
const runner = CairoRunner.fromProgram(PEDERSEN_PROGRAM);
const runner = CairoRunner.fromProgram(PEDERSEN_PROGRAM, 'all_cairo');
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);

Expand Down Expand Up @@ -218,7 +231,7 @@ describe('cairoRunner', () => {

describe('poseidon', () => {
test('should properly compute Poseidon states from initial states (1, 2, 3) and (13, 40, 36)', () => {
const runner = CairoRunner.fromProgram(POSEIDON_PROGRAM);
const runner = CairoRunner.fromProgram(POSEIDON_PROGRAM, 'all_cairo');
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);

Expand Down Expand Up @@ -262,7 +275,10 @@ describe('cairoRunner', () => {

describe('keccak', () => {
test('Should properly compute state from input state KeccakBuiltinState(0, 0, 0, 0, 0, 0, 0, 0)', () => {
const runner = CairoRunner.fromProgram(KECCAK_SEED_PROGRAM);
const runner = CairoRunner.fromProgram(
KECCAK_SEED_PROGRAM,
'all_cairo'
);
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);

Expand All @@ -288,7 +304,7 @@ describe('cairoRunner', () => {
});

test('Should properly compute state from input state KeccakBuiltinState(1, 2, 3, 4, 5, 6, 7, 8)', () => {
const runner = CairoRunner.fromProgram(KECCAK_PROGRAM);
const runner = CairoRunner.fromProgram(KECCAK_PROGRAM, 'all_cairo');
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);

Expand Down Expand Up @@ -316,7 +332,7 @@ describe('cairoRunner', () => {

describe('output', () => {
test('Should properly store the jmp dest value in the output segment', () => {
const runner = CairoRunner.fromProgram(JMP_PROGRAM);
const runner = CairoRunner.fromProgram(JMP_PROGRAM, 'small');
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);
const output = runner.getOutput();
Expand All @@ -325,18 +341,28 @@ describe('cairoRunner', () => {
});

test('Should properly write the result of bitwise 1 & 2 to output segment', () => {
const runner = CairoRunner.fromProgram(BITWISE_OUTPUT_PROGRAM);
const runner = CairoRunner.fromProgram(
BITWISE_OUTPUT_PROGRAM,
'all_cairo'
);
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);
const output = runner.getOutput();
expect(output.length).toEqual(1);
expect(output[0]).toEqual(new Felt(0n));

expect(runner.getBuiltinSegment('bitwise')).toEqual(
new Proxy([new Felt(1n), new Felt(2n), new Felt(0n)], bitwiseHandler)
);
expect(runner.getOutput()).toEqual(
new Proxy([new Felt(0n)], outputHandler)
);
});
});

describe('range_check', () => {
test('should properly write 2 ** 128 - 1 to the range check segment', () => {
const runner = CairoRunner.fromProgram(RANGE_CHECK_PROGRAM);
const runner = CairoRunner.fromProgram(
RANGE_CHECK_PROGRAM,
'all_cairo'
);
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);
const executionSize = runner.vm.memory.getSegmentSize(1);
Expand All @@ -347,7 +373,10 @@ describe('cairoRunner', () => {
});

test('should crash the VM when trying to assert -1 to the range check segment', () => {
const runner = CairoRunner.fromProgram(BAD_RANGE_CHECK_PROGRAM);
const runner = CairoRunner.fromProgram(
BAD_RANGE_CHECK_PROGRAM,
'all_cairo'
);
const config: RunOptions = { relocate: true, offset: 1 };
expect(() => runner.run(config)).toThrow(
new RangeCheckOutOfBounds(new Felt(-1n), 128n)
Expand All @@ -357,7 +386,10 @@ describe('cairoRunner', () => {

describe('range_check96', () => {
test('should properly write 2 ** 96 - 1 to the range check segment', () => {
const runner = CairoRunner.fromProgram(RANGE_CHECK96_PROGRAM);
const runner = CairoRunner.fromProgram(
RANGE_CHECK96_PROGRAM,
'all_cairo'
);
const config: RunOptions = { relocate: true, offset: 1 };
runner.run(config);
const executionSize = runner.vm.memory.getSegmentSize(1);
Expand All @@ -368,7 +400,10 @@ describe('cairoRunner', () => {
});

test('should crash the VM when trying to assert 2 ** 96 to the range check segment', () => {
const runner = CairoRunner.fromProgram(BAD_RANGE_CHECK96_PROGRAM);
const runner = CairoRunner.fromProgram(
BAD_RANGE_CHECK96_PROGRAM,
'all_cairo'
);
const config: RunOptions = { relocate: true, offset: 1 };
expect(() => runner.run(config)).toThrow(
new RangeCheckOutOfBounds(new Felt(2n ** 96n), 96n)
Expand Down Expand Up @@ -413,4 +448,52 @@ describe('cairoRunner', () => {
expect(tsTrace.equals(pyTrace)).toBeTrue();
});
});

describe('layout', () => {
test.each([
[[], 'plain'],
[['bitwise'], 'recursive'],
[['output', 'pedersen'], 'small'],
[['output', 'pedersen', 'range_check'], 'small'],
[['pedersen', 'range_check'], 'small'],
[['output', 'range_check', 'poseidon'], 'starknet'],
[['output', 'range_check', 'segment_arena'], 'all_cairo'],
[['range_check', 'gas_builtin'], 'all_cairo'],
[['output', 'range_check', 'segment_arena', 'system'], 'starknet'],
])(
'should correctly parse a program with an appropriate layout',
(builtins, layout) => {
const dummyProgram = parseProgram(
fs.readFileSync('cairo_programs/cairo_0/fibonacci.json', 'utf-8')
);

expect(
() => new CairoRunner(dummyProgram, [], layout, 0, builtins)
).not.toThrow();
}
);

test.each([
[['output'], 'plain'],
[['ecdsa'], 'recursive'],
[['output', 'range_check', 'pedersen'], 'small'],
[['output', 'pedersen', 'range_check', 'ecdsa', 'ec_op'], 'small'],
[['output', 'range_check', 'poseidon', 'range_check96'], 'starknet'],
[['output', 'segment_arena', 'range_check'], 'all_cairo'],
[['output', 'range_check', 'gas_builtin', 'segment_arena'], 'all_cairo'],
])(
'should throw InvalidBuiltins if builtins are not within the layout or unordered',
(builtins, layout) => {
const dummyProgram = parseProgram(
fs.readFileSync('cairo_programs/cairo_0/fibonacci.json', 'utf-8')
);

expect(
() => new CairoRunner(dummyProgram, [], layout, 0, builtins)
).toThrow(
new InvalidBuiltins(builtins, layouts[layout].builtins, layout)
);
}
);
});
});
Loading

0 comments on commit 6b9bb03

Please sign in to comment.