├── .claspignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── trunk-check.yaml ├── .gitignore ├── .trunk ├── .gitignore ├── configs │ ├── .isort.cfg │ ├── .markdownlint.yaml │ ├── .prettierrc.json │ ├── .yamllint.yaml │ └── ruff.toml └── trunk.yaml ├── .vscode └── launch.json ├── README.md ├── appsscript.json ├── cairo0 ├── builtins │ ├── bitwise_builtin.cairo │ ├── ecop_builtin.cairo │ ├── keccak_builtin.cairo │ ├── pedersen_builtin.cairo │ ├── poseidon_builtin.cairo │ ├── range_check96_builtin.cairo │ └── range_check_builtin.cairo ├── dict.cairo ├── fibonacci │ ├── fibonacci.cairo │ └── main.cairo ├── main.cairo ├── main_memory.bin └── main_trace.bin ├── package-lock.json ├── package.json ├── pyproject.toml ├── src ├── builtins │ ├── bitwise │ │ └── bitwise.ts │ ├── ec_op │ │ ├── ec_op.ts │ │ └── modular_ar.ts │ ├── ecdsa │ │ └── ecdsa.ts │ ├── keccak │ │ ├── keccak_f1600.ts │ │ └── keccak_utils.ts │ ├── pedersen │ │ ├── constants.ts │ │ └── pedersen.ts │ ├── poseidon │ │ ├── constants.ts │ │ └── poseidon.ts │ ├── range_check │ │ └── range_check.ts │ └── range_check96 │ │ └── range_check96.ts ├── tests.ts ├── ui │ ├── Code.ts │ ├── api.ts │ ├── dialog.html │ ├── tutorials │ │ ├── fibonacci │ │ │ ├── program.ts │ │ │ └── steps.ts │ │ └── tutorials.ts │ └── ui.ts ├── utils.ts └── vm │ ├── constants.ts │ ├── errors.ts │ ├── instructions.ts │ └── vm.ts ├── tsconfig.json └── uv.lock /.claspignore: -------------------------------------------------------------------------------- 1 | .git/**/* 2 | .trunk/**/* 3 | node_modules/**/* 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Pull Request Title [TO BE UPDATED] 4 | 5 | 7 | 8 | Time spent on this PR: 9 | 10 | ## Pull request type 11 | 12 | 14 | 15 | Please check the type of change your PR introduces: 16 | 17 | - [ ] Bugfix 18 | - [ ] Feature 19 | - [ ] Code style update (formatting, renaming) 20 | - [ ] Refactoring (no functional changes, no api changes) 21 | - [ ] Build related changes 22 | - [ ] Documentation content changes 23 | - [ ] Other (please describe): 24 | 25 | ## What is the current behavior? 26 | 27 | 29 | 30 | Resolves # 31 | 32 | ## What is the new behavior? 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/trunk-check.yaml: -------------------------------------------------------------------------------- 1 | name: Trunk 2 | 3 | on: [pull_request] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | trunk_check: 13 | name: Trunk Check Runner 14 | runs-on: ubuntu-latest 15 | permissions: 16 | checks: write # For trunk to post annotations 17 | contents: read # For repo checkout 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | - name: Trunk Check 23 | uses: trunk-io/trunk-action@v1 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .clasprc.json 2 | .clasp.json 3 | .venv 4 | creds.json 5 | node_modules 6 | -------------------------------------------------------------------------------- /.trunk/.gitignore: -------------------------------------------------------------------------------- 1 | *out 2 | *logs 3 | *actions 4 | *notifications 5 | *tools 6 | plugins 7 | user_trunk.yaml 8 | user.yaml 9 | tmp 10 | -------------------------------------------------------------------------------- /.trunk/configs/.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile=black 3 | -------------------------------------------------------------------------------- /.trunk/configs/.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # Autoformatter friendly markdownlint config (all formatting rules disabled) 2 | default: true 3 | blank_lines: false 4 | bullet: false 5 | html: false 6 | indentation: false 7 | line_length: false 8 | spaces: false 9 | url: false 10 | whitespace: false 11 | -------------------------------------------------------------------------------- /.trunk/configs/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "always" 3 | } 4 | -------------------------------------------------------------------------------- /.trunk/configs/.yamllint.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | quoted-strings: 3 | required: only-when-needed 4 | extra-allowed: ["{|}"] 5 | key-duplicates: {} 6 | octal-values: 7 | forbid-implicit-octal: true 8 | -------------------------------------------------------------------------------- /.trunk/configs/ruff.toml: -------------------------------------------------------------------------------- 1 | # Generic, formatter-friendly config. 2 | select = ["B", "D3", "E", "F"] 3 | 4 | # Never enforce `E501` (line length violations). This should be handled by formatters. 5 | ignore = ["E501"] 6 | -------------------------------------------------------------------------------- /.trunk/trunk.yaml: -------------------------------------------------------------------------------- 1 | # This file controls the behavior of Trunk: https://docs.trunk.io/cli 2 | # To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml 3 | version: 0.1 4 | cli: 5 | version: 1.22.10 6 | # Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) 7 | plugins: 8 | sources: 9 | - id: trunk 10 | ref: v1.6.7 11 | uri: https://github.com/trunk-io/plugins 12 | # Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) 13 | runtimes: 14 | enabled: 15 | - node@18.20.5 16 | - python@3.10.8 17 | # This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) 18 | lint: 19 | enabled: 20 | - actionlint@1.7.7 21 | - bandit@1.8.2 22 | - black@25.1.0 23 | - isort@6.0.0 24 | - osv-scanner@1.9.2 25 | - ruff@0.9.5 26 | - taplo@0.9.3 27 | - yamllint@1.35.1 28 | - checkov@3.2.369 29 | - git-diff-check 30 | - markdownlint@0.44.0 31 | - prettier@3.5.0 32 | - trufflehog@3.88.5 33 | actions: 34 | disabled: 35 | - trunk-announce 36 | - trunk-check-pre-push 37 | - trunk-fmt-pre-commit 38 | enabled: 39 | - trunk-upgrade-available 40 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug cairo-compile", 9 | "type": "debugpy", 10 | "request": "launch", 11 | "program": "${command:python.interpreterPath}/../cairo-compile", 12 | "args": [ 13 | "cairo0/main.cairo", 14 | "--output", 15 | "cairo0/main_compiled.json", 16 | "--cairo_path", 17 | "cairo0" 18 | ], 19 | "console": "integratedTerminal", 20 | "justMyCode": false 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cairo VM gs 2 | 3 | ## Wat Cairo VM 4 | 5 | Cairo VM stands for Cairo Virtual Machine (VM). Cairo stands for CPU AIR and is 6 | a Turing complete, STARK friendly, CPU like instruction set and programming 7 | language. At a high level, it lets the developer write program as with any other 8 | programming language, but provable programs. 9 | 10 | What is a provable program? Well, a program for which a given execution can be 11 | proven without the need to re-execute it. In other words, with a provable 12 | program P, you can run P on a given input I, returning output O, and convince 13 | anyone that P(I) = O in logarithmic time. An application can be for example that 14 | you delegate some heavy computation of cost N to a given cloud provider, and can 15 | verify in cost (log N)^2 that they actually did the job and not just returned a 16 | random number. 17 | 18 | ## Were Cairo VM 19 | 20 | The Cairo VM is currently powering the Starknet blockchain, though its usage is 21 | not limited to blockchain applications. There are currently five open sources 22 | implementations of the spec, as defined in the Cairo paper: 23 | 24 | 1. The original Python implementation, deprecated since July 2023 25 | 2. The Starknet current VM, in rust, from LambdaClass 26 | 3. A primarily educational Cairo VM in go, also from LambdaClass 27 | 4. Another go implementation, by Nethermind 28 | 5. Yet another educational implementation in typescript, by Kakarot 29 | 6. And here we are, this spreadsheet with its associated app script! 30 | 31 | ## Wy Cairo VM gs 32 | 33 | At the heart of the Cairo VM lies the concept of felt and relocatable. Because 34 | we are here to keep it simple, say that: 35 | 36 | - a felt (or Field Element) is a constant number between 0 and p (a given prime 37 | number), much like (here the cryptographers die) in usual computation a 38 | variable is a uint of some kind (uint32 or uint64 in most of the languages and 39 | architectures, uint256 in the Ethereum Virtual Machine for example); 40 | - a relocatable is a reference to a felt or to another relocatable (?). While 41 | this sounds complicated, it appears that spreadsheets are by design exactly 42 | this, a mix of cells with constant values and cells referencing other cells. 43 | 44 | In the end, the task of the Cairo VM is just to fill some cells such that, at 45 | each line, the constraints defined by the program holds. For example, one may 46 | have at a given line an instruction like: 47 | 48 | | Opcode | dst | src | 49 | | -------- | --- | ---- | 50 | | AssertEq | 10 | =D33 | 51 | 52 | In this context, the run of the Cairo VM would consist in writing in D33 the 53 | value 10 such that the equality with B33 holds. That's it, you've become a 54 | cryptographer specialized in ZK. 55 | 56 | ## How Cairo VM gs 57 | 58 | The aim of this implementation is definitely not to reach prod perf (though 59 | everything is possible in gsheet) but to provide an easy educational, 60 | plug-and-play tool for running step by step compiled Cairo codes. 61 | 62 | It provides few new functions that can be used directly from the sheet: 63 | 64 | - DECODE_INSTRUCTION(hex): disassemble a Cairo compiled program 65 | 66 | It also adds a menu "Cairo VM" which allows to: 67 | 68 | - load a compiled cairo program 69 | - run it step by step 70 | - run it until the end 71 | 72 | ## Wen Cairo VM gs 73 | 74 | This spreadsheet is under active development. Currently, only the 75 | DECODE_INSTRUCTION is working. Next priorities are to fix the Step and Run menu 76 | over a small fibonacci program. Then, we will add the option to load any 77 | compiled program. Eventually, we may stop there or keep improving the VM and 78 | support Cairo 1. 79 | 80 | ## Contribute 81 | 82 | ### Setup 83 | 84 | To start contributing: 85 | 86 | - fork this repo and `git clone` (as usual) 87 | - run `npm install` to get the 88 | [clasp](https://developers.google.com/apps-script/guides/clasp) CLI tool 89 | - create a copy of the [main Google sheet](https://cairovm.gs) in the UX 90 | - clone your app script project locally with the CLI 91 | 92 | ```bash 93 | npx clasp login 94 | npx clasp clone 204 | 205 | 206 |
207 | 210 | 211 | 212 |
213 | 214 | 218 |
219 | 220 |
221 | 222 | 236 |
237 | 238 | 241 |
242 |
243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /src/ui/tutorials/fibonacci/program.ts: -------------------------------------------------------------------------------- 1 | const FIBONACCI_PROGRAM = JSON.stringify( 2 | { 3 | attributes: [], 4 | builtins: [], 5 | compiler_version: "0.13.1", 6 | data: [ 7 | "0x482680017ffd8000", 8 | "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffff", 9 | "0x480680017fff8000", 10 | "0x1", 11 | "0x480680017fff8000", 12 | "0x1", 13 | "0x482480017ffd8000", 14 | "0x800000000000011000000000000000000000000000000000000000000000000", 15 | "0x48127ffe7fff8000", 16 | "0x48307ffd7ffc8000", 17 | "0x20680017fff7ffd", 18 | "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffffd", 19 | "0x208b7fff7fff7ffe", 20 | "0x480680017fff8000", 21 | "0x4", 22 | "0x1104800180018000", 23 | "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffff2", 24 | "0x208b7fff7fff7ffe", 25 | ], 26 | debug_info: { 27 | file_contents: {}, 28 | instruction_locations: { 29 | "0": { 30 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 31 | flow_tracking_data: { 32 | ap_tracking: { 33 | group: 0, 34 | offset: 0, 35 | }, 36 | reference_ids: { 37 | "fibonacci.fibonacci.n": 0, 38 | }, 39 | }, 40 | hints: [], 41 | inst: { 42 | end_col: 22, 43 | end_line: 2, 44 | input_file: { 45 | filename: 46 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 47 | }, 48 | start_col: 17, 49 | start_line: 2, 50 | }, 51 | }, 52 | "2": { 53 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 54 | flow_tracking_data: { 55 | ap_tracking: { 56 | group: 0, 57 | offset: 1, 58 | }, 59 | reference_ids: { 60 | "fibonacci.fibonacci.n": 1, 61 | }, 62 | }, 63 | hints: [], 64 | inst: { 65 | end_col: 18, 66 | end_line: 3, 67 | input_file: { 68 | filename: 69 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 70 | }, 71 | start_col: 17, 72 | start_line: 3, 73 | }, 74 | }, 75 | "4": { 76 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 77 | flow_tracking_data: { 78 | ap_tracking: { 79 | group: 0, 80 | offset: 2, 81 | }, 82 | reference_ids: { 83 | "fibonacci.fibonacci.i": 2, 84 | "fibonacci.fibonacci.n": 1, 85 | }, 86 | }, 87 | hints: [], 88 | inst: { 89 | end_col: 18, 90 | end_line: 4, 91 | input_file: { 92 | filename: 93 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 94 | }, 95 | start_col: 17, 96 | start_line: 4, 97 | }, 98 | }, 99 | "6": { 100 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 101 | flow_tracking_data: { 102 | ap_tracking: { 103 | group: 0, 104 | offset: 3, 105 | }, 106 | reference_ids: { 107 | "fibonacci.fibonacci.i": 5, 108 | "fibonacci.fibonacci.j": 6, 109 | "fibonacci.fibonacci.n": 4, 110 | "fibonacci.fibonacci.next": 7, 111 | }, 112 | }, 113 | hints: [], 114 | inst: { 115 | end_col: 22, 116 | end_line: 12, 117 | input_file: { 118 | filename: 119 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 120 | }, 121 | start_col: 17, 122 | start_line: 12, 123 | }, 124 | }, 125 | "8": { 126 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 127 | flow_tracking_data: { 128 | ap_tracking: { 129 | group: 0, 130 | offset: 4, 131 | }, 132 | reference_ids: { 133 | "fibonacci.fibonacci.i": 5, 134 | "fibonacci.fibonacci.j": 6, 135 | "fibonacci.fibonacci.n": 8, 136 | "fibonacci.fibonacci.next": 7, 137 | }, 138 | }, 139 | hints: [], 140 | inst: { 141 | end_col: 21, 142 | end_line: 9, 143 | input_file: { 144 | filename: 145 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 146 | }, 147 | parent_location: [ 148 | { 149 | end_col: 18, 150 | end_line: 13, 151 | input_file: { 152 | filename: 153 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 154 | }, 155 | start_col: 17, 156 | start_line: 13, 157 | }, 158 | "While expanding the reference 'j' in:", 159 | ], 160 | start_col: 13, 161 | start_line: 9, 162 | }, 163 | }, 164 | "9": { 165 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 166 | flow_tracking_data: { 167 | ap_tracking: { 168 | group: 0, 169 | offset: 5, 170 | }, 171 | reference_ids: { 172 | "fibonacci.fibonacci.i": 9, 173 | "fibonacci.fibonacci.j": 6, 174 | "fibonacci.fibonacci.n": 8, 175 | "fibonacci.fibonacci.next": 7, 176 | }, 177 | }, 178 | hints: [], 179 | inst: { 180 | end_col: 21, 181 | end_line: 10, 182 | input_file: { 183 | filename: 184 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 185 | }, 186 | parent_location: [ 187 | { 188 | end_col: 21, 189 | end_line: 14, 190 | input_file: { 191 | filename: 192 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 193 | }, 194 | start_col: 17, 195 | start_line: 14, 196 | }, 197 | "While expanding the reference 'next' in:", 198 | ], 199 | start_col: 16, 200 | start_line: 10, 201 | }, 202 | }, 203 | "10": { 204 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 205 | flow_tracking_data: { 206 | ap_tracking: { 207 | group: 0, 208 | offset: 6, 209 | }, 210 | reference_ids: { 211 | "fibonacci.fibonacci.i": 9, 212 | "fibonacci.fibonacci.j": 10, 213 | "fibonacci.fibonacci.n": 8, 214 | "fibonacci.fibonacci.next": 7, 215 | }, 216 | }, 217 | hints: [], 218 | inst: { 219 | end_col: 23, 220 | end_line: 20, 221 | input_file: { 222 | filename: 223 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 224 | }, 225 | start_col: 5, 226 | start_line: 20, 227 | }, 228 | }, 229 | "12": { 230 | accessible_scopes: ["fibonacci", "fibonacci.fibonacci"], 231 | flow_tracking_data: { 232 | ap_tracking: { 233 | group: 0, 234 | offset: 6, 235 | }, 236 | reference_ids: { 237 | "fibonacci.fibonacci.i": 12, 238 | "fibonacci.fibonacci.j": 13, 239 | "fibonacci.fibonacci.n": 11, 240 | "fibonacci.fibonacci.next": 7, 241 | }, 242 | }, 243 | hints: [], 244 | inst: { 245 | end_col: 14, 246 | end_line: 27, 247 | input_file: { 248 | filename: 249 | "/home/antoine/Documents/cairo-vm-gs/cairo0/fibonacci.cairo", 250 | }, 251 | start_col: 5, 252 | start_line: 27, 253 | }, 254 | }, 255 | "13": { 256 | accessible_scopes: ["__main__", "__main__.main"], 257 | flow_tracking_data: { 258 | ap_tracking: { 259 | group: 1, 260 | offset: 0, 261 | }, 262 | reference_ids: {}, 263 | }, 264 | hints: [], 265 | inst: { 266 | end_col: 16, 267 | end_line: 4, 268 | input_file: { 269 | filename: "../../cairo0-test/fibonacci/main.cairo", 270 | }, 271 | start_col: 15, 272 | start_line: 4, 273 | }, 274 | }, 275 | "15": { 276 | accessible_scopes: ["__main__", "__main__.main"], 277 | flow_tracking_data: { 278 | ap_tracking: { 279 | group: 1, 280 | offset: 1, 281 | }, 282 | reference_ids: {}, 283 | }, 284 | hints: [], 285 | inst: { 286 | end_col: 17, 287 | end_line: 4, 288 | input_file: { 289 | filename: "../../cairo0-test/fibonacci/main.cairo", 290 | }, 291 | start_col: 5, 292 | start_line: 4, 293 | }, 294 | }, 295 | "17": { 296 | accessible_scopes: ["__main__", "__main__.main"], 297 | flow_tracking_data: { 298 | ap_tracking: { 299 | group: 2, 300 | offset: 0, 301 | }, 302 | reference_ids: {}, 303 | }, 304 | hints: [], 305 | inst: { 306 | end_col: 15, 307 | end_line: 5, 308 | input_file: { 309 | filename: "../../cairo0-test/fibonacci/main.cairo", 310 | }, 311 | start_col: 5, 312 | start_line: 5, 313 | }, 314 | }, 315 | }, 316 | }, 317 | hints: {}, 318 | identifiers: { 319 | "__main__.fibonacci": { 320 | destination: "fibonacci.fibonacci", 321 | type: "alias", 322 | }, 323 | "__main__.main": { 324 | decorators: [], 325 | pc: 13, 326 | type: "function", 327 | }, 328 | "__main__.main.Args": { 329 | full_name: "__main__.main.Args", 330 | members: {}, 331 | size: 0, 332 | type: "struct", 333 | }, 334 | "__main__.main.ImplicitArgs": { 335 | full_name: "__main__.main.ImplicitArgs", 336 | members: {}, 337 | size: 0, 338 | type: "struct", 339 | }, 340 | "__main__.main.Return": { 341 | cairo_type: "()", 342 | type: "type_definition", 343 | }, 344 | "__main__.main.SIZEOF_LOCALS": { 345 | type: "const", 346 | value: 0, 347 | }, 348 | "fibonacci.fibonacci": { 349 | decorators: [], 350 | pc: 0, 351 | type: "function", 352 | }, 353 | "fibonacci.fibonacci.Args": { 354 | full_name: "fibonacci.fibonacci.Args", 355 | members: { 356 | n: { 357 | cairo_type: "felt", 358 | offset: 0, 359 | }, 360 | }, 361 | size: 1, 362 | type: "struct", 363 | }, 364 | "fibonacci.fibonacci.ImplicitArgs": { 365 | full_name: "fibonacci.fibonacci.ImplicitArgs", 366 | members: {}, 367 | size: 0, 368 | type: "struct", 369 | }, 370 | "fibonacci.fibonacci.Return": { 371 | cairo_type: "felt", 372 | type: "type_definition", 373 | }, 374 | "fibonacci.fibonacci.SIZEOF_LOCALS": { 375 | type: "const", 376 | value: 0, 377 | }, 378 | "fibonacci.fibonacci.body": { 379 | pc: 6, 380 | type: "label", 381 | }, 382 | "fibonacci.fibonacci.end": { 383 | pc: 12, 384 | type: "label", 385 | }, 386 | "fibonacci.fibonacci.i": { 387 | cairo_type: "felt", 388 | full_name: "fibonacci.fibonacci.i", 389 | references: [ 390 | { 391 | ap_tracking_data: { 392 | group: 0, 393 | offset: 2, 394 | }, 395 | pc: 4, 396 | value: "[cast(ap + (-1), felt*)]", 397 | }, 398 | { 399 | ap_tracking_data: { 400 | group: 0, 401 | offset: 3, 402 | }, 403 | pc: 6, 404 | value: "[cast(ap + (-2), felt*)]", 405 | }, 406 | { 407 | ap_tracking_data: { 408 | group: 0, 409 | offset: 5, 410 | }, 411 | pc: 9, 412 | value: "[cast(ap + (-1), felt*)]", 413 | }, 414 | { 415 | ap_tracking_data: { 416 | group: 0, 417 | offset: 6, 418 | }, 419 | pc: 12, 420 | value: "[cast(ap + (-2), felt*)]", 421 | }, 422 | ], 423 | type: "reference", 424 | }, 425 | "fibonacci.fibonacci.j": { 426 | cairo_type: "felt", 427 | full_name: "fibonacci.fibonacci.j", 428 | references: [ 429 | { 430 | ap_tracking_data: { 431 | group: 0, 432 | offset: 3, 433 | }, 434 | pc: 6, 435 | value: "[cast(ap + (-1), felt*)]", 436 | }, 437 | { 438 | ap_tracking_data: { 439 | group: 0, 440 | offset: 3, 441 | }, 442 | pc: 6, 443 | value: "[cast(ap + (-1), felt*)]", 444 | }, 445 | { 446 | ap_tracking_data: { 447 | group: 0, 448 | offset: 6, 449 | }, 450 | pc: 10, 451 | value: "[cast(ap + (-1), felt*)]", 452 | }, 453 | { 454 | ap_tracking_data: { 455 | group: 0, 456 | offset: 6, 457 | }, 458 | pc: 12, 459 | value: "[cast(ap + (-1), felt*)]", 460 | }, 461 | ], 462 | type: "reference", 463 | }, 464 | "fibonacci.fibonacci.n": { 465 | cairo_type: "felt", 466 | full_name: "fibonacci.fibonacci.n", 467 | references: [ 468 | { 469 | ap_tracking_data: { 470 | group: 0, 471 | offset: 0, 472 | }, 473 | pc: 0, 474 | value: "[cast(fp + (-3), felt*)]", 475 | }, 476 | { 477 | ap_tracking_data: { 478 | group: 0, 479 | offset: 1, 480 | }, 481 | pc: 2, 482 | value: "[cast(ap + (-1), felt*)]", 483 | }, 484 | { 485 | ap_tracking_data: { 486 | group: 0, 487 | offset: 3, 488 | }, 489 | pc: 6, 490 | value: "[cast(ap + (-3), felt*)]", 491 | }, 492 | { 493 | ap_tracking_data: { 494 | group: 0, 495 | offset: 4, 496 | }, 497 | pc: 8, 498 | value: "[cast(ap + (-1), felt*)]", 499 | }, 500 | { 501 | ap_tracking_data: { 502 | group: 0, 503 | offset: 6, 504 | }, 505 | pc: 12, 506 | value: "[cast(ap + (-3), felt*)]", 507 | }, 508 | ], 509 | type: "reference", 510 | }, 511 | "fibonacci.fibonacci.next": { 512 | cairo_type: "felt", 513 | full_name: "fibonacci.fibonacci.next", 514 | references: [ 515 | { 516 | ap_tracking_data: { 517 | group: 0, 518 | offset: 3, 519 | }, 520 | pc: 6, 521 | value: "cast([ap + (-2)] + [ap + (-1)], felt)", 522 | }, 523 | ], 524 | type: "reference", 525 | }, 526 | }, 527 | main_scope: "__main__", 528 | prime: "0x800000000000011000000000000000000000000000000000000000000000001", 529 | reference_manager: { 530 | references: [ 531 | { 532 | ap_tracking_data: { 533 | group: 0, 534 | offset: 0, 535 | }, 536 | pc: 0, 537 | value: "[cast(fp + (-3), felt*)]", 538 | }, 539 | { 540 | ap_tracking_data: { 541 | group: 0, 542 | offset: 1, 543 | }, 544 | pc: 2, 545 | value: "[cast(ap + (-1), felt*)]", 546 | }, 547 | { 548 | ap_tracking_data: { 549 | group: 0, 550 | offset: 2, 551 | }, 552 | pc: 4, 553 | value: "[cast(ap + (-1), felt*)]", 554 | }, 555 | { 556 | ap_tracking_data: { 557 | group: 0, 558 | offset: 3, 559 | }, 560 | pc: 6, 561 | value: "[cast(ap + (-1), felt*)]", 562 | }, 563 | { 564 | ap_tracking_data: { 565 | group: 0, 566 | offset: 3, 567 | }, 568 | pc: 6, 569 | value: "[cast(ap + (-3), felt*)]", 570 | }, 571 | { 572 | ap_tracking_data: { 573 | group: 0, 574 | offset: 3, 575 | }, 576 | pc: 6, 577 | value: "[cast(ap + (-2), felt*)]", 578 | }, 579 | { 580 | ap_tracking_data: { 581 | group: 0, 582 | offset: 3, 583 | }, 584 | pc: 6, 585 | value: "[cast(ap + (-1), felt*)]", 586 | }, 587 | { 588 | ap_tracking_data: { 589 | group: 0, 590 | offset: 3, 591 | }, 592 | pc: 6, 593 | value: "cast([ap + (-2)] + [ap + (-1)], felt)", 594 | }, 595 | { 596 | ap_tracking_data: { 597 | group: 0, 598 | offset: 4, 599 | }, 600 | pc: 8, 601 | value: "[cast(ap + (-1), felt*)]", 602 | }, 603 | { 604 | ap_tracking_data: { 605 | group: 0, 606 | offset: 5, 607 | }, 608 | pc: 9, 609 | value: "[cast(ap + (-1), felt*)]", 610 | }, 611 | { 612 | ap_tracking_data: { 613 | group: 0, 614 | offset: 6, 615 | }, 616 | pc: 10, 617 | value: "[cast(ap + (-1), felt*)]", 618 | }, 619 | { 620 | ap_tracking_data: { 621 | group: 0, 622 | offset: 6, 623 | }, 624 | pc: 12, 625 | value: "[cast(ap + (-3), felt*)]", 626 | }, 627 | { 628 | ap_tracking_data: { 629 | group: 0, 630 | offset: 6, 631 | }, 632 | pc: 12, 633 | value: "[cast(ap + (-2), felt*)]", 634 | }, 635 | { 636 | ap_tracking_data: { 637 | group: 0, 638 | offset: 6, 639 | }, 640 | pc: 12, 641 | value: "[cast(ap + (-1), felt*)]", 642 | }, 643 | ], 644 | }, 645 | }, 646 | null, 647 | 2, 648 | ); 649 | -------------------------------------------------------------------------------- /src/ui/tutorials/fibonacci/steps.ts: -------------------------------------------------------------------------------- 1 | const FIBONACCI: string = "Fibonacci"; 2 | 3 | function startFibonacci() { 4 | loadProgram(FIBONACCI_PROGRAM, "Execution", "plain"); 5 | startTutorial(FIBONACCI); 6 | addNotesFibonacci(); 7 | } 8 | 9 | function addNotesFibonacci() { 10 | addNotes(FIBONACCI); 11 | } 12 | 13 | const stepsFibonacci: Step[] = [ 14 | { 15 | cell: "A1", 16 | sheetName: "Run", 17 | message: 18 | "This is the starting point of the Fibonacci tutorial. Go to the next step to execute the fibonacci program on the GS CairoVM !", 19 | action: () => SpreadsheetApp.getActiveSpreadsheet().toast("Hey !"), 20 | }, 21 | { 22 | cell: "A1", 23 | sheetName: "Run", 24 | message: "The script is running...", 25 | action: menuRun, 26 | }, 27 | { 28 | cell: "C3", 29 | sheetName: "Program", 30 | message: "There we go.", 31 | }, 32 | ]; 33 | -------------------------------------------------------------------------------- /src/ui/tutorials/tutorials.ts: -------------------------------------------------------------------------------- 1 | interface Step { 2 | cell: string; 3 | sheetName: string; 4 | message: string; 5 | action?: () => void; 6 | } 7 | 8 | const steps = { 9 | Fibonacci: stepsFibonacci, 10 | }; 11 | 12 | function startTutorial(tutorialName: string) { 13 | clearNotes(); 14 | 15 | const ui = SpreadsheetApp.getUi(); 16 | 17 | const response = ui.alert( 18 | `${tutorialName} Tutorial`, 19 | `Welcome to the ${tutorialName} tutorial! 20 | \nThis tutorial consists of cell annotations being shown to you so that you get a better understanding of the CairoVM. 21 | \nThe cell associated to a step of the tutorial has a yellow background and you can see its annotation by hovering the cell. 22 | \nOnce you are done with reading the note, go in the "Tutorial" menu and click "Next". 23 | \nClick "Ok" to begin.`, 24 | ui.ButtonSet.OK, 25 | ); 26 | 27 | if (response === ui.Button.OK) { 28 | const offset: number = Object.keys(steps).includes(getTutorialName()) 29 | ? -1 30 | : 0; 31 | programSheet 32 | .getRange(programSheet.getLastRow() + 1 + offset, 2) 33 | .setValue(0); 34 | programSheet.getRange(programSheet.getLastRow(), 1).setValue(tutorialName); 35 | doStepTuto(0, tutorialName); 36 | } 37 | } 38 | 39 | function doStepTuto(stepIndex: number, tutorialName: string) { 40 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 41 | 42 | //Clear previous step background 43 | const previousStepIndex = stepIndex == 0 ? 0 : stepIndex - 1; 44 | const previousStep = steps[tutorialName][previousStepIndex]; 45 | const previousSheet = spreadsheet.getSheetByName(previousStep.sheetName); 46 | previousSheet.getRange(previousStep.cell).setBackground("white"); 47 | 48 | //Set focus on step cell 49 | const currentStep = steps[tutorialName][stepIndex]; 50 | const currentSheet = spreadsheet.getSheetByName(currentStep.sheetName); 51 | 52 | const range = currentSheet.getRange(currentStep.cell); 53 | range.setBackground("yellow"); 54 | currentSheet.activate(); 55 | currentSheet.setActiveRange(range); 56 | 57 | //Execute action related to step 58 | if (currentStep.action) { 59 | currentStep.action(); 60 | } 61 | } 62 | 63 | function nextStepTuto() { 64 | const previousStepIndex = getStepIndex(); 65 | const tutorialName: string = getTutorialName(); 66 | 67 | if (previousStepIndex < steps[tutorialName].length - 1) { 68 | doStepTuto(previousStepIndex + 1, tutorialName); 69 | programSheet 70 | .getRange(programSheet.getLastRow(), 2) 71 | .setValue(previousStepIndex + 1); 72 | } else { 73 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 74 | const previousStep = steps[tutorialName][previousStepIndex]; 75 | const previousSheet = spreadsheet.getSheetByName(previousStep.sheetName); 76 | previousSheet.getRange(previousStep.cell).setBackground("white"); 77 | 78 | SpreadsheetApp.getUi().alert( 79 | `You have completed the ${tutorialName} tutorial!`, 80 | ); 81 | } 82 | } 83 | 84 | function clearNotes() { 85 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 86 | const sheets = spreadsheet.getSheets(); 87 | 88 | sheets.forEach((sheet) => { 89 | const range = sheet.getDataRange(); // Get the range of all data in the sheet 90 | range.clearNote(); // Clear all notes in the range 91 | }); 92 | } 93 | 94 | function addNotes(tutorialName: string) { 95 | const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 96 | 97 | steps[tutorialName].forEach((step: Step) => { 98 | const sheet = spreadsheet.getSheetByName(step.sheetName); 99 | const range = sheet.getRange(step.cell); 100 | range.setNote(step.message); 101 | }); 102 | } 103 | 104 | function getTutorialName(): string { 105 | return String(programSheet.getRange(programSheet.getLastRow(), 1).getValue()); 106 | } 107 | 108 | function getStepIndex(): number { 109 | return Number(programSheet.getRange(programSheet.getLastRow(), 2).getValue()); 110 | } 111 | -------------------------------------------------------------------------------- /src/ui/ui.ts: -------------------------------------------------------------------------------- 1 | function onOpen(): void { 2 | var ui: GoogleAppsScript.Base.Ui = SpreadsheetApp.getUi(); 3 | ui.createMenu("Cairo VM") 4 | .addItem("Step", "menuStep") 5 | .addItem("Run", "menuRun") 6 | .addItem("Clear", "clear") 7 | .addItem("Load Program", "showPicker") 8 | .addItem("Relocate", "relocate") 9 | .addToUi(); 10 | 11 | ui.createMenu("Tutorials") 12 | .addItem("Fibonacci - add notes", "addNotesFibonacci") 13 | .addItem("Fibonacci - start tutorial", "startFibonacci") 14 | .addItem("Next", "nextStepTuto") 15 | .addItem("Clear notes", "clearNotes") 16 | .addToUi(); 17 | } 18 | 19 | function menuStep(): void { 20 | step(getLastActiveRowNumber(`${pcColumn}`, runSheet) - 2); 21 | } 22 | 23 | function menuRun(): void { 24 | let lastStepNumber: number = runUntilPc(); 25 | if (isProofMode()) { 26 | step(lastStepNumber - 1); 27 | step(lastStepNumber - 1 + 1); 28 | let lastRegisters = runSheet 29 | .getRange( 30 | `${pcColumn}${lastStepNumber + 1}:${apColumn}${lastStepNumber + 1}`, 31 | ) 32 | .getValues(); 33 | let firstOfLoopRegister = runSheet 34 | .getRange( 35 | `${pcColumn}${lastStepNumber + 2}:${apColumn}${lastStepNumber + 2}`, 36 | ) 37 | .getValues(); 38 | if ( 39 | Number(lastRegisters[0][0]) != Number(firstOfLoopRegister[0][0]) && 40 | Number(lastRegisters[0][1]) != Number(firstOfLoopRegister[0][1]) && 41 | Number(lastRegisters[0][2]) != Number(firstOfLoopRegister[0][2]) 42 | ) { 43 | Logger.log("Make sure the program is compiled and loaded in proof mode."); 44 | throw new InvalidLoopRegisters(); 45 | } 46 | for ( 47 | let stepNum = lastStepNumber + 3; 48 | stepNum <= nextPowerOfTwo(lastStepNumber) + 1; 49 | stepNum++ 50 | ) { 51 | step(stepNum - 2); 52 | } 53 | 54 | //Clear new registers for proper relocation purposes 55 | let registerRowToClear: number = nextPowerOfTwo(lastStepNumber) + 2; 56 | runSheet 57 | .getRange( 58 | `${pcColumn}${registerRowToClear}:${apColumn}${registerRowToClear}`, 59 | ) 60 | .clearContent(); 61 | } 62 | } 63 | 64 | function clear(): void { 65 | const stackLength: number = Number( 66 | runSheet.getRange(`${apColumn}2`).getValue(), 67 | ); 68 | runSheet.getRange(`${pcColumn}3:${apColumn}`).clearContent(); 69 | runSheet.getRange(`${opcodeColumn}2:${runOp1Column}`).clearContent(); 70 | runSheet 71 | .getRange(`${executionColumn}${stackLength + 2}:${executionColumn}`) 72 | .clearContent(); 73 | runSheet 74 | .getRange( 75 | `${firstBuiltinColumn}2:${indexToColumn(columnToIndex(firstBuiltinColumn) + stackLength + 2)}`, 76 | ) 77 | .clearContent(); 78 | proverSheet 79 | .getRange( 80 | `${provSegmentsColumn}3:${indexToColumn(getLastActiveColumnNumber(2, proverSheet) - 1)}`, 81 | ) 82 | .clearContent(); 83 | } 84 | 85 | function showPicker() { 86 | try { 87 | const html = HtmlService.createHtmlOutputFromFile("src/ui/dialog.html") 88 | .setWidth(800) 89 | .setHeight(600) 90 | .setSandboxMode(HtmlService.SandboxMode.IFRAME); 91 | SpreadsheetApp.getUi().showModalDialog(html, "Select a file"); 92 | } catch (e) { 93 | // TODO (Developer) - Handle exception 94 | console.log("Failed with error: %s", e.error); 95 | } 96 | } 97 | 98 | var cache = CacheService.getScriptCache(); 99 | 100 | function updateCacheProgress(current: string) { 101 | cache.put("current", current, 10); // Cache for 10 seconds 102 | } 103 | 104 | function getProgress() { 105 | const current = cache.get("current"); 106 | return { current: current || "" }; 107 | } 108 | 109 | function loadProgram( 110 | programStringified: any, 111 | selectedRunnerMode: string, 112 | selectedLayout: string, 113 | ) { 114 | updateCacheProgress("Parsing program..."); 115 | let program = JSON.parse(programStringified); 116 | let isProofMode: boolean = selectedRunnerMode === "proof"; 117 | let layout: Layout = layouts[selectedLayout]; 118 | 119 | updateCacheProgress("Clearing prover sheet..."); 120 | proverSheet 121 | .getRange( 122 | `${provSegmentsColumn}3:${indexToColumn(getLastActiveColumnNumber(2, proverSheet) - 1)}`, 123 | ) 124 | .clearContent(); 125 | 126 | updateCacheProgress("Clearing program sheet..."); 127 | programSheet 128 | .getRange( 129 | `${progBytecodeColumn}2:${indexToColumn(getLastActiveColumnNumber(1, programSheet) - 1)}`, 130 | ) 131 | .clearContent(); 132 | 133 | updateCacheProgress("Decoding instruction and flags..."); 134 | programSheet 135 | .getRange(`${progDecInstructionColumn}1`) 136 | .setValue("Decimal instruction"); 137 | programSheet 138 | .getRange(`${progDstOffsetColumn}1:${progOp1OffsetColumn}1`) 139 | .setValues([["Dst Offset", "Op0 Offset", "Op1 Offset"]]); 140 | 141 | for (let flagIndex = 0; flagIndex < 16; flagIndex++) { 142 | programSheet 143 | .getRange( 144 | `${indexToColumn(columnToIndex(progOp1OffsetColumn) + flagIndex + 1)}1`, 145 | ) 146 | .setValue(`f_${flagIndex}`); 147 | } 148 | 149 | const bytecode: string[] = program.data; 150 | let isConstant: boolean = false; 151 | for (var i = 0; i < bytecode.length; i++) { 152 | programSheet 153 | .getRange(`${progBytecodeColumn}${i + 2}:${progOpcodeColumn}${i + 2}`) 154 | .setValues([ 155 | [ 156 | bytecode[i], 157 | isConstant 158 | ? `=TO_SIGNED_INTEGER(${progBytecodeColumn}${i + 2})` 159 | : `=DECODE_INSTRUCTION(${progBytecodeColumn}${i + 2})`, 160 | ], 161 | ]); 162 | programSheet 163 | .getRange(`${progDstOffsetColumn}${i + 2}`) 164 | .setFormula(`=GET_FLAGS_AND_OFFSETS(${progBytecodeColumn}${i + 2})`); 165 | programSheet 166 | .getRange(`${progDecInstructionColumn}${i + 2}`) 167 | .setValue(BigInt(bytecode[i]).toString(10)); 168 | if (!isConstant) { 169 | isConstant = size(decodeInstruction(BigInt(bytecode[i]))) == 2; 170 | } else { 171 | isConstant = false; 172 | } 173 | } 174 | 175 | //Store complementary data (builtins, initial and final pc, proofMode, layout) 176 | //to avoid loosing it when reloading the page for instance. 177 | updateCacheProgress("Storing complementary data..."); 178 | let lastActiveRowProgram: number = getLastActiveRowNumber( 179 | progBytecodeColumn, 180 | programSheet, 181 | ); 182 | let rowOffset: number = lastActiveRowProgram + 3; //3 offset is arbitrary 183 | programSheet.getRange(rowOffset, 1).setValue("Complementary information"); 184 | //programSheet.getRange(rowOffset,1,rowOffset,2).mergeAcross(); 185 | rowOffset++; 186 | programSheet.getRange(rowOffset, 1).setValue("proof_mode"); 187 | programSheet.getRange(rowOffset, 2).setValue(isProofMode ? 1 : 0); 188 | rowOffset++; 189 | programSheet.getRange(rowOffset, 1).setValue("used_builtins"); 190 | if (program.builtins.length > 0) { 191 | programSheet 192 | .getRange(rowOffset, 2, program.builtins.length) 193 | .setValues(program.builtins.map((builtin) => [builtin])); 194 | rowOffset += program.builtins.length; 195 | } else { 196 | rowOffset += 1; 197 | } 198 | programSheet.getRange(rowOffset, 1).setValue("initial_pc"); 199 | programSheet 200 | .getRange(rowOffset, 2) 201 | .setValue( 202 | `Program!A${program["identifiers"][`__main__.${isProofMode ? "__start__" : "main"}`]["pc"] + 2}`, 203 | ); 204 | rowOffset++; 205 | programSheet.getRange(rowOffset, 1).setValue(FINAL_PC); 206 | rowOffset++; 207 | programSheet.getRange(rowOffset, 1).setValue("initial_ap"); 208 | rowOffset++; 209 | programSheet.getRange(rowOffset, 1).setValue("layout"); 210 | programSheet.getRange(rowOffset, 2).setValue(selectedLayout); 211 | rowOffset++; 212 | 213 | //Run sheet 214 | updateCacheProgress("Initializing run sheet..."); 215 | runSheet 216 | .getRange( 217 | `${pcColumn}1:${indexToColumn(getLastActiveColumnNumber(1, runSheet) - 1)}`, 218 | ) 219 | .clearContent(); 220 | runSheet 221 | .getRange(`${pcColumn}1:${executionColumn}1`) 222 | .setValues([ 223 | ["PC", "FP", "AP", "Opcode", "Dst", "Res", "Op0", "Op1", "Execution"], 224 | ]); 225 | 226 | initializeProgram(program, isProofMode, layout); 227 | } 228 | 229 | function relocate() { 230 | let lastLastActiveColumnIndex: number = 231 | getLastActiveColumnNumber(2, proverSheet) - 1; 232 | proverSheet 233 | .getRange( 234 | `${provSegmentsColumn}3:${indexToColumn(lastLastActiveColumnIndex == -1 ? 0 : lastLastActiveColumnIndex)}`, 235 | ) 236 | .clearContent(); 237 | 238 | proverSheet.getRange(`${provSegmentsColumn}1`).setValue("Memory"); 239 | proverSheet 240 | .getRange(`${provSegmentsColumn}1:${provMemoryRelocatedColumn}1`) 241 | .mergeAcross(); 242 | 243 | proverSheet.getRange(`${provRelocatedPcColumn}1`).setValue("Relocated Trace"); 244 | proverSheet 245 | .getRange(`${provRelocatedPcColumn}1:${provRelocatedApColumn}1`) 246 | .mergeAcross(); 247 | 248 | proverSheet 249 | .getRange(`${provSegmentsColumn}2:${provRelocatedApColumn}2`) 250 | .setValues([ 251 | ["Segments", "Addresses", "Values", "Relocated", "PC", "FP", "AP"], 252 | ]); 253 | 254 | relocateMemory(); 255 | relocateTrace(); 256 | } 257 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | function objectFromEntries(keys: any, values: any): any { 2 | return keys.reduce( 3 | (prev, key, index) => ({ ...prev, [key]: values[index] }), 4 | {}, 5 | ); 6 | } 7 | 8 | function getLastActiveRowNumber(column: string, sheet): number { 9 | let columnValues: string[] = sheet 10 | .getRange(`${column}1:${column}`) 11 | .getValues() 12 | .map((element) => { 13 | return element && element.length > 0 ? element[0] : ""; 14 | }); 15 | let lastNonEmptyIndex = columnValues 16 | .slice() 17 | .reverse() 18 | .findIndex((value) => value !== ""); 19 | if (lastNonEmptyIndex === -1) { 20 | return 0; 21 | } 22 | 23 | return columnValues.length - lastNonEmptyIndex; 24 | } 25 | 26 | function getLastActiveFormulaRowNumber(column: string, sheet): number { 27 | let columnValues: string[] = sheet 28 | .getRange(`${column}1:${column}`) 29 | .getFormulas() 30 | .map((element) => { 31 | return element && element.length > 0 ? element[0] : ""; 32 | }); 33 | let lastNonEmptyIndex = columnValues 34 | .slice() 35 | .reverse() 36 | .findIndex((value) => value !== ""); 37 | if (lastNonEmptyIndex === -1) { 38 | return 0; 39 | } 40 | 41 | return columnValues.length - lastNonEmptyIndex; 42 | } 43 | 44 | function getLastActiveColumnNumber(row: number, sheet): number { 45 | let rowValues: string[] = sheet.getRange(`${row}1:${row}`).getValues()[0]; 46 | 47 | let lastNonEmptyIndex = rowValues 48 | .slice() 49 | .reverse() 50 | .findIndex((value) => value !== ""); 51 | 52 | if (lastNonEmptyIndex === -1) { 53 | return 0; 54 | } 55 | 56 | return rowValues.length - lastNonEmptyIndex; 57 | } 58 | 59 | function encodeUTF8(str: string): Uint8Array { 60 | const utf8Bytes: number[] = []; 61 | 62 | for (let i = 0; i < str.length; i++) { 63 | let charCode = str.charCodeAt(i); 64 | 65 | if (charCode < 0x80) { 66 | utf8Bytes.push(charCode); 67 | } else if (charCode < 0x800) { 68 | utf8Bytes.push(((charCode >> 6) & 0x1f) | 0xc0); 69 | utf8Bytes.push((charCode & 0x3f) | 0x80); 70 | } else { 71 | utf8Bytes.push(((charCode >> 12) & 0x0f) | 0xe0); 72 | utf8Bytes.push(((charCode >> 6) & 0x3f) | 0x80); 73 | utf8Bytes.push((charCode & 0x3f) | 0x80); 74 | } 75 | } 76 | 77 | return new Uint8Array(utf8Bytes); 78 | } 79 | 80 | function isCell(str: string): boolean { 81 | const regex = /^([A-Za-z]+[A-Za-z]*\d+|[A-Za-z]+)(\![A-Za-z]+\d+)?$/; 82 | return regex.test(str); 83 | } 84 | 85 | function getSheetName(cellReference: string): string { 86 | const parts = cellReference.split("!"); 87 | return parts.length > 1 ? parts[0] : ""; 88 | } 89 | 90 | function getFormulaOfAddition( 91 | value1: string, 92 | value2: string, 93 | address1: string, 94 | address2: string, 95 | ): string { 96 | var formula: string; 97 | if (isCell(value1) && !isCell(value2)) { 98 | let columnValue1: string = indexToColumn( 99 | runSheet.getRange(value1).getColumn() - 1, 100 | ); 101 | let rowValue1: number = runSheet.getRange(value1).getRow(); 102 | let sheetNamePrefix: string = 103 | getSheetName(value1) == "" ? "" : getSheetName(value1) + "!"; 104 | formula = `="${sheetNamePrefix}${columnValue1}" & (${rowValue1} + ${address2})`; 105 | } 106 | if (!isCell(value1) && isCell(value2)) { 107 | let columnValue2: string = indexToColumn( 108 | runSheet.getRange(value2).getColumn() - 1, 109 | ); 110 | let rowValue2: number = runSheet.getRange(value2).getRow(); 111 | let sheetNamePrefix: string = 112 | getSheetName(value2) == "" ? "" : getSheetName(value2) + "!"; 113 | formula = `="${sheetNamePrefix}${columnValue2}" & (${rowValue2} + ${address1})`; 114 | } 115 | if (!isCell(value1) && !isCell(value2)) { 116 | formula = `=${address1} + ${address2}`; 117 | } 118 | if (isCell(value1) && isCell(value2)) { 119 | if (value1[0] !== value2[0]) { 120 | throw new InvalidCellSumError(); 121 | } 122 | let columnValue: string = indexToColumn( 123 | runSheet.getRange(value1).getColumn() - 1, 124 | ); 125 | let sheetNamePrefix: string = 126 | getSheetName(value1) == "" ? "" : getSheetName(value1) + "!"; 127 | let rowValue1: number = runSheet.getRange(value1).getRow(); 128 | let rowValue2: number = runSheet.getRange(value2).getRow(); 129 | formula = `="${sheetNamePrefix}${columnValue}" & (${rowValue1} + ${rowValue2})`; 130 | } 131 | return formula; 132 | } 133 | 134 | function addSegmentValues( 135 | segmentValue1: string, 136 | segmentValue2: string, 137 | ): string { 138 | var sum: string; 139 | if (isCell(segmentValue1) && !isCell(segmentValue2)) { 140 | let columnValue1: string = indexToColumn( 141 | runSheet.getRange(segmentValue1).getColumn() - 1, 142 | ); 143 | let rowValue1: number = runSheet.getRange(segmentValue1).getRow(); 144 | let sheetNamePrefix: string = 145 | getSheetName(segmentValue1) == "" 146 | ? "" 147 | : getSheetName(segmentValue1) + "!"; 148 | sum = `${sheetNamePrefix}${columnValue1}${rowValue1 + Number(segmentValue2)}`; 149 | } 150 | if (!isCell(segmentValue1) && isCell(segmentValue2)) { 151 | let columnValue2: string = indexToColumn( 152 | runSheet.getRange(segmentValue2).getColumn() - 1, 153 | ); 154 | let rowValue2: number = runSheet.getRange(segmentValue2).getRow(); 155 | let sheetNamePrefix: string = 156 | getSheetName(segmentValue2) == "" 157 | ? "" 158 | : getSheetName(segmentValue2) + "!"; 159 | sum = `${sheetNamePrefix}${columnValue2}${rowValue2 + Number(segmentValue1)}`; 160 | } 161 | if (!isCell(segmentValue1) && !isCell(segmentValue2)) { 162 | sum = `${Number(segmentValue1) + Number(segmentValue2)}`; 163 | } 164 | if (isCell(segmentValue1) && isCell(segmentValue2)) { 165 | if (segmentValue1[0] !== segmentValue2[0]) { 166 | throw new InvalidCellSumError(); 167 | } 168 | let columnValue: string = indexToColumn( 169 | runSheet.getRange(segmentValue1).getColumn() - 1, 170 | ); 171 | let sheetNamePrefix: string = 172 | getSheetName(segmentValue1) == "" 173 | ? "" 174 | : getSheetName(segmentValue1) + "!"; 175 | let rowValue1: number = runSheet.getRange(segmentValue1).getRow(); 176 | let rowValue2: number = runSheet.getRange(segmentValue2).getRow(); 177 | sum = `${sheetNamePrefix}${columnValue}${rowValue1 + rowValue2}`; 178 | } 179 | return sum; 180 | } 181 | 182 | function updateBuiltins() { 183 | const startColumn = 8; 184 | const lastColumn = runSheet.getLastColumn(); 185 | const range = runSheet.getRange( 186 | 1, 187 | startColumn, 188 | 1, 189 | lastColumn - startColumn + 1, 190 | ); 191 | const values = range.getValues()[0]; 192 | 193 | for (let i = 0; i < values.length; i++) { 194 | if (builtins[values[i]]) { 195 | builtins[values[i]].column = String.fromCharCode(startColumn + i + 64); 196 | } 197 | } 198 | } 199 | 200 | function isFinalPc(pc: number | string): boolean { 201 | return pc == programSheet.getRange(getFinalPcCell()).getValue(); 202 | } 203 | 204 | function bigintTo15BitString(value: bigint): string { 205 | let binaryStr = value.toString(2); 206 | 207 | if (binaryStr.length > 15) { 208 | binaryStr = binaryStr.slice(-15); 209 | } else { 210 | binaryStr = binaryStr.padStart(15, "0"); 211 | } 212 | 213 | return binaryStr; 214 | } 215 | 216 | function indexToColumn(index: number): string { 217 | let column = ""; 218 | index += 1; 219 | while (index > 0) { 220 | let remainder = (index - 1) % 26; 221 | column = String.fromCharCode(65 + remainder) + column; 222 | index = Math.floor((index - 1) / 26); 223 | } 224 | return column; 225 | } 226 | 227 | function columnToIndex(column: string): number { 228 | let rowNum = 0; 229 | for (let i = 0; i < column.length; i++) { 230 | rowNum *= 26; 231 | rowNum += column.charCodeAt(i) - 64; 232 | } 233 | return rowNum - 1; 234 | } 235 | 236 | function getFinalPcCell(): string { 237 | return `B${ 238 | programSheet 239 | .getRange("A1:A") 240 | .getValues() 241 | .map((value) => { 242 | if (value) { 243 | return value[0]; 244 | } 245 | }) 246 | .indexOf(FINAL_PC) + 1 247 | }`; 248 | } 249 | 250 | function getProofModeCell(): string { 251 | return `B${ 252 | programSheet 253 | .getRange("A1:A") 254 | .getValues() 255 | .map((value) => { 256 | if (value) { 257 | return value[0]; 258 | } 259 | }) 260 | .indexOf("proof_mode") + 1 261 | }`; 262 | } 263 | 264 | function isProofMode(): boolean { 265 | return Number(programSheet.getRange(getProofModeCell()).getValue()) == 1; 266 | } 267 | 268 | function nextPowerOfTwo(n: number): number { 269 | return 1 << Math.ceil(Math.log2(n)); 270 | } 271 | 272 | function hasEmptyCell(array): boolean { 273 | return array.flat().includes(""); 274 | } 275 | -------------------------------------------------------------------------------- /src/vm/constants.ts: -------------------------------------------------------------------------------- 1 | interface RegistersType { 2 | AP: string; 3 | FP: string; 4 | PC: string; 5 | } 6 | 7 | interface Op1SrcType { 8 | Op0: string; 9 | AP: string; 10 | FP: string; 11 | PC: string; 12 | } 13 | 14 | interface PCUpdatesType { 15 | Regular: string; 16 | Jump: string; 17 | JumpRel: string; 18 | Jnz: string; 19 | } 20 | 21 | interface ResLogicsType { 22 | Op1: string; 23 | Add: string; 24 | Mul: string; 25 | Unused: string; 26 | } 27 | 28 | interface OpcodesType { 29 | NOp: string; 30 | Call: string; 31 | Ret: string; 32 | AssertEq: string; 33 | } 34 | 35 | interface ApUpdatesType { 36 | Constant: string; 37 | AddRes: string; 38 | Add1: string; 39 | Add2: string; 40 | } 41 | 42 | interface FpUpdatesType { 43 | Constant: string; 44 | Dst: string; 45 | ApPlus2: string; 46 | } 47 | 48 | interface SignatureType { 49 | r: bigint; 50 | s: bigint; 51 | } 52 | 53 | interface HadesParamsType { 54 | r: number; 55 | c: number; 56 | m: number; 57 | Rf: number; 58 | Rp: number; 59 | nRounds: number; 60 | outputSize: number; 61 | ark: bigint[][]; 62 | mds: bigint[][]; 63 | } 64 | 65 | interface BuitlinType { 66 | freeCellsPerBuiltin: number; 67 | numOutputCells: number; 68 | column: string; 69 | functionName: string; 70 | } 71 | 72 | type Layout = { 73 | builtins: string[]; 74 | }; 75 | 76 | const PRIME: bigint = BigInt( 77 | "0x800000000000011000000000000000000000000000000000000000000000001", 78 | ); 79 | 80 | const GENERATOR = new AffinePoint( 81 | "874739451078007766457464989774322083649278607533249481151382481072868806602", 82 | "152666792071518830868575557812948353041420400780739481342941381225525861407", 83 | ); 84 | const ORDER = BigInt( 85 | "3618502788666131213697322783095070105526743751716087489154079457884512865583", 86 | ); 87 | 88 | const ALPHA: bigint = BigInt(1); 89 | 90 | const Registers: RegistersType = { 91 | AP: "AP", 92 | FP: "FP", 93 | PC: "PC", 94 | }; 95 | 96 | const Op1Src: Op1SrcType = { 97 | Op0: "Op0", 98 | AP: "AP", 99 | FP: "FP", 100 | PC: "PC", 101 | }; 102 | 103 | const PcUpdates: PCUpdatesType = { 104 | Regular: "PC + instruction size", 105 | Jump: "jmp abs", 106 | JumpRel: "jmp rel", 107 | Jnz: "jmp if != 0", 108 | }; 109 | 110 | const ResLogics: ResLogicsType = { 111 | Op1: "Op1", 112 | Add: "Add", 113 | Mul: "Mul", 114 | Unused: "Unused", 115 | }; 116 | 117 | const Opcodes: OpcodesType = { 118 | NOp: "", 119 | Call: "Call", 120 | Ret: "Ret", 121 | AssertEq: "AssertEq", 122 | }; 123 | 124 | const ApUpdates: ApUpdatesType = { 125 | Constant: "AP", 126 | AddRes: "AP + res", 127 | Add1: "AP + 1", 128 | Add2: "AP + 2", 129 | }; 130 | 131 | const FpUpdates: FpUpdatesType = { 132 | Constant: "FP", 133 | Dst: "Dst", 134 | ApPlus2: "AP + 2", 135 | }; 136 | 137 | const FINAL_FP: string = "final_fp"; 138 | const FINAL_PC: string = "final_pc"; 139 | 140 | const layouts: { [key: string]: Layout } = { 141 | plain: { 142 | builtins: [], 143 | }, 144 | small: { 145 | builtins: ["output", "pedersen", "range_check", "ecdsa"], 146 | }, 147 | dex: { 148 | builtins: ["output", "pedersen", "range_check", "ecdsa"], 149 | }, 150 | recursive: { 151 | builtins: ["output", "pedersen", "range_check", "bitwise"], 152 | }, 153 | starknet: { 154 | builtins: [ 155 | "output", 156 | "pedersen", 157 | "range_check", 158 | "ecdsa", 159 | "bitwise", 160 | "ec_op", 161 | "poseidon", 162 | ], 163 | }, 164 | starknet_with_keccak: { 165 | builtins: [ 166 | "output", 167 | "pedersen", 168 | "range_check", 169 | "ecdsa", 170 | "bitwise", 171 | "ec_op", 172 | "keccak", 173 | "poseidon", 174 | ], 175 | }, 176 | recursive_large_output: { 177 | builtins: ["output", "pedersen", "range_check", "bitwise", "poseidon"], 178 | }, 179 | recursive_with_poseidon: { 180 | builtins: ["output", "pedersen", "range_check", "bitwise", "poseidon"], 181 | }, 182 | all_cairo: { 183 | builtins: [ 184 | "output", 185 | "pedersen", 186 | "range_check", 187 | "ecdsa", 188 | "bitwise", 189 | "ec_op", 190 | "keccak", 191 | "poseidon", 192 | "range_check96", 193 | ], 194 | }, 195 | all_solidity: { 196 | builtins: [ 197 | "output", 198 | "pedersen", 199 | "range_check", 200 | "ecdsa", 201 | "bitwise", 202 | "ec_op", 203 | ], 204 | }, 205 | dynamic: { 206 | builtins: [ 207 | "output", 208 | "pedersen", 209 | "range_check", 210 | "ecdsa", 211 | "bitwise", 212 | "ec_op", 213 | ], 214 | }, 215 | }; 216 | -------------------------------------------------------------------------------- /src/vm/errors.ts: -------------------------------------------------------------------------------- 1 | class NonZeroHighBitError extends Error {} 2 | class InvalidPcUpdateError extends Error {} 3 | class InvalidResError extends Error {} 4 | class InvalidOpcodeError extends Error {} 5 | class InvalidApUpdateError extends Error {} 6 | class InvalidOp0RegisterError extends Error {} 7 | class InvalidOp1RegisterError extends Error {} 8 | class InvalidDstRegisterError extends Error {} 9 | class InvalidRangeError extends Error {} 10 | class AssertEqError extends Error {} 11 | class InvalidCellSumError extends Error {} 12 | class InvalidLoopRegisters extends Error {} 13 | -------------------------------------------------------------------------------- /src/vm/instructions.ts: -------------------------------------------------------------------------------- 1 | interface decodedInstruction { 2 | Opcode: string; 3 | Op0Register: string; 4 | Op1Register: string; 5 | DstRegister: string; 6 | ResLogic: string; 7 | Op0Offset: number; 8 | Op1Offset: number; 9 | DstOffset: number; 10 | PcUpdate: string; 11 | ApUpdate: string; 12 | FpUpdate: string; 13 | } 14 | 15 | interface traceInfo { 16 | dstOffset: number; 17 | op0Offset: number; 18 | op1Offset: number; 19 | flags: number[]; 20 | } 21 | 22 | function fromBiasedRepresentation(offset: bigint): number { 23 | const bias: bigint = BigInt(1) << BigInt(15); 24 | return Number(BigInt(Number(offset)) - bias); 25 | } 26 | 27 | function toSignedInteger(encodedInstruction): bigint { 28 | const number: bigint = BigInt(encodedInstruction); 29 | return number > BigInt(2 ** 128) ? number - PRIME : number; 30 | } 31 | 32 | function toUnsignedInteger(encodedInstruction): bigint { 33 | const number: bigint = BigInt(encodedInstruction); 34 | return number < 0 ? number + PRIME : number; 35 | } 36 | 37 | function size(instruction): number { 38 | return instruction.Op1Register === Registers.PC ? 2 : 1; 39 | } 40 | 41 | function decodeInstruction(encodedInstruction: bigint): decodedInstruction { 42 | const HighBit: bigint = BigInt(1) << BigInt(63); 43 | const DstRegMask: bigint = BigInt(0x0001); 44 | const DstRegOff: bigint = BigInt(0); 45 | const Op0RegMask: bigint = BigInt(0x0002); 46 | const Op0RegOff: bigint = BigInt(1); 47 | const Op1SrcMask: bigint = BigInt(0x001c); 48 | const Op1SrcOff: bigint = BigInt(2); 49 | const ResLogicMask: bigint = BigInt(0x0060); 50 | const ResLogicOff: bigint = BigInt(5); 51 | const PcUpdateMask: bigint = BigInt(0x0380); 52 | const PcUpdateOff: bigint = BigInt(7); 53 | const ApUpdateMask: bigint = BigInt(0x0c00); 54 | const ApUpdateOff: bigint = BigInt(10); 55 | const OpcodeMask: bigint = BigInt(0x7000); 56 | const OpcodeOff: bigint = BigInt(12); 57 | 58 | if ((encodedInstruction & HighBit) !== BigInt(0)) { 59 | throw new NonZeroHighBitError(); 60 | } 61 | 62 | const dstOffset: number = this.fromBiasedRepresentation( 63 | encodedInstruction & BigInt(0xffff), 64 | ); 65 | const op0Offset: number = this.fromBiasedRepresentation( 66 | (encodedInstruction >> BigInt(16)) & BigInt(0xffff), 67 | ); 68 | const op1Offset: number = this.fromBiasedRepresentation( 69 | (encodedInstruction >> BigInt(32)) & BigInt(0xffff), 70 | ); 71 | 72 | const flags: bigint = encodedInstruction >> BigInt(48); 73 | 74 | const dstRegNum: bigint = (flags & DstRegMask) >> DstRegOff; 75 | const op0RegNum: bigint = (flags & Op0RegMask) >> Op0RegOff; 76 | const op1SrcNum: bigint = (flags & Op1SrcMask) >> Op1SrcOff; 77 | const resLogicNum: bigint = (flags & ResLogicMask) >> ResLogicOff; 78 | const pcUpdateNum: bigint = (flags & PcUpdateMask) >> PcUpdateOff; 79 | const apUpdateNum: bigint = (flags & ApUpdateMask) >> ApUpdateOff; 80 | const opCodeNum: bigint = (flags & OpcodeMask) >> OpcodeOff; 81 | 82 | let dstRegister: string; 83 | let op0Register: string; 84 | let op1Src: string; 85 | let pcUpdate: string; 86 | let res: string; 87 | let opcode: string; 88 | let apUpdate: string; 89 | let fpUpdate: string; 90 | 91 | switch (dstRegNum) { 92 | case BigInt(0): 93 | dstRegister = Registers.AP; 94 | break; 95 | case BigInt(1): 96 | dstRegister = Registers.FP; 97 | break; 98 | default: 99 | throw new InvalidDstRegisterError(); 100 | } 101 | 102 | switch (op0RegNum) { 103 | case BigInt(0): 104 | op0Register = Registers.AP; 105 | break; 106 | case BigInt(1): 107 | op0Register = Registers.FP; 108 | break; 109 | default: 110 | throw new InvalidOp0RegisterError(); 111 | } 112 | 113 | switch (op1SrcNum) { 114 | case BigInt(0): 115 | op1Src = Op1Src.Op0; 116 | break; 117 | case BigInt(1): 118 | op1Src = Op1Src.PC; 119 | break; 120 | case BigInt(2): 121 | op1Src = Op1Src.FP; 122 | break; 123 | case BigInt(4): 124 | op1Src = Op1Src.AP; 125 | break; 126 | default: 127 | throw new InvalidOp1RegisterError(); 128 | } 129 | 130 | switch (opCodeNum) { 131 | case BigInt(0): 132 | opcode = Opcodes.NOp; 133 | break; 134 | case BigInt(1): 135 | opcode = Opcodes.Call; 136 | break; 137 | case BigInt(2): 138 | opcode = Opcodes.Ret; 139 | break; 140 | case BigInt(4): 141 | opcode = Opcodes.AssertEq; 142 | break; 143 | default: 144 | throw new InvalidOpcodeError(); 145 | } 146 | 147 | switch (apUpdateNum) { 148 | case BigInt(0): 149 | if (opcode === Opcodes.Call) { 150 | apUpdate = ApUpdates.Add2; 151 | } else { 152 | apUpdate = ApUpdates.Constant; 153 | } 154 | break; 155 | case BigInt(1): 156 | apUpdate = ApUpdates.AddRes; 157 | break; 158 | case BigInt(2): 159 | apUpdate = ApUpdates.Add1; 160 | break; 161 | default: 162 | throw new InvalidApUpdateError(); 163 | } 164 | 165 | switch (opcode) { 166 | case Opcodes.NOp: 167 | fpUpdate = FpUpdates.Constant; 168 | break; 169 | case Opcodes.Call: 170 | fpUpdate = FpUpdates.ApPlus2; 171 | break; 172 | case Opcodes.Ret: 173 | fpUpdate = FpUpdates.Dst; 174 | break; 175 | case Opcodes.AssertEq: 176 | fpUpdate = FpUpdates.Constant; 177 | break; 178 | default: 179 | throw new InvalidOpcodeError(); 180 | } 181 | 182 | switch (pcUpdateNum) { 183 | case BigInt(0): 184 | pcUpdate = PcUpdates.Regular; 185 | break; 186 | case BigInt(1): 187 | pcUpdate = PcUpdates.Jump; 188 | break; 189 | case BigInt(2): 190 | pcUpdate = PcUpdates.JumpRel; 191 | break; 192 | case BigInt(4): 193 | pcUpdate = PcUpdates.Jnz; 194 | break; 195 | default: 196 | throw new InvalidPcUpdateError(); 197 | } 198 | 199 | switch (resLogicNum) { 200 | case BigInt(0): 201 | res = ResLogics.Op1; 202 | break; 203 | case BigInt(1): 204 | res = ResLogics.Add; 205 | break; 206 | case BigInt(2): 207 | res = ResLogics.Mul; 208 | break; 209 | default: 210 | throw new InvalidResError(); 211 | } 212 | 213 | return { 214 | Opcode: opcode, 215 | Op0Register: op0Register, 216 | Op1Register: op1Src, 217 | DstRegister: dstRegister, 218 | ResLogic: res, 219 | Op0Offset: op0Offset, 220 | Op1Offset: op1Offset, 221 | DstOffset: dstOffset, 222 | PcUpdate: pcUpdate, 223 | ApUpdate: apUpdate, 224 | FpUpdate: fpUpdate, 225 | }; 226 | } 227 | 228 | function getTraceInfo(encodedInstruction: bigint): traceInfo { 229 | const dstOffset: number = Number(encodedInstruction & BigInt(0xffff)); 230 | const op0Offset: number = Number( 231 | (encodedInstruction >> BigInt(16)) & BigInt(0xffff), 232 | ); 233 | const op1Offset: number = Number( 234 | (encodedInstruction >> BigInt(32)) & BigInt(0xffff), 235 | ); 236 | 237 | let flagsBigint: bigint = encodedInstruction >> BigInt(48); 238 | let flags: number[] = bigintTo15BitString(flagsBigint) 239 | .split("") 240 | .map((flag) => Number(flag)) 241 | .reverse(); 242 | 243 | return { dstOffset, op0Offset, op1Offset, flags }; 244 | } 245 | -------------------------------------------------------------------------------- /src/vm/vm.ts: -------------------------------------------------------------------------------- 1 | const runSheet: GoogleAppsScript.Spreadsheet.Sheet = 2 | SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Run"); 3 | const proverSheet: GoogleAppsScript.Spreadsheet.Sheet = 4 | SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Prover"); 5 | const programSheet: GoogleAppsScript.Spreadsheet.Sheet = 6 | SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Program"); 7 | 8 | const columns: String[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); 9 | 10 | let i = 0; 11 | const pcColumn: string = indexToColumn(i); 12 | i++; 13 | const fpColumn: string = indexToColumn(i); 14 | i++; 15 | const apColumn: string = indexToColumn(i); 16 | i++; 17 | const opcodeColumn: string = indexToColumn(i); 18 | i++; 19 | const dstColumn: string = indexToColumn(i); 20 | i++; 21 | const resColumn: string = indexToColumn(i); 22 | i++; 23 | const op0Column: string = indexToColumn(i); 24 | i++; 25 | const runOp1Column: string = indexToColumn(i); 26 | i++; 27 | const executionColumn: string = indexToColumn(i); 28 | i++; 29 | const firstBuiltinColumn: string = indexToColumn(i); 30 | i++; 31 | 32 | let j = 0; 33 | const progBytecodeColumn: string = indexToColumn(j); 34 | j++; 35 | const progOpcodeColumn: string = indexToColumn(j); 36 | j++; 37 | const progDstColumn: string = indexToColumn(j); 38 | j++; 39 | const progOpColumn: string = indexToColumn(j); 40 | j++; 41 | const progPcupdateColumn: string = indexToColumn(j); 42 | j++; 43 | const progApupdateColumn: string = indexToColumn(j); 44 | j++; 45 | const progFpupdateColumn: string = indexToColumn(j); 46 | j++; 47 | const progDecInstructionColumn: string = indexToColumn(j); 48 | j++; 49 | const progDstOffsetColumn: string = indexToColumn(j); 50 | j++; 51 | const progOp0OffsetColumn: string = indexToColumn(j); 52 | j++; 53 | const progOp1OffsetColumn: string = indexToColumn(j); 54 | j++; 55 | const progFlag0Column: string = indexToColumn(j); 56 | j += 15; 57 | const progFlag15Column: string = indexToColumn(j); 58 | j++; 59 | 60 | let k = 0; 61 | const provSegmentsColumn: string = indexToColumn(k); 62 | k++; 63 | const provAddressColumn: string = indexToColumn(k); 64 | k++; 65 | const provValuesColumn: string = indexToColumn(k); 66 | k++; 67 | const provMemoryRelocatedColumn: string = indexToColumn(k); 68 | k++; 69 | const provRelocatedPcColumn: string = indexToColumn(k); 70 | k++; 71 | const provRelocatedFpColumn: string = indexToColumn(k); 72 | k++; 73 | const provRelocatedApColumn: string = indexToColumn(k); 74 | k++; 75 | 76 | type Builtins = { 77 | output: BuitlinType; 78 | pedersen: BuitlinType; 79 | range_check: BuitlinType; 80 | range_check96: BuitlinType; 81 | ecdsa: BuitlinType; 82 | bitwise: BuitlinType; 83 | ec_op: BuitlinType; 84 | keccak: BuitlinType; 85 | poseidon: BuitlinType; 86 | }; 87 | 88 | let builtins: Builtins = { 89 | output: { 90 | freeCellsPerBuiltin: 0, 91 | numOutputCells: 0, 92 | column: "", 93 | functionName: "OUTPUT", 94 | }, 95 | pedersen: { 96 | freeCellsPerBuiltin: 2, 97 | numOutputCells: 1, 98 | column: "", 99 | functionName: "PEDERSEN", 100 | }, 101 | range_check: { 102 | freeCellsPerBuiltin: 0, 103 | numOutputCells: 1, 104 | column: "", 105 | functionName: "RANGE_CHECK", 106 | }, 107 | range_check96: { 108 | freeCellsPerBuiltin: 0, 109 | numOutputCells: 1, 110 | column: "", 111 | functionName: "RANGE_CHECK96", 112 | }, 113 | ecdsa: { 114 | freeCellsPerBuiltin: 3, 115 | numOutputCells: 1, 116 | column: "", 117 | functionName: "CHECK_ECDSA_SIGNATURE", 118 | }, 119 | bitwise: { 120 | freeCellsPerBuiltin: 2, 121 | numOutputCells: 3, 122 | column: "", 123 | functionName: "BITWISE", 124 | }, 125 | ec_op: { 126 | freeCellsPerBuiltin: 5, 127 | numOutputCells: 2, 128 | column: "", 129 | functionName: "EC_OP", 130 | }, 131 | keccak: { 132 | freeCellsPerBuiltin: 8, 133 | numOutputCells: 8, 134 | column: "", 135 | functionName: "KECCAK", 136 | }, 137 | poseidon: { 138 | freeCellsPerBuiltin: 3, 139 | numOutputCells: 3, 140 | column: "", 141 | functionName: "POSEIDON", 142 | }, 143 | }; 144 | 145 | function reorderBuiltins(builtinsList: string[], layout: Layout): string[] { 146 | const layoutBuiltins = layout.builtins; 147 | 148 | const orderedBuiltins = builtinsList.filter((builtin) => 149 | layoutBuiltins.includes(builtin), 150 | ); 151 | const unorderedBuiltins = builtinsList.filter( 152 | (builtin) => !layoutBuiltins.includes(builtin), 153 | ); 154 | 155 | const result = layoutBuiltins.filter((builtin) => 156 | orderedBuiltins.includes(builtin), 157 | ); 158 | 159 | return [...result, ...unorderedBuiltins]; 160 | } 161 | 162 | let builtinsColumns: {}; 163 | function initializeProgram(program: any, isProofMode: boolean, layout: Layout) { 164 | const usedBuiltins: string[] = program.builtins; 165 | let builtinsList: string[] = isProofMode 166 | ? [...new Set(usedBuiltins.concat(layout.builtins))] 167 | : usedBuiltins; 168 | 169 | let counter: number = 0; 170 | const executionColumnOffset: number = columnToIndex(executionColumn) + 1; 171 | 172 | let builtinsListOrdered: string[] = reorderBuiltins(builtinsList, layout); 173 | 174 | for (var key of builtinsListOrdered) { 175 | builtins[key].column = indexToColumn(counter + executionColumnOffset); 176 | counter++; 177 | } 178 | 179 | const builtinStack = builtinsListOrdered 180 | .map((builtin) => { 181 | let base: string = `${builtins[builtin].column}2`; 182 | if (isProofMode) { 183 | return usedBuiltins.includes(builtin) ? base : 0; 184 | } 185 | return base; 186 | }) 187 | .filter((value) => !(value === 0)); 188 | 189 | let stack: string[] = []; 190 | if (!isProofMode) { 191 | let fpColumn: string = 192 | program.builtins.length != 0 193 | ? indexToColumn( 194 | columnToIndex( 195 | builtins[builtinsListOrdered[builtinsListOrdered.length - 1]] 196 | .column, 197 | ) + 1, 198 | ) 199 | : indexToColumn(columnToIndex(executionColumn) + 1); 200 | let pcColumn: string = 201 | program.builtins.length != 0 202 | ? indexToColumn( 203 | columnToIndex( 204 | builtins[builtinsListOrdered[builtinsListOrdered.length - 1]] 205 | .column, 206 | ) + 2, 207 | ) 208 | : indexToColumn(columnToIndex(executionColumn) + 2); 209 | stack = [...builtinStack, `${fpColumn}2`, `${pcColumn}2`]; 210 | runSheet 211 | .getRange( 212 | `${indexToColumn(columnToIndex(executionColumn) + 1)}1:${pcColumn}1`, 213 | ) 214 | .setValues([[...builtinsListOrdered, FINAL_FP, FINAL_PC]]); 215 | programSheet 216 | .getRange(`B${Number(getFinalPcCell().substring(1)) + 1}`) 217 | .setValue(`${executionColumn}${stack.length + 2}`); 218 | programSheet.getRange(getFinalPcCell()).setValue(`${pcColumn}2`); 219 | } else { 220 | stack = [`${executionColumn}2`, "0", ...builtinStack]; 221 | programSheet 222 | .getRange(`B${Number(getFinalPcCell().substring(1)) + 1}`) 223 | .setValue(`${executionColumn}${2}`); 224 | programSheet 225 | .getRange(getFinalPcCell()) 226 | .setValue(program["identifiers"]["__main__.__end__"]["pc"]); 227 | 228 | if (builtinsListOrdered.length > 0) { 229 | runSheet 230 | .getRange( 231 | `${builtins[builtinsListOrdered[0]].column}1:${builtins[builtinsListOrdered[builtinsListOrdered.length - 1]].column}1`, 232 | ) 233 | .setValues([builtinsListOrdered]); 234 | } 235 | } 236 | runSheet 237 | .getRange(`${executionColumn}2:${executionColumn}${stack.length + 1}`) 238 | .setValues(stack.map((address) => [address])); 239 | 240 | runSheet 241 | .getRange(`${pcColumn}2`) 242 | .setFormula(`=Program!B${Number(getFinalPcCell().substring(1)) - 1}`); 243 | runSheet 244 | .getRange(`${apColumn}2`) 245 | .setFormula(`=Program!B${Number(getFinalPcCell().substring(1)) + 1}`); 246 | runSheet.getRange(`${fpColumn}2`).setFormula(`=${apColumn}2`); 247 | } 248 | 249 | function step(n: number = 0): void { 250 | const program: any[][] = programSheet.getRange("A2:A").getValues(); 251 | updateBuiltins(); 252 | 253 | const registersAddress: RegistersType = { 254 | PC: `${pcColumn}${n + 2}`, 255 | FP: `${fpColumn}${n + 2}`, 256 | AP: `${apColumn}${n + 2}`, 257 | }; 258 | const pc: string = runSheet.getRange(registersAddress["PC"]).getValue(); 259 | const fp: string = runSheet.getRange(registersAddress["FP"]).getValue(); 260 | const ap: string = runSheet.getRange(registersAddress["AP"]).getValue(); 261 | const registers: RegistersType = { 262 | PC: pc, 263 | FP: fp, 264 | AP: ap, 265 | }; 266 | 267 | const encodedInstruction: any = 268 | program[runSheet.getRange(pc).getRow() - 2][0]; 269 | const instruction: decodedInstruction = decodeInstruction( 270 | BigInt(encodedInstruction), 271 | ); 272 | runSheet.getRange(`${opcodeColumn}${n + 2}`).setValue(instruction.Opcode); 273 | 274 | const op0Index: number = 275 | runSheet.getRange(registers[instruction.Op0Register]).getRow() - 276 | 2 + 277 | instruction.Op0Offset; 278 | const dstIndex: number = 279 | runSheet.getRange(registers[instruction.DstRegister]).getRow() - 280 | 2 + 281 | instruction.DstOffset; 282 | let op1Index: number; 283 | 284 | // Addresses are sheet address (e.g. H4) or constants 285 | // Constants come from the Program, ie when register is PC 286 | // Indexes are +2 since the CairoVM is 0 based, while the Sheet is 1 base 287 | // and the first row is a header 288 | let op0Addr: string = 289 | instruction.Op0Register === Registers.PC 290 | ? `Program!${progOpColumn}${op0Index + 2}` 291 | : `${executionColumn}${op0Index + 2}`; 292 | let op1Addr: string; 293 | let dstAddr: string = 294 | instruction.DstRegister === Registers.PC 295 | ? `Program!${progOpColumn}${dstIndex + 2}` 296 | : `${executionColumn}${dstIndex + 2}`; 297 | let op0Value: string = runSheet.getRange(op0Addr).getValue(); 298 | let dstValue: string = runSheet.getRange(dstAddr).getValue(); 299 | 300 | switch (instruction.Op1Register) { 301 | case Op1Src.Op0: 302 | //instruction.Op0Register can't be Registers.PC because we expect op0Value to be an pointer to a segment emplacement and not a felt 303 | //So there is no need to deal with this case. 304 | let op0ValueColumn: string = indexToColumn( 305 | runSheet.getRange(op0Value).getColumn() - 1, 306 | ); 307 | let op0ValueRow: number = runSheet.getRange(op0Value).getRow(); 308 | op1Addr = `${op0ValueColumn}${op0ValueRow + instruction.Op1Offset}`; 309 | break; 310 | case Op1Src.PC: 311 | op1Index = 312 | runSheet.getRange(registers[instruction.Op1Register]).getRow() - 313 | 2 + 314 | instruction.Op1Offset; 315 | op1Addr = `Program!${progOpColumn}${op1Index + 2}`; 316 | break; 317 | default: 318 | op1Index = 319 | runSheet.getRange(registers[instruction.Op1Register]).getRow() - 320 | 2 + 321 | instruction.Op1Offset; 322 | op1Addr = `${executionColumn}${op1Index + 2}`; 323 | break; 324 | } 325 | 326 | builtinsColumns = Object.fromEntries( 327 | Object.keys(builtins) 328 | .filter((builtin) => !!builtins[builtin] && builtins[builtin].column) 329 | .map((builtin) => [builtins[builtin].column, builtins[builtin]]), 330 | ); 331 | let addr = op1Addr.split("!").pop(); 332 | let op1Column = addr[0]; 333 | let op1Offset = Number(addr.slice(1)) - 2; 334 | if (Object.keys(builtinsColumns).includes(op1Column)) { 335 | const builtin = builtinsColumns[op1Column]; 336 | if (builtin.freeCellsPerBuiltin > 0) { 337 | const builtinSize = builtin.freeCellsPerBuiltin + builtin.numOutputCells; 338 | 339 | const requiredInstances = 340 | op1Offset > 0 ? Math.ceil(op1Offset / builtinSize) : 1; 341 | const lastInstance = (requiredInstances - 1) * builtinSize; 342 | const lastBuiltinCell = requiredInstances * builtinSize; 343 | if ( 344 | runSheet.getRange(`${op1Column}${lastBuiltinCell + 1}`).getFormula() == 345 | "" 346 | ) { 347 | var inputCells: string[] = []; 348 | for (let j = 1; j <= builtin.freeCellsPerBuiltin; j++) { 349 | inputCells.push(`${op1Column}${lastInstance + j + 1}`); 350 | } 351 | runSheet 352 | .getRange( 353 | `${builtin.column}${lastInstance + 1 + builtin.freeCellsPerBuiltin + 1}`, 354 | ) 355 | .setFormula( 356 | `=${builtin.functionName}(${inputCells[0]}:${inputCells[inputCells.length - 1]})`, 357 | ); 358 | } 359 | } 360 | } 361 | 362 | if (builtins["range_check"].column != "") { 363 | for ( 364 | let j = 2; 365 | j <= 366 | getLastActiveFormulaRowNumber(builtins["range_check"].column, runSheet); 367 | j++ 368 | ) { 369 | let currentCell: string = `${builtins["range_check"].column}${j}`; 370 | let currentCellFormula: string = runSheet 371 | .getRange(currentCell) 372 | .getFormula(); 373 | if (currentCellFormula[1] != "R") { 374 | runSheet 375 | .getRange(currentCell) 376 | .setFormula(`=RANGE_CHECK(${currentCellFormula.substring(1)})`); 377 | } 378 | } 379 | } 380 | 381 | if (builtins["range_check96"].column != "") { 382 | for ( 383 | let j = 2; 384 | j <= 385 | getLastActiveFormulaRowNumber(builtins["range_check96"].column, runSheet); 386 | j++ 387 | ) { 388 | let currentCell: string = `${builtins["range_check96"].column}${j}`; 389 | let currentCellFormula: string = runSheet 390 | .getRange(currentCell) 391 | .getFormula(); 392 | if (currentCellFormula[1] != "R") { 393 | runSheet 394 | .getRange(currentCell) 395 | .setFormula(`=RANGE_CHECK96(${currentCellFormula.substring(1)})`); 396 | } 397 | } 398 | } 399 | 400 | let op1Value: string = runSheet.getRange(op1Addr).getValue(); 401 | 402 | // Set formula for current opcode: dst and res 403 | runSheet.getRange(`${dstColumn}${n + 2}`).setFormula(`=${dstAddr}`); 404 | switch (instruction.ResLogic) { 405 | case ResLogics.Add: 406 | var resFormula: string = getFormulaOfAddition( 407 | op0Value, 408 | op1Value, 409 | op0Addr, 410 | op1Addr, 411 | ); 412 | runSheet.getRange(`${resColumn}${n + 2}`).setFormula(resFormula); 413 | break; 414 | case ResLogics.Mul: 415 | runSheet 416 | .getRange(`${resColumn}${n + 2}`) 417 | .setFormula(`=${op0Addr} * ${op1Addr}`); 418 | break; 419 | case ResLogics.Op1: 420 | runSheet.getRange(`${resColumn}${n + 2}`).setFormula(`=${op1Addr}`); 421 | break; 422 | } 423 | // Cairo instructions are like 424 | // res = res_logic(op0, op1) 425 | // opcode(dst, res) 426 | switch (instruction.Opcode) { 427 | case Opcodes.Call: 428 | let validCallOp0Value: string = addSegmentValues( 429 | registers[Registers.PC], 430 | size(instruction).toString(10), 431 | ); 432 | let validCallDstValue: string = registers[Registers.FP]; 433 | if (op0Value == "") { 434 | runSheet.getRange(op0Addr).setFormula(`="${validCallOp0Value}"`); 435 | op0Value = runSheet.getRange(op0Addr).getValue(); 436 | } 437 | if (dstValue == "") { 438 | runSheet.getRange(dstAddr).setFormula(`="${validCallDstValue}"`); 439 | dstValue = runSheet.getRange(dstAddr).getValue(); 440 | } 441 | 442 | if (dstValue != validCallDstValue || op0Value != validCallOp0Value) { 443 | throw new AssertEqError(); 444 | } 445 | break; 446 | case Opcodes.AssertEq: 447 | let validAssertEqDstValue: string; 448 | switch (instruction.ResLogic) { 449 | case ResLogics.Add: 450 | if (op0Value === "") { 451 | runSheet 452 | .getRange(op0Addr) 453 | .setFormula(`="${addSegmentValues(dstValue, `-${op1Value}"`)}`); 454 | op0Value = runSheet.getRange(op0Addr).getValue(); 455 | } 456 | if (op1Value === "") { 457 | runSheet 458 | .getRange(op1Addr) 459 | .setFormula(`="${addSegmentValues(dstValue, `-${op0Value}"`)}`); 460 | op1Value = runSheet.getRange(op1Addr).getValue(); 461 | } 462 | if (dstValue === "") { 463 | runSheet 464 | .getRange(dstAddr) 465 | .setFormula( 466 | `="${addSegmentValues(`${op0Value}`, `${op1Value}`)}"`, 467 | ); 468 | } 469 | validAssertEqDstValue = addSegmentValues(op0Value, op1Value); 470 | break; 471 | case ResLogics.Mul: 472 | if (op0Value === "") { 473 | runSheet 474 | .getRange(op0Addr) 475 | .setFormula(`="${BigInt(dstValue) / BigInt(op1Value)}"`); 476 | op0Value = runSheet.getRange(op0Addr).getValue(); 477 | } 478 | if (op1Value === "") { 479 | runSheet 480 | .getRange(op1Addr) 481 | .setFormula(`="${BigInt(dstValue) / BigInt(op0Value)}"`); 482 | op1Value = runSheet.getRange(op1Addr).getValue(); 483 | } 484 | if (dstValue === "") { 485 | runSheet 486 | .getRange(dstAddr) 487 | .setFormula(`="${BigInt(op0Value) * BigInt(op1Value)}"`); 488 | } 489 | validAssertEqDstValue = Number( 490 | BigInt(op0Value) * BigInt(op1Value), 491 | ).toString(10); 492 | break; 493 | case ResLogics.Op1: 494 | if (op1Value === "") { 495 | runSheet.getRange(op1Addr).setFormula(`="${dstValue}"`); 496 | op1Value = runSheet.getRange(op1Addr).getValue(); 497 | } 498 | if (dstValue === "") { 499 | runSheet.getRange(dstAddr).setFormula(`="${op1Value}"`); 500 | } 501 | validAssertEqDstValue = op1Value; 502 | break; 503 | } 504 | dstValue = runSheet.getRange(dstAddr).getValue(); 505 | if (dstValue != validAssertEqDstValue) { 506 | throw new AssertEqError(); 507 | } 508 | break; 509 | } 510 | 511 | runSheet.getRange(`${op0Column}${n + 2}`).setFormula(`=${op0Addr}`); 512 | runSheet.getRange(`${runOp1Column}${n + 2}`).setFormula(`=${op1Addr}`); 513 | 514 | const resValue: string = runSheet.getRange(`${resColumn}${n + 2}`).getValue(); 515 | 516 | switch (instruction.PcUpdate) { 517 | case PcUpdates.Jump: 518 | runSheet 519 | .getRange(`${pcColumn}${n + 2 + 1}`) 520 | .setFormula(`=${resColumn}${n + 2}`); 521 | break; 522 | case PcUpdates.JumpRel: 523 | runSheet 524 | .getRange(`${pcColumn}${n + 2 + 1}`) 525 | .setFormula( 526 | getFormulaOfAddition( 527 | pc, 528 | resValue, 529 | `${pcColumn}${n + 2}`, 530 | `${resColumn}${n + 2}`, 531 | ), 532 | ); 533 | break; 534 | case PcUpdates.Jnz: 535 | runSheet 536 | .getRange(`${pcColumn}${n + 2 + 1}`) 537 | .setFormula( 538 | getFormulaOfAddition( 539 | pc, 540 | dstValue == "0" ? size(instruction).toString(10) : resValue, 541 | `${pcColumn}${n + 2}`, 542 | dstValue == "0" 543 | ? size(instruction).toString(10) 544 | : `${resColumn}${n + 2}`, 545 | ), 546 | ); 547 | break; 548 | case PcUpdates.Regular: 549 | runSheet 550 | .getRange(`${pcColumn}${n + 2 + 1}`) 551 | .setFormula( 552 | getFormulaOfAddition( 553 | pc, 554 | `${size(instruction)}`, 555 | `${pcColumn}${n + 2}`, 556 | `${size(instruction)}`, 557 | ), 558 | ); 559 | break; 560 | } 561 | 562 | switch (instruction.FpUpdate) { 563 | case FpUpdates.Constant: 564 | runSheet 565 | .getRange(`${fpColumn}${n + 2 + 1}`) 566 | .setFormula(`=${fpColumn}${n + 2}`); 567 | break; 568 | case FpUpdates.ApPlus2: 569 | runSheet 570 | .getRange(`${fpColumn}${n + 2 + 1}`) 571 | .setFormula(getFormulaOfAddition(ap, "2", `${apColumn}${n + 2}`, "2")); 572 | break; 573 | case FpUpdates.Dst: 574 | runSheet 575 | .getRange(`${fpColumn}${n + 2 + 1}`) 576 | .setFormula(`=${dstColumn}${n + 2}`); 577 | break; 578 | } 579 | 580 | switch (instruction.ApUpdate) { 581 | case ApUpdates.Add1: 582 | runSheet 583 | .getRange(`${apColumn}${n + 2 + 1}`) 584 | .setFormula(getFormulaOfAddition(ap, `1`, `${apColumn}${n + 2}`, "1")); 585 | break; 586 | case ApUpdates.Add2: 587 | runSheet 588 | .getRange(`${apColumn}${n + 2 + 1}`) 589 | .setFormula(getFormulaOfAddition(ap, `2`, `${apColumn}${n + 2}`, "2")); 590 | break; 591 | case ApUpdates.AddRes: 592 | runSheet 593 | .getRange(`${apColumn}${n + 2 + 1}`) 594 | .setFormula( 595 | getFormulaOfAddition( 596 | ap, 597 | resValue, 598 | `${apColumn}${n + 2}`, 599 | `${resColumn}${n + 2}`, 600 | ), 601 | ); 602 | break; 603 | case ApUpdates.Constant: 604 | runSheet 605 | .getRange(`${apColumn}${n + 2 + 1}`) 606 | .setFormula(`=${apColumn}${n + 2}`); 607 | break; 608 | } 609 | } 610 | 611 | function runUntilPc(): number { 612 | let i: number = getLastActiveRowNumber(`${pcColumn}`, runSheet) - 2; 613 | let pc: string = runSheet.getRange(`${pcColumn}${i + 1 + 1}`).getValue(); 614 | while (!isFinalPc(pc)) { 615 | step(i); 616 | i++; 617 | pc = runSheet.getRange(`${pcColumn}${i + 1 + 1}`).getValue(); 618 | } 619 | return i + 1; //number of steps exectued (i+2 is the row of the new registers) 620 | } 621 | 622 | function relocateMemory() { 623 | let formulas: string[] = []; 624 | let columnIndex: number = columnToIndex(executionColumn); 625 | let maxProgramRow: number = getLastActiveRowNumber( 626 | progDecInstructionColumn, 627 | programSheet, 628 | ); 629 | for (let row = 2; row <= maxProgramRow; row++) { 630 | formulas.push(`=Program!${progBytecodeColumn}${row}`); 631 | } 632 | while (runSheet.getRange(1, columnIndex + 1).getValue() != "") { 633 | let currentColumn: string = indexToColumn(columnIndex); 634 | let maxRowNumber: number = getLastActiveRowNumber(currentColumn, runSheet); 635 | if (maxRowNumber == 1) { 636 | columnIndex++; 637 | continue; 638 | } 639 | for (let row = 2; row <= maxRowNumber; row++) { 640 | formulas.push(`=Run!${currentColumn}${row}`); 641 | } 642 | columnIndex++; 643 | } 644 | proverSheet 645 | .getRange(3, columnToIndex(provValuesColumn) + 1, formulas.length) 646 | .setFormulas(formulas.map((formula) => [formula])); 647 | 648 | proverSheet 649 | .getRange(3, columnToIndex(provSegmentsColumn) + 1, formulas.length) 650 | .setFormulas( 651 | formulas.map((_, index) => [ 652 | `=FORMULATEXT(${provValuesColumn}${index + 3})`, 653 | ]), 654 | ); 655 | 656 | proverSheet 657 | .getRange(3, columnToIndex(provAddressColumn) + 1, formulas.length) 658 | .setFormulas( 659 | formulas.map((_, index) => { 660 | if (index <= maxProgramRow - 2) { 661 | return [`=REGEXEXTRACT(${provSegmentsColumn}${index + 3}; "=(.*)")`]; 662 | } else { 663 | return [ 664 | `=REGEXEXTRACT(${provSegmentsColumn}${index + 3}; "([A-Z]+[0-9]+)$")`, 665 | ]; 666 | } 667 | }), 668 | ); 669 | 670 | let finalRegisters: number = 671 | getLastActiveRowNumber(provAddressColumn, proverSheet) - 1; 672 | proverSheet 673 | .getRange(3, columnToIndex(provMemoryRelocatedColumn) + 1, formulas.length) 674 | .setFormulas( 675 | formulas.map((_, index) => { 676 | let currentCell: string = `${provValuesColumn}${index + 3}`; 677 | let searchingRange: string = `${provAddressColumn}3:${provAddressColumn}`; 678 | Logger.log(finalRegisters); 679 | return [ 680 | `=IFERROR(MATCH(${currentCell};${searchingRange};0); IFERROR(MATCH(LEFT(${currentCell}) & RIGHT(${currentCell}; LEN(${currentCell})-1)-1; ${searchingRange}; 0)+1; IF(IS_CELL(${currentCell});${finalRegisters};${currentCell})))`, 681 | ]; 682 | }), 683 | ); 684 | } 685 | 686 | function relocateTrace() { 687 | let relocatedPCFormulas: string[] = []; 688 | let relocatedFPFormulas: string[] = []; 689 | let relocatedAPFormulas: string[] = []; 690 | 691 | let registersValue: string[][] = runSheet 692 | .getRange(`${pcColumn}2:${apColumn}`) 693 | .getValues(); 694 | registersValue.forEach(([pc, fp, ap], index) => { 695 | if (Boolean(pc) && Boolean(fp) && Boolean(ap)) { 696 | let searchingRange: string = `${provAddressColumn}3:${provAddressColumn}`; 697 | let currentPcCell: string = `Run!${pcColumn}${index + 2}`; 698 | let currentFpCell: string = `Run!${fpColumn}${index + 2}`; 699 | let currentApCell: string = `Run!${apColumn}${index + 2}`; 700 | relocatedPCFormulas.push( 701 | `=IFERROR(MATCH(${currentPcCell};${searchingRange};0); IFERROR(MATCH(LEFT(${currentPcCell}) & RIGHT(${currentPcCell}; LEN(${currentPcCell})-1)-1; ${searchingRange}; 0)+1; ${currentPcCell}))`, 702 | ); 703 | relocatedFPFormulas.push( 704 | `=IFERROR(MATCH(${currentFpCell};${searchingRange};0); IFERROR(MATCH(LEFT(${currentFpCell}) & RIGHT(${currentFpCell}; LEN(${currentFpCell})-1)-1; ${searchingRange}; 0)+1; ${currentFpCell}))`, 705 | ); 706 | relocatedAPFormulas.push( 707 | `=IFERROR(MATCH(${currentApCell};${searchingRange};0); IFERROR(MATCH(LEFT(${currentApCell}) & RIGHT(${currentApCell}; LEN(${currentApCell})-1)-1; ${searchingRange}; 0)+1; ${currentApCell}))`, 708 | ); 709 | } 710 | }); 711 | 712 | relocatedPCFormulas.pop(); 713 | relocatedFPFormulas.pop(); 714 | relocatedAPFormulas.pop(); 715 | 716 | proverSheet 717 | .getRange(3, 5, relocatedPCFormulas.length) 718 | .setFormulas(relocatedPCFormulas.map((pc) => [pc])); 719 | proverSheet 720 | .getRange(3, 6, relocatedFPFormulas.length) 721 | .setFormulas(relocatedFPFormulas.map((fp) => [fp])); 722 | proverSheet 723 | .getRange(3, 7, relocatedAPFormulas.length) 724 | .setFormulas(relocatedAPFormulas.map((ap) => [ap])); 725 | } 726 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "lib": ["ESNext"], 5 | "experimentalDecorators": true 6 | } 7 | } 8 | --------------------------------------------------------------------------------