From 781742f382475b8404da5bf1a7dfa1c3f8508614 Mon Sep 17 00:00:00 2001 From: enitrat Date: Sun, 5 Oct 2025 10:52:47 +0200 Subject: [PATCH 1/2] - postgresVectorStore.ts - Added `updateDocumentsMetadata()` to update metadata (and `source` column) in-place by `uniqueId` without re-embedding or replacing content. - vectorStoreUtils.ts - `findChunksToUpdateAndRemove()` now returns three buckets: `contentChanged`, `metadataOnlyChanged`, and `chunksToRemove`. - `updateVectorStore()` now: - Removes documents that no longer exist. - Upserts content-changed documents via `addDocuments()` (re-embeds as before). - Updates metadata-only documents via `updateDocumentsMetadata()` (no re-embed). - Tests - Updated `ingesters/__tests__/vectorStoreUtils.test.ts` to cover content-changed vs metadata-only cases; all tests pass. --- ingesters/__tests__/vectorStoreUtils.test.ts | 15 +- ingesters/src/db/postgresVectorStore.ts | 47 + ingesters/src/ingesters/CairoBookIngester.ts | 7 +- .../src/ingesters/CoreLibDocsIngester.ts | 4 +- .../src/ingesters/OpenZeppelinDocsIngester.ts | 4 +- ingesters/src/utils/vectorStoreUtils.ts | 86 +- .../generated/cairo_book_summary.md | 12589 ++++++++-------- 7 files changed, 6164 insertions(+), 6588 deletions(-) diff --git a/ingesters/__tests__/vectorStoreUtils.test.ts b/ingesters/__tests__/vectorStoreUtils.test.ts index 8025dbc..bd4f421 100644 --- a/ingesters/__tests__/vectorStoreUtils.test.ts +++ b/ingesters/__tests__/vectorStoreUtils.test.ts @@ -87,7 +87,7 @@ describe('findChunksToUpdateAndRemove', () => { const result = findChunksToUpdateAndRemove(freshChunks, storedChunkHashes); - expect(result.chunksToUpdate).toEqual([ + expect(result.contentChanged).toEqual([ { metadata: { name: '2', @@ -113,6 +113,7 @@ describe('findChunksToUpdateAndRemove', () => { pageContent: 'Some Content 3', }, ]); + expect(result.metadataOnlyChanged).toEqual([]); expect(result.chunksToRemove).toEqual(['3']); }); @@ -173,14 +174,15 @@ describe('findChunksToUpdateAndRemove', () => { const result = findChunksToUpdateAndRemove(freshChunks, storedChunkHashes); - expect(result.chunksToUpdate).toEqual([]); + expect(result.contentChanged).toEqual([]); + expect(result.metadataOnlyChanged).toEqual([]); expect(result.chunksToRemove).toEqual([]); }); it('should handle empty inputs correctly', () => { const result = findChunksToUpdateAndRemove([], []); - - expect(result.chunksToUpdate).toEqual([]); + expect(result.contentChanged).toEqual([]); + expect(result.metadataOnlyChanged).toEqual([]); expect(result.chunksToRemove).toEqual([]); }); @@ -217,8 +219,8 @@ describe('findChunksToUpdateAndRemove', () => { const result = findChunksToUpdateAndRemove(freshChunks, storedChunkHashes); - // Should update because metadata changed (sourceLink and title) - expect(result.chunksToUpdate).toEqual([ + // Should update metadata-only because metadata changed (sourceLink and title) + expect(result.metadataOnlyChanged).toEqual([ { metadata: { name: '1', @@ -232,6 +234,7 @@ describe('findChunksToUpdateAndRemove', () => { pageContent: 'Some Content 1', }, ]); + expect(result.contentChanged).toEqual([]); expect(result.chunksToRemove).toEqual([]); }); }); diff --git a/ingesters/src/db/postgresVectorStore.ts b/ingesters/src/db/postgresVectorStore.ts index 724b366..fe58a6d 100644 --- a/ingesters/src/db/postgresVectorStore.ts +++ b/ingesters/src/db/postgresVectorStore.ts @@ -357,6 +357,53 @@ export class VectorStore { } } + /** + * Update only the metadata (and source column for consistency) for existing documents. + * Does NOT modify content, embedding, or contentHash. + */ + async updateDocumentsMetadata( + documents: DocumentInterface[], + options?: { ids?: string[] }, + ): Promise { + if (documents.length === 0) return; + + logger.info(`Updating metadata for ${documents.length} documents`); + + try { + const client = await this.pool.connect(); + try { + await client.query('BEGIN'); + + const updates = documents.map((doc, i) => { + const uniqueId = options?.ids?.[i] || doc.metadata.uniqueId || null; + const source = doc.metadata.source || null; + const query = ` + UPDATE ${this.tableName} + SET metadata = $2, + source = $3 + WHERE uniqueId = $1 + `; + return client.query(query, [ + uniqueId, + JSON.stringify(doc.metadata), + source, + ]); + }); + + await Promise.all(updates); + await client.query('COMMIT'); + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } + } catch (error) { + logger.error('Error updating document metadata:', error); + throw DatabaseError.handlePgError(error as PgError); + } + } + /** * Find a specific book chunk by name * @param name - Name of the book chunk diff --git a/ingesters/src/ingesters/CairoBookIngester.ts b/ingesters/src/ingesters/CairoBookIngester.ts index 17e14bc..4b64c9e 100644 --- a/ingesters/src/ingesters/CairoBookIngester.ts +++ b/ingesters/src/ingesters/CairoBookIngester.ts @@ -33,7 +33,7 @@ export class CairoBookIngester extends MarkdownIngester { chunkOverlap: 512, baseUrl: 'https://book.cairo-lang.org', urlSuffix: '.html', - useUrlMapping: false, + useUrlMapping: true, }; super(config, DocumentSource.CAIRO_BOOK); @@ -71,7 +71,7 @@ export class CairoBookIngester extends MarkdownIngester { maxChars: 2048, minChars: 500, overlap: 256, - headerLevels: [1, 2], // Split on H1 and H2 headers + headerLevels: [1, 2, 3], // Split on H1/H2/H3 (title uses deepest) preserveCodeBlocks: true, idPrefix: 'cairo-book', trim: true, @@ -97,12 +97,13 @@ export class CairoBookIngester extends MarkdownIngester { chunkNumber: chunk.meta.chunkNumber, // Already 0-based contentHash: contentHash, uniqueId: chunk.meta.uniqueId, - sourceLink: this.config.baseUrl, + sourceLink: chunk.meta.sourceLink || this.config.baseUrl, source: this.source, }, }); }); + return localChunks; } diff --git a/ingesters/src/ingesters/CoreLibDocsIngester.ts b/ingesters/src/ingesters/CoreLibDocsIngester.ts index 62698ca..ea39ca1 100644 --- a/ingesters/src/ingesters/CoreLibDocsIngester.ts +++ b/ingesters/src/ingesters/CoreLibDocsIngester.ts @@ -75,7 +75,7 @@ export class CoreLibDocsIngester extends MarkdownIngester { maxChars: 2048, minChars: 500, overlap: 256, - headerLevels: [1, 2], // Split on H1 and H2 headers + headerLevels: [1, 2, 3], // Split on H1/H2/H3 (title uses deepest) preserveCodeBlocks: true, idPrefix: 'corelib', trim: true, @@ -101,7 +101,7 @@ export class CoreLibDocsIngester extends MarkdownIngester { chunkNumber: chunk.meta.chunkNumber, // Already 0-based contentHash: contentHash, uniqueId: chunk.meta.uniqueId, - sourceLink: this.config.baseUrl, + sourceLink: chunk.meta.sourceLink || this.config.baseUrl, source: this.source, }, }); diff --git a/ingesters/src/ingesters/OpenZeppelinDocsIngester.ts b/ingesters/src/ingesters/OpenZeppelinDocsIngester.ts index 274d339..ae78730 100644 --- a/ingesters/src/ingesters/OpenZeppelinDocsIngester.ts +++ b/ingesters/src/ingesters/OpenZeppelinDocsIngester.ts @@ -75,7 +75,7 @@ export class OpenZeppelinDocsIngester extends MarkdownIngester { maxChars: 2048, minChars: 500, overlap: 256, - headerLevels: [1, 2], // Split on H1 and H2 headers + headerLevels: [1, 2, 3], // Split on H1/H2/H3 (title uses deepest) preserveCodeBlocks: true, idPrefix: 'openzeppelin-docs', trim: true, @@ -101,7 +101,7 @@ export class OpenZeppelinDocsIngester extends MarkdownIngester { chunkNumber: chunk.meta.chunkNumber, // Already 0-based contentHash: contentHash, uniqueId: chunk.meta.uniqueId, - sourceLink: this.config.baseUrl, + sourceLink: chunk.meta.sourceLink || this.config.baseUrl, source: this.source, }, }); diff --git a/ingesters/src/utils/vectorStoreUtils.ts b/ingesters/src/utils/vectorStoreUtils.ts index b8a8ca4..9b11c03 100644 --- a/ingesters/src/utils/vectorStoreUtils.ts +++ b/ingesters/src/utils/vectorStoreUtils.ts @@ -23,7 +23,8 @@ export function findChunksToUpdateAndRemove( metadata: BookChunk; }[], ): { - chunksToUpdate: Document[]; + contentChanged: Document[]; + metadataOnlyChanged: Document[]; chunksToRemove: string[]; } { const storedDataMap = new Map( @@ -33,31 +34,51 @@ export function findChunksToUpdateAndRemove( freshChunks.map((chunk) => [chunk.metadata.uniqueId, chunk]), ); - // Find chunks that need to be updated (content or metadata has changed) - const chunksToUpdate = freshChunks.filter((chunk) => { - const storedMetadata = storedDataMap.get(chunk.metadata.uniqueId); - if (!storedMetadata) { - // New chunk that doesn't exist in storage - return true; + const contentChanged: Document[] = []; + const metadataOnlyChanged: Document[] = []; + + for (const fresh of freshChunks) { + const stored = storedDataMap.get(fresh.metadata.uniqueId); + if (!stored) { + // New doc: requires full insert + embedding + contentChanged.push(fresh); + continue; + } + + const storedHash = stored.contentHash; + const freshHash = fresh.metadata.contentHash; + if (storedHash !== freshHash) { + // Content changed: re-embed and upsert fully + contentChanged.push(fresh); + continue; } - // Update if content hash changed or any metadata field changed - for (const key in chunk.metadata) { - if ( - storedMetadata[key as keyof BookChunk] !== - chunk.metadata[key as keyof BookChunk] - ) { - return true; + + // Content same, check if any metadata field differs + const keys = new Set([ + ...(Object.keys(stored) as (keyof BookChunk)[]), + ...(Object.keys(fresh.metadata) as (keyof BookChunk)[]), + ]); + + let metaDiffers = false; + for (const key of keys) { + // Ignore contentHash here since we already know it's equal + if (key === 'contentHash') continue; + if (stored[key] !== fresh.metadata[key]) { + metaDiffers = true; + break; } } - return false; - }); + if (metaDiffers) { + metadataOnlyChanged.push(fresh); + } + } // Find chunks that need to be removed (no longer exist in fresh chunks) const chunksToRemove = storedChunkHashes .filter((stored) => !freshChunksMap.has(stored.uniqueId)) .map((stored) => stored.uniqueId); - return { chunksToUpdate, chunksToRemove }; + return { contentChanged, metadataOnlyChanged, chunksToRemove }; } /** @@ -80,16 +101,21 @@ export async function updateVectorStore( await vectorStore.getStoredBookPagesMetadata(source); // Find chunks to update and remove - const { chunksToUpdate, chunksToRemove } = findChunksToUpdateAndRemove( - chunks, - storedChunkHashes, - ); + const { contentChanged, metadataOnlyChanged, chunksToRemove } = + findChunksToUpdateAndRemove( + chunks, + storedChunkHashes, + ); logger.info( - `Found ${storedChunkHashes.length} stored chunks for source: ${source}. ${chunksToUpdate.length} chunks to update and ${chunksToRemove.length} chunks to remove`, + `Found ${storedChunkHashes.length} stored chunks for source: ${source}. ${contentChanged.length} content changes, ${metadataOnlyChanged.length} metadata-only changes, and ${chunksToRemove.length} removals`, ); - if (chunksToUpdate.length === 0 && chunksToRemove.length === 0) { + if ( + contentChanged.length === 0 && + metadataOnlyChanged.length === 0 && + chunksToRemove.length === 0 + ) { logger.info('No changes to update or remove'); return; } @@ -129,13 +155,19 @@ export async function updateVectorStore( } // Update chunks that have changed - if (chunksToUpdate.length > 0) { - await vectorStore.addDocuments(chunksToUpdate, { - ids: chunksToUpdate.map((chunk) => chunk.metadata.uniqueId), + if (contentChanged.length > 0) { + await vectorStore.addDocuments(contentChanged, { + ids: contentChanged.map((chunk) => chunk.metadata.uniqueId), + }); + } + + if (metadataOnlyChanged.length > 0) { + await vectorStore.updateDocumentsMetadata(metadataOnlyChanged, { + ids: metadataOnlyChanged.map((chunk) => chunk.metadata.uniqueId), }); } logger.info( - `Updated ${chunksToUpdate.length} chunks and removed ${chunksToRemove.length} chunks for source: ${source}.`, + `Updated ${contentChanged.length} content chunks, ${metadataOnlyChanged.length} metadata-only chunks, and removed ${chunksToRemove.length} chunks for source: ${source}.`, ); } diff --git a/python/src/scripts/summarizer/generated/cairo_book_summary.md b/python/src/scripts/summarizer/generated/cairo_book_summary.md index b81a4a4..d4f7734 100644 --- a/python/src/scripts/summarizer/generated/cairo_book_summary.md +++ b/python/src/scripts/summarizer/generated/cairo_book_summary.md @@ -1,103 +1,91 @@ -# cairo-book Documentation Summary +--- -Introduction to Cairo +Sources: -What is Cairo? +- https://www.starknet.io/cairo-book/ch00-00-introduction.html +- https://www.starknet.io/cairo-book/ch00-01-foreword.html +- https://www.starknet.io/cairo-book/ +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/title-page.html -# What is Cairo? +--- -## Overview +# Introduction to Cairo and The Cairo Book -Cairo is a programming language designed to enable computational integrity through mathematical proofs, leveraging STARK technology. It allows programs to prove that they have executed correctly, even on untrusted machines. This capability is crucial for scenarios where verifiable computation is essential. +## Book Overview and Resources -## Core Technology and Applications +This version of the text assumes you’re using Cairo version 2.12.0 and Starknet Foundry version 0.48.0. The book is open source, supported by the Cairo Community, StarkWare, and Voyager. -- **STARK Technology:** Cairo is built upon STARKs (Scalable Transparent ARguments of Knowledge), a modern form of Probabilistically Checkable Proofs, to transform computational claims into verifiable constraint systems. The ultimate goal of Cairo is to generate mathematical proofs that can be verified efficiently and with absolute certainty. -- **Starknet:** The primary application of Cairo is Starknet, a Layer 2 scaling solution for Ethereum. Starknet utilizes Cairo's proof system to allow computations to be executed off-chain, with a STARK proof generated and then verified on the Ethereum mainnet. This significantly enhances scalability while maintaining security, as it avoids the need for every participant to verify every computation. -- **Beyond Blockchain:** Cairo's potential extends to any domain requiring efficient verification of computations, not just blockchain. +Additional resources for mastering Cairo include: +* **The Cairo Playground**: A browser-based environment for writing, compiling, debugging, and proving Cairo code without setup. It shows compilation into Sierra (Intermediate Representation) and Casm (Cairo Assembly). +* **The Cairo Core Library Docs**: Documentation for the standard set of types, traits, and utilities built into the language. +* **The Cairo Package Registry**: Hosts reusable libraries like Alexandria and Open Zeppelin Contracts for Cairo, integrated via Scarb. +* **The Scarb documentation**: Official documentation for Cairo’s package manager and build tool. +* **The Cairo whitepaper**: Explains Cairo as a language for writing provable programs and its architecture for scalable, verifiable computation. -## Design Philosophy +## What is Cairo? -- **Provable Programs:** Cairo is a general-purpose programming language specifically engineered for creating provable programs. It abstracts away the underlying cryptographic complexities, allowing developers to focus on program logic without needing deep expertise in cryptography or complex mathematics. -- **Rust Inspiration:** The language is strongly inspired by Rust, aiming for a developer-friendly experience. -- **Performance:** Powered by a Rust VM and a next-generation prover, Cairo offers fast execution and proof generation, making it suitable for building provable applications. +Cairo is a programming language designed specifically to leverage mathematical proofs for computational integrity, allowing programs to prove they executed correctly, even on untrusted machines. -## Target Audience +* **Foundation**: The language is built on STARK technology, a modern evolution of Probabilistically Checkable Proofs (PCPs), which transforms computational claims into verifiable constraint systems. +* **Design**: Strongly inspired by Rust, Cairo abstracts away deep cryptographic complexities, allowing developers to focus on program logic while maintaining the full power of STARKs. +* **Performance**: Powered by a Rust VM and a next-generation prover, Cairo execution and proof generation are very fast. -This book is intended for developers with a basic understanding of programming concepts who wish to learn Cairo for building smart contracts on Starknet or for other applications requiring verifiable computation. +## Applications and Context -Getting Started with the Cairo Book +Cairo's primary application today is **Starknet**, a Layer 2 scaling solution for Ethereum. -# Getting Started with the Cairo Book +* **Scalability**: Starknet addresses the Blockchain Trilemma by offloading complex computations from Ethereum L1. It uses Cairo's proof system where computations are executed off-chain, and a STARK proof is generated. This proof is then verified on L1 using significantly less power than re-executing the computation, enabling massive scalability while maintaining security. +* **General Purpose**: Beyond blockchain, Cairo can be used for any scenario where computational integrity needs efficient verification across different machines. -This section outlines recommended reading paths through the Cairo book based on your background and goals. +## Audience and Prerequisites -## For General-Purpose Developers +This book assumes basic programming knowledge (variables, functions, data structures). Prior Rust experience is helpful but not required. -Focus on chapters 1-12, which cover core language features and programming concepts without deep dives into smart contract specifics. +The book caters to three main audiences with recommended reading paths: +1. **General-Purpose Developers**: Focus on chapters 1-12 for core language features, avoiding deep smart contract specifics. +2. **New Smart Contract Developers**: Read the book front to back for a solid foundation in both language and contract development. +3. **Experienced Smart Contract Developers**: Focus on Cairo basics (Chapters 1-3), the trait/generics system (Chapter 8), and then smart contract development (Chapter 15+). -## For New Smart Contract Developers +## References -Read the book from beginning to end to build a solid foundation in both Cairo language fundamentals and smart contract development principles. +* Cairo CPU Architecture: https://eprint.iacr.org/2021/1063 +* Cairo, Sierra and Casm: https://medium.com/nethermind-eth/under-the-hood-of-cairo-1-0-exploring-sierra-7f32808421f5 +* State of non determinism: https://twitter.com/PapiniShahar/status/1638203716535713798 -## For Experienced Smart Contract Developers +--- -A focused path is recommended: +Sources: -- Chapters 1-3: Cairo basics -- Chapter 8: Cairo's trait and generics system -- Chapter 15: Smart contract development -- Reference other chapters as needed. +- https://www.starknet.io/cairo-book/ch01-01-installation.html +- https://www.starknet.io/cairo-book/ch01-00-getting-started.html -### Prerequisites +--- -Basic programming knowledge (variables, functions, data structures) is assumed. Prior experience with Rust is helpful but not required. +# Setting Up the Development Environment -Cairo's Architecture +### Installing Core Tooling with `starkup` -# Cairo's Architecture +The first step to setting up the environment is installing Cairo using `starkup`, a command line tool for managing Cairo versions and associated tools. `starkup` installs the latest stable versions of Cairo, Scarb, and Starknet Foundry. -Cairo is a STARK-friendly Von Neumann architecture designed for generating validity proofs for arbitrary computations. It is optimized for the STARK proof system but remains compatible with other proof systems. Cairo features a Turing-complete process virtual machine. +**Scarb** is Cairo's build toolchain and package manager, inspired by Rust's Cargo. It handles building code (pure Cairo or Starknet contracts), dependency management, and provides LSP support for the VSCode Cairo 1 extension. -## Components of Cairo +**Starknet Foundry** is a toolchain supporting features like writing and running tests, deploying contracts, and interacting with the Starknet network. -Cairo comprises three primary components: - -1. **Cairo Compiler:** Transforms Cairo source code into Cairo bytecode (instructions and metadata), often referred to as compilation artifacts. -2. **Cairo Virtual Machine (CairoVM):** Executes the compilation artifacts, processing instructions to produce the AIR private input (execution trace and memory) and AIR public input (initial/final states, public memory, configuration data). -3. **Cairo Prover and Verifier:** The prover generates a proof using the AIR inputs, and the verifier asynchronously verifies the proof against the AIR public input. - -## Arithmetic Intermediate Representation (AIR) - -AIR is an arithmetization technique fundamental to proof systems. While STARKs use AIRs, other systems might employ different techniques like R1CS or PLONKish arithmetization. - -Getting Started with Cairo - -Introduction and Setup - -# Introduction and Setup - -## Installing Cairo - -Cairo is installed using `starkup`, a command-line tool for managing Cairo versions and associated tools. An internet connection is required for the download. - -### Installing `starkup` on Linux or MacOs - -Open a terminal and run the following command: +To install `starkup` on Linux or macOS, run the following command in the terminal: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.dev | sh ``` -This script installs the `starkup` tool, which in turn installs the latest stable versions of Cairo, Scarb, and Starknet Foundry. A success message will appear upon completion: - -```bash +If successful, the output will show: +``` starkup: Installation complete. ``` -### Verifying Installations +### Verifying Toolchain Installation -After `starkup` completes, open a new terminal session and verify the installations: +After `starkup` completes, verify the installations of Scarb and Starknet Foundry in a new terminal session: ```bash $ scarb --version @@ -109,85 +97,63 @@ $ snforge --version snforge 0.48.0 ``` -## Scarb - -Scarb is Cairo's build toolchain and package manager, inspired by Rust's Cargo. It bundles the Cairo compiler and language server, simplifying code building, dependency management, and providing LSP support for the VSCode Cairo 1 extension. - -## Starknet Foundry - -Starknet Foundry is a toolchain for Cairo programs and Starknet smart contract development, offering features for writing and running tests, deploying contracts, and interacting with the Starknet network. +### Configuring the VSCode Extension -## VSCode Extension +Cairo provides a VSCode extension offering syntax highlighting and code completion. Install it from the VSCode Marketplace. After installation, navigate to the extension settings and ensure the following options are ticked: -Install the Cairo VSCode extension from the [VSCode Marketplace][vsc extension] for syntax highlighting, code completion, and other features. Ensure `Enable Language Server` and `Enable Scarb` are ticked in the extension settings. +* `Enable Language Server` +* `Enable Scarb` -[vsc extension]: https://marketplace.visualstudio.com/items?itemName=starkware.cairo1 +--- -## Cairo Editions +Sources: -Cairo uses editions (prelude versions) to manage available functions and traits. The `edition` is specified in the `Scarb.toml` file. New projects typically use the latest edition. +- https://www.starknet.io/cairo-book/ch01-02-hello-world.html +- https://www.starknet.io/cairo-book/ch07-01-packages-and-crates.html -| Version | Details | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| `2024-07` | [details for 2024-07](https://community.starknet.io/t/cairo-v2-7-0-is-coming/114362#the-2024_07-edition-3) | -| `2023-11` | [details for 2023-11](https://community.starknet.io/t/cairo-v2-5-0-is-out/112807#the-pub-keyword-9) | -| `2023-10` / `2023-1` | [details for 2023-10](https://community.starknet.io/t/cairo-v2-4-0-is-out/109275#editions-and-the-introduction-of-preludes-10) | +--- -## Getting Help +## Scarb: Project Creation and Basic Execution -For questions about Starknet or Cairo, use the [Starknet Discord server][discord]. +### Creating a Project Directory -[discord]: https://discord.gg/starknet-community +You should start by making a directory for your Cairo code. For the examples in this book, making a *cairo_projects* directory in your home directory is suggested. -## Starknet AI Agent +For Linux, macOS, and PowerShell on Windows, use: -The Starknet AI Agent, trained on Cairo and Starknet documentation, assists with related questions. It can be accessed at [Starknet Agent][agent gpt]. - -[agent gpt]: https://agent.starknet.id/ - -Creating Your First Cairo Project - -# Creating Your First Cairo Project - -To begin writing your first Cairo program, you'll need to set up a project directory and initialize a new project using Scarb. - -## Creating a Project Directory - -It's recommended to create a dedicated directory for your Cairo projects. You can do this using the following commands in your terminal: - -For Linux, macOS, and PowerShell on Windows: - -```shell +```bash mkdir ~/cairo_projects cd ~/cairo_projects ``` -For Windows CMD: +For Windows CMD, use: ```cmd > mkdir "%USERPROFILE%\cairo_projects" > cd /d "%USERPROFILE%\cairo_projects" ``` -## Creating a Project with Scarb +### Initializing the Project -Once you are in your projects directory, you can create a new Scarb project. Scarb will prompt you to choose a test runner; `Starknet Foundry` is the recommended default. +Navigate to your project directory and create a new project using Scarb: ```bash scarb new hello_world ``` -This command generates a new directory named `hello_world` containing the following files and directories: +Scarb will prompt for a test runner setup; `❯ Starknet Foundry (default)` is generally preferred. + +This command creates a directory named *hello_world* containing configuration files and source directories. -- `Scarb.toml`: The project's manifest file. -- `src/lib.cairo`: The main library file. -- `tests/`: A directory for tests. +### Project Structure and Configuration -Scarb also initializes a Git repository. You can skip this with the `--no-vcs` flag during project creation. +After running `scarb new hello_world`, navigate into the directory (`cd hello_world`). Scarb generates a *Scarb.toml* file, a *src* directory with *lib.cairo*, and a *tests* directory (which can be removed for simple executable programs). A Git repository is also initialized. -### `Scarb.toml` Configuration +#### Scarb.toml Manifest -The `Scarb.toml` file is written in TOML format and defines your project's configuration. A typical `Scarb.toml` for a basic project looks like this: +The *Scarb.toml* file uses TOML format for configuration. The initial structure for a Starknet contract project looks like this: + +Filename: Scarb.toml ```toml [package] @@ -209,158 +175,159 @@ sierra = true [scripts] test = "snforge test" + +# ... ``` -### Project Structure +For a basic Cairo executable program instead of a contract, you can modify *Scarb.toml* to resemble the following: -Scarb enforces a standard project structure: +Filename: Scarb.toml -```txt -├── Scarb.toml -├── src -│ ├── lib.cairo -│ └── hello_world.cairo -``` +```toml +[package] +name = "hello_world" +version = "0.1.0" +edition = "2024_07" + +[cairo] +enable-gas = false + +[dependencies] +cairo_execute = "2.12.0" -All source code should reside within the `src` directory. The top-level directory is for non-code related files like READMEs and configuration. +[[target.executable]] +name = "hello_world_main" +function = "hello_world::hello_world::main" +``` -### Writing Your First Program +#### Source File Organization -To create a simple "Hello, World!" program, you'll modify the `src/lib.cairo` and create a new file `src/hello_world.cairo`. +Scarb reserves the top-level directory for non-code content. Source files must reside in the *src* directory. -First, update `src/lib.cairo` to declare the `hello_world` module: +1. Delete the content of *src/lib.cairo* and replace it with a module declaration: -```cairo,noplayground +```rust mod hello_world; ``` -Then, create `src/hello_world.cairo` with the following content: +2. Create a new file named *src/hello_world.cairo* with the following code: -```cairo +Filename: src/hello_world.cairo + +```rust #[executable] fn main() { println!("Hello, World!"); } ``` -This code defines an executable function `main` that prints "Hello, World!" to the console. - -## Building a Scarb Project - -To build your project, navigate to the project's root directory (e.g., `hello_world`) and run: +The resulting structure is: -```bash -scarb build ``` - -This command compiles your Cairo code. The output will indicate the compilation progress and completion. - -## Setting Up an Executable Project (Example: `prime_prover`) - -For projects intended to be executable programs (not libraries or Starknet contracts), you need to configure `Scarb.toml` to define an executable target and potentially disable gas tracking. - -Create a new project: - -```bash -scarb new prime_prover -cd prime_prover +├── Scarb.toml +├── src +│   ├── lib.cairo +│   └── hello_world.cairo ``` -Modify `Scarb.toml` to include an executable target and the `cairo_execute` dependency: - -```toml -[package] -name = "prime_prover" -version = "0.1.0" -edition = "2024_07" - -[cairo] -enable-gas = false +### Building and Executing the Project -[dependencies] -cairo_execute = "2.12.0" +From the *hello_world* directory, build the project using: -[[target.executable]] -name = "main" -function = "prime_prover::main" +```bash +$ scarb build + Compiling hello_world v0.1.0 (listings/ch01-getting-started/no_listing_01_hello_world/Scarb.toml) + Finished `dev` profile target(s) in 1 second ``` -- `[[target.executable]]`: Specifies that the package compiles to an executable. -- `name = "main"`: Sets the executable's name. -- `function = "prime_prover::main"`: Defines the entry point function. -- `[cairo] enable-gas = false`: Disables gas tracking, necessary for executables. +To run the compiled program, use `scarb execute`: -Writing and Running Basic Programs - -# Writing and Running Basic Programs - -You can run the `main` function of your Cairo program using the `scarb execute` command. This command first compiles your code and then executes it. - -```shell +```bash $ scarb execute - Compiling hello_world v0.1.0 (listings/ch01-getting-started/no_listing_01_hello_world/Scarb.toml) + Compiling hello_world v0.1.0 (listings/ch01-getting-started/no_listing_01_hello_world/Scarb.toml) Finished `dev` profile target(s) in 1 second Executing hello_world Hello, World! - ``` -If `Hello, world!` is printed to your terminal, you have successfully written and executed your first Cairo program. +### Summary of Scarb Commands -## Anatomy of a Cairo Program +* Install Scarb versions using `asdf`. +* Create a project using `scarb new`. +* Build a project using `scarb build` to generate compiled Sierra code. +* Execute a Cairo program using the `scarb execute` command. -A basic Cairo program consists of functions, with `main` being a special entry point. +Scarb commands are consistent across different operating systems. -### The `main` Function +--- -The `main` function is the starting point for execution in any executable Cairo program. +Sources: -```cairo,noplayground -fn main() { +- https://www.starknet.io/cairo-book/ch01-03-proving-a-prime-number.html -} -``` +--- -- `fn main()`: Declares a function named `main` that takes no parameters and returns nothing. Parameters would be placed inside the parentheses `()`. -- `{}`: Encloses the function body. The opening curly bracket is typically placed on the same line as the function declaration, separated by a space. +## Guided Project: Implementing a Provable Program -### The `println!` Macro +This project introduces key Cairo concepts and the process of generating zero-knowledge proofs locally using the Stwo prover. We will implement a program to prove that a given number is prime using a trial division algorithm. -The body of the `main` function often contains code to perform actions, such as printing output to the screen. +### Project Goal -```cairo,noplayground - println!("Hello, World!"); -``` +The program will take an input number, check its primality, and then use Scarb to execute the program and generate a proof that the primality check was performed correctly. + +### Setting Up a New Project + +Ensure Scarb 2.12.0 or later is installed. Create and navigate into a new Scarb project: -Key points about this line: +```bash +scarb new prime_prover +cd prime_prover +``` -- **Indentation**: Cairo style uses four spaces for indentation. -- **Macro Call**: `println!` calls a Cairo macro. The exclamation mark `!` signifies a macro call, distinguishing it from a regular function call (e.g., `println`). Macros may not follow all the same rules as functions. -- **String Argument**: `"Hello, world!"` is a string literal passed as an argument to the `println!` macro. -- **Semicolon**: The line ends with a semicolon `;`, indicating the end of the expression. Most lines of Cairo code require a semicolon. +The initial `Scarb.toml` file looks minimal: -Building a Primality Prover +Filename: Scarb.toml -# Building a Primality Prover +```toml +[package] +name = "prime_prover" +version = "0.1.0" +edition = "2024_07" -## Proving That A Number Is Prime +[dependencies] -This section introduces key Cairo concepts and the process of generating zero-knowledge proofs locally using the Stwo prover. We will implement a classic mathematical problem suited for zero-knowledge proofs: proving that a number is prime. This project will cover functions, control flow, executable targets, Scarb workflows, and proving statements. +[dev-dependencies] +cairo_test = "2.12.0" +``` -To build a project using Scarb, you can use `scarb build` to generate compiled Sierra code. To execute a Cairo program, use the `scarb execute` command. The commands are cross-platform. +To create an executable program suitable for proving, update `Scarb.toml` to define an executable target and include the `cairo_execute` plugin: -To enable execution and proving, add the following to your `Scarb.toml` file: +Filename: Scarb.toml ```toml +[package] +name = "prime_prover" +version = "0.1.0" +edition = "2024_07" + +[cairo] +enable-gas = false + [dependencies] cairo_execute = "2.12.0" + +[[target.executable]] +name = "main" +function = "prime_prover::main" ``` +The additions specify that the package compiles to an executable, disable gas tracking (necessary for executables), and include the necessary plugin for execution and proving. + ### Writing the Prime-Checking Logic -We will implement a program to check if a number is prime using a simple trial division algorithm. Replace the contents of `src/lib.cairo` with the following code: +We replace the contents of `src/lib.cairo` with the primality test logic and the executable entry point. The implementation uses trial division. -Filename: src/lib.cairo +Filename: src/lib.cairo ```cairo /// Checks if a number is prime @@ -402,206 +369,246 @@ fn main(input: u32) -> bool { } ``` -#### Explanation of the Code: +The `is_prime` function handles edge cases (numbers $\le 1$, 2, and even numbers) and then iterates through odd divisors up to $\sqrt{n}$. The `main` function, marked with `#[executable]`, serves as the entry point, taking a `u32` input and returning the boolean result of `is_prime`. + +*Note: Later modifications might change `u32` to `u128` for a larger range and add panicking behavior for inputs exceeding a certain limit (e.g., 1,000,000), which prevents proof generation if the program panics.* + +### Executing the Program + +We use `scarb execute` to run the program and view the output. The arguments are passed after `--arguments`. + +```bash +scarb execute -p prime_prover --print-program-output --arguments 17 +``` + +Execution output: + +``` +$ scarb execute -p prime_prover --print-program-output --arguments 17 + Compiling prime_prover v0.1.0 (listings/ch01-getting-started/prime_prover/Scarb.toml) + Finished `dev` profile target(s) in 1 second + Executing prime_prover +Program output: +1 +``` + +The output `1` indicates `true` (17 is prime). If the input was 4, the output would be `[0, 0]` (false). + +### Summary -The `is_prime` function: +This guided project demonstrated: +* Defining executable targets in `Scarb.toml`. +* Writing functions and control flow in Cairo. +* Using `scarb execute` to run programs and generate execution traces. +* The overall workflow for proving and verifying computations using `scarb prove` and `scarb verify` (though the latter commands are not explicitly run here, they are the next logical step). -- Accepts a `u32` (unsigned 32-bit integer) and returns a `bool`. -- Handles edge cases: numbers less than or equal to 1 are not prime, 2 is prime, and even numbers greater than 2 are not prime. -- Utilizes a loop to test odd divisors up to the square root of `n`. If no divisors are found, the number is determined to be prime. +--- -The `main` function: +Sources: -- Is marked with `#[executable]`, designating it as the program's entry point. -- Receives input from the user and returns a boolean indicating primality. -- Calls the `is_prime` function to perform the primality check. +- https://www.starknet.io/cairo-book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html -Conclusion and Resources +--- -# Conclusion and Resources +## Advanced Scarb Features and Project Structure -Congratulations on building your first Cairo program! You've successfully: +### Dependency Management in Scarb -- Defined executable targets in `Scarb.toml`. -- Written functions and control flow in Cairo. -- Used `scarb execute` to run programs and generate execution traces. -- Proved and verified computations with `scarb prove` and `scarb verify`. +If you want to import multiple packages, create only one `[dependencies]` section in *Scarb.toml* and list all desired packages beneath it. Development dependencies are declared in a `[dev-dependencies]` section. -Experiment with different inputs or modify the primality check. For instance, the following code demonstrates a check that panics if the input exceeds 1,000,000: +Run `scarb build` to fetch all external dependencies and compile your package. -```bash -$ scarb execute -p prime_prover --print-program-output --arguments 1000001 - Compiling prime_prover v0.1.0 (listings/ch01-getting-started/prime_prover2/Scarb.toml) - Finished `dev` profile target(s) in 1 second - Executing prime_prover -error: Panicked with "Input too large, must be <= 1,000,000". +Dependencies can also be managed via the command line: +* Use `scarb add` to add a dependency, which automatically edits *Scarb.toml*. +* Use `scarb add --dev` for development dependencies. +* To remove a dependency, edit *Scarb.toml* or use the `scarb rm` command. +### The Glob Operator + +To bring *all* public items defined in a path into scope, specify that path followed by the `*` glob operator in a `use` statement: + +```rust +#![allow(unused)] +fn main() { +use core::num::traits::*; +} ``` -If the program panics, no proof can be generated, and thus, verification is not possible. +This brings all public items defined in `core::num::traits` into the current scope. Use the glob operator carefully, as it can make it harder to trace where a name was defined. It is often used when testing to bring everything under test into the `tests` module. -## Additional Resources +--- -To continue your journey with Cairo, explore these resources: +Sources: -- **Cairo Playground:** An online environment for writing, compiling, debugging, and proving Cairo code without any setup. It's useful for experimenting with code snippets and observing their compilation to Sierra and Casm. -- **Cairo Core Library Docs:** Documentation for Cairo's standard library, providing essential types, traits, and utilities available in every Cairo project. -- **Cairo Package Registry:** A hub for reusable Cairo libraries like Alexandria and Open Zeppelin Contracts for Cairo, which can be integrated using Scarb. -- **Scarb Documentation:** Official documentation for Cairo's package manager and build tool, covering package management, dependencies, builds, and project configuration. +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html -For the latest information, ensure you are using Cairo version 2.12.0 and Starknet Foundry version 0.48.0 or later. You can find the book's source code and contribute on its [GitHub repository](https://github.com/cairo-book/cairo-book). +--- -Cairo Language Fundamentals +# Cairo for Smart Contracts and Starknet -Cairo Language Fundamentals +## Introduction to Smart Contracts -# Cairo Language Fundamentals +Smart contracts are programs deployed on a blockchain, gaining prominence with Ethereum. They are not inherently "smart" or "contracts," but rather code executed based on specific inputs. They consist of storage and functions, and users interact by initiating blockchain transactions. Smart contracts have their own address and can hold tokens. + +### Programming Languages and Compilation -## Keywords and Built-ins +The language varies by blockchain; Solidity is common for EVM, while Cairo is used for Starknet. Compilation differs: Solidity compiles to bytecode, whereas Cairo compiles first to Sierra and then to Cairo Assembly (CASM). -Cairo has several types of keywords and built-in functions: +### Smart Contract Use Cases -### Keywords +Smart contracts facilitate various applications: +* **Tokenization:** Tokenizing real-world assets (real estate, art) to increase liquidity and enable fractional ownership. +* **Voting:** Creating secure, transparent voting systems where results are tallied automatically. +* **Royalties:** Automating royalty payments to creators upon content consumption or sale. +* **Decentralized Identities (DIDs):** Managing digital identities, allowing users control over personal information sharing and access verification. -- **`type`**: Defines a type alias. -- **`use`**: Brings symbols into scope. -- **`while`**: Loops conditionally based on an expression's result. -- **`self`**: Refers to the method subject. -- **`super`**: Refers to the parent module of the current module. +## The Rise of Starknet and Cairo -### Reserved Keywords +Before interacting with contracts (e.g., a voting contract), voter and admin accounts must be registered and funded on Starknet. Account Abstraction details are covered in the Starknet Docs. -These keywords are reserved for future use and should not be used for defining items to ensure forward compatibility: `Self`, `do`, `dyn`, `for`, `hint`, `in`, `macro`, `move`, `static_assert`, `static`, `try`, `typeof`, `unsafe`, `where`, `with`, `yield`. +### Setting up Starkli and Account Preparation -### Built-in Functions +Aside from Scarb, you need **Starkli**, a command-line tool for Starknet interaction. Verify the version matches the required specification: -These functions have special purposes and should not be used as names for other items: +> Note: Please verify that the version of `starkli` match the specified version provided below. +> +> ``` +> $ starkli --version +> 0.3.6 (8d6db8c) +> ``` +> +> To upgrade `starkli` to `0.3.6`, use the `starkliup -v 0.3.6` command, or simply `starkliup` which installed the latest stable version. -- **`assert`**: Checks a boolean expression; triggers `panic` if false. -- **`panic`**: Acknowledges an error and terminates the program. +The smart wallet class hash can be retrieved using the following command, noting the use of the `--rpc` flag pointing to the `katana` RPC endpoint: -## Operators and Symbols +``` +starkli class-hash-at --rpc http://0.0.0.0:5050 +``` -Cairo uses various operators and symbols for different purposes. +### Contract Deployment Workflow -### Operators +Contracts must be declared before deployment using the `starkli declare` command: -Operators have specific explanations and may be overloadable with associated traits: +``` +starkli declare target/dev/listing_99_12_vote_contract_Vote.contract_class.json --rpc http://0.0.0.0:5050 --account katana-0 +``` -| Operator | Example | Explanation | Overloadable? | -| :------- | :------------------------- | :--------------------------------------- | :------------ | -------------------------------- | ------- | --------------------------- | --- | -| `!` | `!expr` | Logical complement | `Not` | -| `~` | `~expr` | Bitwise NOT | `BitNot` | -| `!=` | `expr != expr` | Non-equality comparison | `PartialEq` | -| `%` | `expr % expr` | Arithmetic remainder | `Rem` | -| `%=` | `var %= expr` | Arithmetic remainder and assignment | `RemEq` | -| `&` | `expr & expr` | Bitwise AND | `BitAnd` | -| `&&` | `expr && expr` | Short-circuiting logical AND | | -| `*` | `expr * expr` | Arithmetic multiplication | `Mul` | -| `*=` | `var *= expr` | Arithmetic multiplication and assignment | `MulEq` | -| `@` | `@var` | Snapshot | | -| `*` | `*var` | Desnap | | -| `+` | `expr + expr` | Arithmetic addition | `Add` | -| `+=` | `var += expr` | Arithmetic addition and assignment | `AddEq` | -| `,` | `expr, expr` | Argument and element separator | | -| `-` | `-expr` | Arithmetic negation | `Neg` | -| `-` | `expr - expr` | Arithmetic subtraction | `Sub` | -| `-=` | `var -= expr` | Arithmetic subtraction and assignment | `SubEq` | -| `->` | `fn(...) -> type`, ` | ... | -> type` | Function and closure return type | | -| `.` | `expr.ident` | Member access | | -| `/` | `expr / expr` | Arithmetic division | `Div` | -| `/=` | `var /= expr` | Arithmetic division and assignment | `DivEq` | -| `:` | `pat: type`, `ident: type` | Constraints | | -| `:` | `ident: expr` | Struct field initializer | | -| `;` | `expr;` | Statement and item terminator | | -| `<` | `expr < expr` | Less than comparison | `PartialOrd` | -| `<=` | `expr <= expr` | Less than or equal to comparison | `PartialOrd` | -| `=` | `var = expr` | Assignment | | -| `==` | `expr == expr` | Equality comparison | `PartialEq` | -| `=>` | `pat => expr` | Part of match arm syntax | | -| `>` | `expr > expr` | Greater than comparison | `PartialOrd` | -| `>=` | `expr >= expr` | Greater than or equal to comparison | `PartialOrd` | -| `^` | `expr ^ expr` | Bitwise exclusive OR | `BitXor` | -| ` | ` | `expr | expr` | Bitwise OR | `BitOr` | -| ` | | ` | `expr | | expr` | Short-circuiting logical OR | | -| `?` | `expr?` | Error propagation | | +If a `compiler-version` error occurs due to version mismatch, you can specify the version using the `--compiler-version x.y.z` flag. Upgrading Starkli via `starkliup` is also recommended to resolve such issues. -### Non-Operator Symbols +--- -These symbols have specific meanings when used alone or within paths: +Sources: -| Symbol | Explanation | -| :-------------------------------------- | :---------------------------------------- | -| `..._u8`, `..._usize`, `..._bool`, etc. | Numeric literal of specific type | -| `\"...\"` | String literal | -| `'...'` | Short string, 31 ASCII characters maximum | -| `_` | “Ignored” pattern binding | +- https://www.starknet.io/cairo-book/ch02-05-control-flow.html +- https://www.starknet.io/cairo-book/ch02-02-data-types.html +- https://www.starknet.io/cairo-book/ch02-03-functions.html +- https://www.starknet.io/cairo-book/ch06-02-the-match-control-flow-construct.html +- https://www.starknet.io/cairo-book/ch02-01-variables-and-mutability.html +- https://www.starknet.io/cairo-book/ch11-01-closures.html +- https://www.starknet.io/cairo-book/ch05-01-defining-and-instantiating-structs.html +- https://www.starknet.io/cairo-book/ch12-08-printing.html +- https://www.starknet.io/cairo-book/appendix-03-derivable-traits.html +- https://www.starknet.io/cairo-book/ch05-03-method-syntax.html +- https://www.starknet.io/cairo-book/ch06-01-enums.html +- https://www.starknet.io/cairo-book/ch06-03-concise-control-flow-with-if-let-and-while-let.html +- https://www.starknet.io/cairo-book/ch12-04-hash.html +- https://www.starknet.io/cairo-book/ch03-01-arrays.html +- https://www.starknet.io/cairo-book/ch04-01-what-is-ownership.html +- https://www.starknet.io/cairo-book/ch05-02-an-example-program-using-structs.html +- https://www.starknet.io/cairo-book/ch102-04-serialization-of-cairo-types.html +- https://www.starknet.io/cairo-book/ch01-02-hello-world.html +- https://www.starknet.io/cairo-book/ch02-00-common-programming-concepts.html +- https://www.starknet.io/cairo-book/ch02-04-comments.html +- https://www.starknet.io/cairo-book/ch03-00-common-collections.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html +- https://www.starknet.io/cairo-book/ch05-00-using-structs-to-structure-related-data.html +- https://www.starknet.io/cairo-book/ch06-00-enums-and-pattern-matching.html +- https://www.starknet.io/cairo-book/ch08-00-generic-types-and-traits.html +- https://www.starknet.io/cairo-book/ch08-01-generic-data-types.html +- https://www.starknet.io/cairo-book/ch09-02-recoverable-errors.html +- https://www.starknet.io/cairo-book/ch11-00-functional-features.html -#### Path-Related Syntax +--- -| Symbol | Explanation | -| :------------------- | :--------------------------------------------------------------- | -| `ident::ident` | Namespace path | -| `super::path` | Path relative to the parent of the current module | -| `trait::method(...)` | Disambiguating a method call by naming the trait that defines it | +--- -#### Generic Type Parameter Syntax +Sources: -| Symbol | Explanation | -| :----------------------------- | :----------------------------------------------------------------------------------------------------------- | -| `path<...>` | Specifies parameters to generic type in a type (e.g., `Array`) | -| `path::<...>`, `method::<...>` | Specifies parameters to a generic type, function, or method in an expression; often referred to as turbofish | +- https://www.starknet.io/cairo-book/ch01-02-hello-world.html +- https://www.starknet.io/cairo-book/ch02-00-common-programming-concepts.html +- https://www.starknet.io/cairo-book/ch02-03-functions.html +- https://www.starknet.io/cairo-book/ch02-04-comments.html -## Statements and Expressions +--- -Cairo functions consist of a series of statements, optionally ending in an expression. +## Cairo Program Structure and Fundamentals -- **Statements**: Instructions that perform an action but do not return a value. Example: `let y = 6;`. -- **Expressions**: Evaluate to a resulting value. +### Anatomy of a Cairo Program -Attempting to assign a statement to a variable results in an error, as statements do not return values. +Every executable Cairo program begins execution in the `main` function. -```cairo -#[executable] +The `main` function declaration: +```rust fn main() { - // let x = (let y = 6); // This is an error + } ``` +* The `main` function takes no parameters (if it did, they would be inside `()`). +* The function body is enclosed in curly brackets `{}`. +* It is standard style to place the opening curly bracket on the same line as the function declaration, separated by one space. +* Cairo style dictates using four spaces for indentation. -## Comments +The body of `main` often contains code that performs actions, such as printing to the terminal: +```rust + println!("Hello, World!"); +``` +The `println!` syntax calls a Cairo macro; calling a function would omit the exclamation mark (e.g., `println`). -Comments are ignored by the compiler and used for human readability. +### Statements and Expressions -### Single-line Comments +Cairo is an expression-based language, distinguishing between statements and expressions: -Start with `//` and continue to the end of the line. +* **Statements** are instructions that perform an action but do not return a value. +* **Expressions** evaluate to a resultant value. -```cairo -// This is a single-line comment +Function definitions are statements. Creating a variable assignment using `let` is also a statement, as shown in Listing 2-1: +```rust +#[executable] +fn main() { + let y = 6; +} +``` +Since statements do not return values, attempting to assign a `let` statement to another variable results in an error: +```rust +#[executable] +fn main() { + let x = (let y = 6); +} ``` -### Multi-line Comments +### Comments -Each line must start with `//`. +Comments are ignored by the compiler but aid human readers. -```cairo -// This is a -// multi-line comment +**Standard Comments:** +Idiomatic comments start with two slashes (`//`) and continue until the end of the line. For multi-line comments, `//` must prefix every line: +```rust +// So we’re doing something complicated here, long enough that we need +// multiple lines of comments to do it! Whew! Hopefully, this comment will +// explain what’s going on. ``` - -Comments can also appear at the end of a line of code: - -```cairo +Comments can also appear at the end of lines containing code: +```rust +#[executable] fn main() -> felt252 { 1 + 4 // return the sum of 1 and 4 } ``` -### Item-level Documentation - -Prefixed with `///`, these comments document specific items like functions or traits. They can include descriptions, usage examples, and panic conditions. - -````cairo +**Item-level Documentation:** +These comments refer to specific items (functions, traits, etc.) and are prefixed with three slashes (`///`). They provide detailed descriptions, usage examples, and panic conditions. +```rust /// Returns the sum of `arg1` and `arg2`. /// `arg1` cannot be zero. /// @@ -621,261 +628,239 @@ fn add(arg1: felt252, arg2: felt252) -> felt252 { assert!(arg1 != 0, "Cannot be zero"); arg1 + arg2 } -```` - -## Common Programming Patterns and Potential Vulnerabilities - -Certain programming patterns can lead to unintended behavior if not handled carefully. +``` -### Operator Precedence in Expressions +--- -Ensure expressions involving `&&` and `||` are properly parenthesized to control precedence. +Sources: -```cairo -// ❌ buggy: ctx.coll_ok and ctx.debt_ok are only required in Recovery -assert!( - mode == Mode::None || mode == Mode::Recovery && ctx.coll_ok && ctx.debt_ok, - "EMERGENCY_MODE" -); +- https://www.starknet.io/cairo-book/ch02-01-variables-and-mutability.html +- https://www.starknet.io/cairo-book/ch04-01-what-is-ownership.html -// ✅ fixed -assert!( - (mode == Mode::None || mode == Mode::Recovery) && (ctx.coll_ok && ctx.debt_ok), - "EMERGENCY_MODE" -); -``` +--- -### Unsigned Loop Underflow +### Variables, Mutability, and Scoping -Using unsigned integers (`u32`) for loop counters can lead to underflow panics if decremented below zero. Use signed integers (`i32`) for counters that might handle negative values. +Cairo uses an immutable memory model where once a memory cell is written to, it cannot be overwritten. Variables in Cairo are immutable by default. -```cairo -// ✅ prefer signed counters or explicit break -let mut i: i32 = (n.try_into().unwrap()) - 1; -while i >= 0 { // This would never trigger if `i` was a u32. - // ... - i -= 1; -} -``` +#### Variables and Immutability -### Bit-packing into `felt252` +When a variable is immutable, its bound value cannot be changed. Attempting to reassign a value to an immutable variable results in a compile-time error. -When packing multiple fields into a single `felt252`, ensure the total bit size does not exceed 251 bits and check the bounds of each field before packing. +To demonstrate this, if we use the following code: -```cairo -fn pack_order(book_id: u256, tick_u24: u256, index_u40: u256) -> felt252 { - // width checks - assert!(book_id < (1_u256 * POW_2_187), "BOOK_OVER"); - assert!(tick_u24 < (1_u256 * POW_2_24), "TICK_OVER"); - assert!(index_u40 < (1_u256 * POW_2_40), "INDEX_OVER"); +Filename: src/lib.cairo - let packed: u256 = - (book_id * POW_2_64) + (tick_u24 * POW_2_40) + index_u40; - packed.try_into().expect("PACK_OVERFLOW") +```rust +#[executable] +fn main() { + let x = 5; + println!("The value of x is: {}", x); + x = 6; + println!("The value of x is: {}", x); } ``` -## Hashing +Running the program yields an immutability error: -Cairo supports hashing using Pedersen and Poseidon hash functions. +``` +$ scarb execute + Compiling no_listing_01_variables_are_immutable v0.1.0 (listings/ch02-common-programming-concepts/no_listing_01_variables_are_immutable/Scarb.toml) +error: Cannot assign to an immutable variable. + --> listings/ch02-common-programming-concepts/no_listing_01_variables_are_immutable/src/lib.cairo:7:5 + x = 6; + ^^^^^ -### Initialization and Usage +error: could not compile `no_listing_01_variables_are_immutable` due to previous error +error: `scarb` command exited with error +``` -1. Initialize the hash state: - - Poseidon: `PoseidonTrait::new() -> HashState` - - Pedersen: `PedersenTrait::new(base: felt252) -> HashState` -2. Update the state using `update(self: HashState, value: felt252) -> HashState` or `update_with(self: S, value: T) -> S`. -3. Finalize the hash: `finalize(self: HashState) -> felt252`. +This compile-time check prevents bugs where code relies on a value remaining constant when another part of the code unexpectedly changes it. -### Poseidon Hash Example +#### Making Variables Mutable -```cairo -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; +To allow a variable's value to change, you must add the `mut` keyword in front of the variable name. This signals intent to future readers. Although Cairo's memory is fundamentally immutable, declaring a variable as `mut` allows the value associated with the variable to change. Low-level analysis shows that variable mutation is implemented as syntactic sugar equivalent to variable shadowing, except the variable's type cannot change. -#[derive(Drop, Hash)] -struct StructForHash { - first: felt252, - second: felt252, - third: (u32, u32), - last: bool, -} +Example of using `mut`: +```rust #[executable] -fn main() -> felt252 { - let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; - - let hash = PoseidonTrait::new().update_with(struct_to_hash).finalize(); - hash +fn main() { + let mut x = 5; + println!("The value of x is: {}", x); + x = 6; + println!("The value of x is: {}", x); } ``` -### Pedersen Hash Example +Execution output: -Pedersen hashing requires an initial base state. +``` +$ scarb execute + Compiling no_listing_02_adding_mut v0.1.0 (listings/ch02-common-programming-concepts/no_listing_02_adding_mut/Scarb.toml) + Finished `dev` profile target(s) in 1 second + Executing no_listing_02_adding_mut +The value of x is: 5 +The value of x is: 6 +``` -```cairo -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::pedersen::PedersenTrait; +#### Constants -#[derive(Drop, Hash, Serde, Copy)] -struct StructForHash { - first: felt252, - second: felt252, - third: (u32, u32), - last: bool, -} - -#[executable] -fn main() -> (felt252, felt252) { - let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; - - // hash1 is the result of hashing a struct with a base state of 0 - let hash1 = PedersenTrait::new(0).update_with(struct_to_hash).finalize(); +*Constants* are similar to immutable variables but have key differences: +1. They cannot be declared mutable (`mut` is disallowed). +2. They are declared using the `const` keyword instead of `let`. +3. The type of the value *must* always be annotated. +4. They can only be declared in the global scope. +5. They can only be set to a constant expression, not a value computed at runtime. - let mut serialized_struct: Array = ArrayTrait::new(); - Serde::serialize(@struct_to_hash, ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); +The naming convention for constants is all uppercase with underscores between words. Constants are valid for the entire time the program runs within their scope, making them useful for domain-specific values known throughout the application. - for value in serialized_struct { - state = state.update(value); - } +Example of constants declaration: - // hash2 is the result of hashing only the fields of the struct - let hash2 = state.finalize(); +```rust +struct AnyStruct { + a: u256, + b: u32, +} - (hash1, hash2) +enum AnyEnum { + A: felt252, + B: (usize, u256), } + +const ONE_HOUR_IN_SECONDS: u32 = 3600; +const ONE_HOUR_IN_SECONDS_2: u32 = 60 * 60; +const STRUCT_INSTANCE: AnyStruct = AnyStruct { a: 0, b: 1 }; +const ENUM_INSTANCE: AnyEnum = AnyEnum::A('any enum'); +const BOOL_FIXED_SIZE_ARRAY: [bool; 2] = [true, false]; ``` -## Printing and Formatting +#### Shadowing -Cairo provides macros for printing and string formatting. +Variable shadowing occurs when a new variable is declared with the same name as a previous variable, effectively hiding the previous one. This is done by repeating the `let` keyword. -- `print!` and `println!`: Use the `Display` trait for basic data types. For custom types, you need to implement `Display` or use the `Debug` trait. -- `format!`: Similar to `println!`, but returns a `ByteArray` instead of printing. It's more readable than manual string concatenation and uses snapshots, preventing ownership transfer. +Shadowing allows for transformations on a value while keeping the variable immutable after those steps. Crucially, shadowing allows changing the variable's type, which `mut` does not permit. -```cairo +Example demonstrating shadowing and scope: + +```rust #[executable] fn main() { - let s1: ByteArray = "tic"; - let s2: ByteArray = "tac"; - let s3: ByteArray = "toe"; - let s = s1 + "-" + s2 + "-" + s3; // Consumes s1, s2, s3 - - let s1: ByteArray = "tic"; - let s2: ByteArray = "tac"; - let s3: ByteArray = "toe"; - let s = format!("{s1}-{s2}-{s3}"); // s1, s2, s3 are not consumed - // or - let s = format!("{}-{}-{}", s1, s2, s3); - - println!("{}", s); + let x = 5; + let x = x + 1; + { + let x = x * 2; + println!("Inner scope x value is: {}", x); + } + println!("Outer scope x value is: {}", x); } ``` -### Printing Custom Data Types - -If `Display` is not implemented for a custom type, `print!` or `println!` will result in an error: `Trait has no implementation in context: core::fmt::Display::`. - -## Range Check Builtin - -The Range Check builtin verifies that field elements fall within specific ranges, crucial for integer types and bounded arithmetic. - -### Variants - -- **Standard Range Check**: Verifies values in the range `[0, 2^128 - 1]`. -- **Range Check 96**: Verifies values in the range `[0, 2^96 - 1]`. - -### Purpose and Importance - -While range checking can be implemented in pure Cairo, it's highly inefficient (approx. 384 instructions per check vs. 1.5 instructions for the builtin). This makes the builtin essential for performance. - -### Cells Organization - -The Range Check builtin uses a dedicated memory segment with the following characteristics: +Output: -- **Valid values**: Field elements in the range `[0, 2^128 - 1]`. -- **Error conditions**: Values ≥ 2^128 or relocatable addresses. - -Variables, Scope, and Mutability - -# Variables, Scope, and Mutability - -Cairo enforces an immutable memory model by default, meaning variables are immutable. However, the language provides mechanisms to handle mutability when needed. - -## Mutability +``` +$ scarb execute + Compiling no_listing_03_shadowing v0.1.0 (listings/ch02-common-programming-concepts/no_listing_03_shadowing/Scarb.toml) + Finished `dev` profile target(s) in 1 second + Executing no_listing_03_shadowing +Inner scope x value is: 12 +Outer scope x value is: 6 +``` -By default, variables in Cairo are immutable. Once a value is bound to a name, it cannot be changed. Attempting to reassign a value to an immutable variable results in a compile-time error. +Shadowing allows type changes, unlike `mut`: -```cairo,does_not_compile +```rust #[executable] fn main() { - let x = 5; - println!("The value of x is: {}", x); - x = 6; // This line will cause a compile-time error - println!("The value of x is: {}", x); + let x: u64 = 2; + println!("The value of x is {} of type u64", x); + let x: felt252 = x.into(); // converts x to a felt, type annotation is required. + println!("The value of x is {} of type felt252", x); } ``` -This immutability helps prevent bugs by ensuring that values do not change unexpectedly. However, variables can be made mutable by prefixing them with the `mut` keyword. This signals intent and allows the variable's value to be changed. +If we attempt to change the type using `mut`, a compile-time error occurs because the expected type (`u64`) does not match the assigned type (`u8`): -```cairo +```rust #[executable] fn main() { - let mut x = 5; + let mut x: u64 = 2; println!("The value of x is: {}", x); - x = 6; + x = 5_u8; println!("The value of x is: {}", x); } ``` -When a variable is declared as `mut`, the underlying memory is still immutable, but the variable can be reassigned to refer to a new value. This is implemented as syntactic sugar, equivalent to variable shadowing at a lower level, but without the ability to change the variable's type. +Error output: -## Constants +``` +$ scarb execute + Compiling no_listing_05_mut_cant_change_type v0.1.0 (listings/ch02-common-programming-concepts/no_listing_05_mut_cant_change_type/Scarb.toml) +error: Unexpected argument type. Expected: "core::integer::u64", found: "core::integer::u8". + --> listings/ch02-common-programming-concepts/no_listing_05_mut_cant_change_type/src/lib.cairo:7:9 + x = 5_u8; + ^^^^ -Constants are similar to immutable variables but have key differences: +error: could not compile `no_listing_05_mut_cant_change_type` due to previous error +error: `scarb` command exited with error +``` -- They are always immutable; `mut` cannot be used. -- Declared using `const` instead of `let`. -- Type annotations are required. -- They can only be declared in the global scope. -- They can only be initialized with constant expressions, not runtime values. +#### Variable Scope -Cairo's naming convention for constants is all uppercase with underscores. +A *scope* is the range within a program for which an item is valid. A variable is valid from the point it is declared until it goes out of scope. -```cairo,noplayground -struct AnyStruct { - a: u256, - b: u32, +```rust +//TAG: ignore_fmt +#[executable] +fn main() { + { // s is not valid here, it’s not yet declared + let s = 'hello'; // s is valid from this point forward + // do stuff with s + } // this scope is now over, and s is no longer valid } +``` -enum AnyEnum { - A: felt252, - B: (usize, u256), -} +Listing 4-1: A variable and the scope in which it is valid -const ONE_HOUR_IN_SECONDS: u32 = 3600; -const ONE_HOUR_IN_SECONDS_2: u32 = 60 * 60; -const STRUCT_INSTANCE: AnyStruct = AnyStruct { a: 0, b: 1 }; -const ENUM_INSTANCE: AnyEnum = AnyEnum::A('any enum'); -const BOOL_FIXED_SIZE_ARRAY: [bool; 2] = [true, false]; +##### Moving Values + +*Moving* a value means passing it to another function. When this happens, the variable referring to that value in the original scope is destroyed and can no longer be used; a new variable is created to hold the value. Complex types, like Arrays, are moved when passed to another function. + +Array example: + +```rust +#[executable] +fn main() { + let mut arr: Array = array![]; + arr.append(1); + arr.append(2); +} ``` -Constants are useful for values that are known at compile time and used across multiple parts of the program. +--- + +Sources: -## Shadowing +- https://www.starknet.io/cairo-book/ch02-02-data-types.html -Shadowing occurs when a new variable is declared with the same name as a previous variable. The new variable +--- + +# Data Types: Scalars and Primitives +... +## Felt Type +... +### Unsigned Integers +... +This seems the safest interpretation of the conflicting requirements (explicit syntax vs relative depth based on ToC context). -Data Types +The chunks start with `## Data Types - The Cairo Programming Language\n\n# Data Types\n\n...`. I must ignore this initial context and start with the required heading derived from the TOC path. -# Data Types +```markdown +# Data Types: Scalars and Primitives -Every value in Cairo is of a certain _data type_, which tells Cairo what kind of data is being specified so it knows how to work with that data. Cairo is a _statically typed_ language, meaning that the compiler must know the types of all variables at compile time. The compiler can usually infer the desired type based on the value and its usage. In cases where many types are possible, a conversion method can be used to specify the desired output type. +Cairo is a *statically typed* language, which means that it must know the types of all variables at compile time. The compiler can usually infer the desired type based on the value and its usage. In cases when many types are possible, a conversion method can specify the desired output type: -```cairo +```rust #[executable] fn main() { let x: felt252 = 3; @@ -883,56 +868,58 @@ fn main() { } ``` -## Scalar Types +Scalar types represent a single value. Cairo has three primary scalar types: felts, integers, and booleans. -A _scalar_ type represents a single value. Cairo has three primary scalar types: `felt252`, integers, and booleans. +## Felt Type (`felt252`) -### Felt Type (`felt252`) +If the type of a variable or argument is not specified, it defaults to `felt252` (a field element). This is an integer in the range \\( 0 \\leq x < P \\), where \\( P = {2^{251}} + 17 \\cdot {2^{192}} + 1 \\). Results outside this range are computed $\\mod P$. -In Cairo, if the type of a variable or argument is not specified, it defaults to a field element, represented by the keyword `felt252`. A field element is an integer in the range $0 \leq x < P$, where $P$ is a large prime number ($2^{251} + 17 \cdot 2^{192} + 1$). Operations like addition, subtraction, and multiplication are performed modulo $P$. Division in Cairo is defined such that $\frac{x}{y} \cdot y = x$ always holds. +The most important difference from integer division is that Cairo division is defined to always satisfy \\( \\frac{x}{y} \\cdot y == x \\). For instance, \\( \\frac{1}{2} \\) results in \\( \\frac{P + 1}{2} \\). -### Integer Types +## Integer Types -Integer types are recommended over `felt252` for added security features like overflow and underflow checks. An integer is a number without a fractional component. The size of the integer is indicated by the number of bits used for storage. +It is highly recommended to use integer types over `felt252` due to added security features against vulnerabilities like overflow/underflow. An integer type declares the number of bits used for storage. -**Unsigned Integer Types:** +### Unsigned Integers -| Length | Type | -| ------- | ------- | -| 8-bit | `u8` | -| 16-bit | `u16` | -| 32-bit | `u32` | -| 64-bit | `u64` | -| 128-bit | `u128` | -| 256-bit | `u256` | -| 32-bit | `usize` | +Built-in unsigned integer types: -`usize` is currently an alias for `u32`. Unsigned integers cannot contain negative numbers, and operations resulting in a negative value will cause a panic. +| Length | Unsigned | +| :--- | :--- | +| 8-bit | `u8` | +| 16-bit | `u16` | +| 32-bit | `u32` | +| 64-bit | `u64` | +| 128-bit | `u128` | +| 256-bit | `u256` | +| 32-bit | `usize` | -**Signed Integer Types:** +`usize` is currently an alias for `u32`. Since variables are unsigned, attempting to create a negative result causes a panic: -Cairo also provides signed integers with the prefix `i`, ranging from `i8` to `i128`. Each signed variant can store numbers from $-(2^{n-1})$ to $2^{n-1} - 1$, where `n` is the number of bits. +```rust +fn sub_u8s(x: u8, y: u8) -> u8 { + x - y +} -**Integer Literals:** +#[executable] +fn main() { + sub_u8s(1, 3); +} +``` -Integer literals can be represented in decimal, hexadecimal, octal, or binary formats. Type suffixes (e.g., `57_u8`) can be used for explicit type designation. Visual separators (`_`) improve readability. +`u256` is structured as a struct: `u256 {low: u128, high: u128}`. -| Numeric literals | Example | -| ---------------- | --------- | -| Decimal | `98222` | -| Hex | `0xff` | -| Octal | `0o04321` | -| Binary | `0b01` | +### Signed Integers -**`u256` Type:** +Signed integers start with the prefix `i` (e.g., `i8` to `i128`). They store numbers from \\( -({2^{n - 1}}) \\) to \\( {2^{n - 1}} - 1 \\). -`u256` requires 4 more bits than `felt252` and is internally represented as a struct: `u256 { low: u128, high: u128 }`. +### Integer Literals and Numeric Operations -**Numeric Operations:** +Integer literals can be written in Decimal, Hex (`0xff`), Octal (`0o04321`), or Binary (`0b01`). Type suffixes (e.g., `57_u8`) and visual separators (`_`) are supported. -Cairo supports basic arithmetic operations: addition, subtraction, multiplication, division (truncates towards zero), and remainder. +Cairo supports basic mathematical operations. Integer division truncates toward zero. -```cairo +```rust #[executable] fn main() { // addition @@ -953,239 +940,312 @@ fn main() { } ``` -### The Boolean Type (`bool`) +## Boolean Type -A Boolean type has two possible values: `true` and `false`. It is one `felt252` in size. Boolean values must be declared using `true` or `false` literals, not integer literals. +The `bool` type has two values: `true` and `false`, and is one `felt252` in size. Declaration mandates using these literals; integer literals are disallowed. -```cairo +```rust #[executable] fn main() { let t = true; + let f: bool = false; // with explicit type annotation } ``` -### String Types +## String Types -Cairo handles strings using two methods: short strings with simple quotes and `ByteArray` with double quotes. +Cairo handles strings using short strings (simple quotes) or `ByteArray` (double quotes). -#### Short strings +### Short Strings -Short strings are ASCII strings where each character is encoded on one byte. They are represented using `felt252` and are limited to 31 characters. They can be represented as hexadecimal values or directly using simple quotes. +Short strings are ASCII strings encoded on one byte per character, stored in a `felt252`. A short string is limited to 31 characters. -```cairo -# #[executable] +* `'a'` is equivalent to `0x61`. +* `0x616263` is equivalent to `'abc'`. + +Examples of declaration: + +```rust +#[executable] fn main() { let my_first_char = 'C'; let my_first_char_in_hex = 0x43; let my_first_string = 'Hello world'; -# let my_first_string_in_hex = 0x48656C6C6F20776F726C64; -# -# let long_string: ByteArray = "this is a string which has more than 31 characters"; -# } + let my_first_string_in_hex = 0x48656C6C6F20776F726C64; + + let long_string: ByteArray = "this is a string which has more than 31 characters"; +} ``` +This covers all points concisely using H2/H3 subheadings relative to the H1 section start. -#### Byte Array Strings +--- -For strings longer than 31 characters, Cairo's Core Library provides a `ByteArray` type. It is implemented as an array of `bytes31` words and a pending `felt252` word for remaining bytes. +Sources: -```cairo -# #[executable] -# fn main() { -# let my_first_char = 'C'; -# let my_first_char_in_hex = 0x43; -# -# let my_first_string = 'Hello world'; -# let my_first_string_in_hex = 0x48656C6C6F20776F726C64; +- https://www.starknet.io/cairo-book/ch02-02-data-types.html +- https://www.starknet.io/cairo-book/ch03-01-arrays.html +- https://www.starknet.io/cairo-book/ch03-00-common-collections.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html -# let long_string: ByteArray = "this is a string which has more than 31 characters"; -# } -``` +--- -## Compound Types +## Byte Array Strings -### The Tuple Type +Cairo's Core Library provides a `ByteArray` type for handling strings and byte sequences longer than short strings. It is implemented using an array of `bytes31` words and a pending `felt252` word acting as a buffer. -A tuple groups together values of various types into a single compound type. Tuples have a fixed length. +Unlike short strings, `ByteArray` strings can contain more than 31 characters and are written using double quotes: ```cairo #[executable] fn main() { - let tup: (u32, f64, u8) = (500, 6.4, 1); - let (x, y, z) = tup; - println!("The value of y is {}", y); + let my_first_char = 'C'; + let my_first_char_in_hex = 0x43; + + let my_first_string = 'Hello world'; + let my_first_string_in_hex = 0x48656C6C6F20776F726C64; + + let long_string: ByteArray = "this is a string which has more than 31 characters"; } ``` -## The `Copy` Trait - -The `Copy` trait allows simple types to be duplicated by copying, without allocating new memory. This bypasses Cairo's default "move" semantics. Types like `Array` and `Felt252Dict` cannot implement `Copy`. Basic types implement `Copy` by default. +## The Tuple Type -To implement `Copy` for a custom type, use the `#[derive(Copy)]` annotation. The type and all its components must implement `Copy`. +A *tuple* groups together values of potentially varying types into one compound type. Tuples have a fixed length. -```cairo,ignore_format -#[derive(Copy, Drop)] -struct Point { - x: u128, - y: u128, -} +Tuples are created using a comma-separated list of values inside parentheses. Individual values can be accessed by *destructuring* the tuple using a pattern with `let`: +```cairo #[executable] fn main() { - let p1 = Point { x: 5, y: 10 }; - foo(p1); - foo(p1); -} - -fn foo(p: Point) { // do something with p + let tup: (u32, u64, bool) = (10, 20, true); } ``` -## Serialization and Deserialization - -Serialization is the process of transforming data structures into a format that can be stored or transmitted. Deserialization is the reverse process. - -### Data types using at most 252 bits - -These types (`ContractAddress`, `EthAddress`, `StorageAddress`, `ClassHash`, unsigned integers up to 252 bits, `bytes31`, `felt252`, signed integers) are serialized as a single-member list containing one `felt252` value. Negative values are serialized as $P-x$. - -### Data types using more than 252 bits +```cairo +#[executable] +fn main() { + let tup = (500, 6, true); -Types like `u256`, `u512`, arrays, spans, enums, structs, tuples, and byte arrays have non-trivial serialization. + let (x, y, z) = tup; -**Serialization of Structs:** + if y == 6 { + println!("y is 6!"); + } +} +``` -Struct serialization is determined by the order and types of its members. +Destructuring can also happen during declaration: -```cairo,noplayground -struct MyStruct { - a: u256, - b: felt252, - c: Array +```cairo +#[executable] +fn main() { + let (x, y): (felt252, felt252) = (2, 3); } ``` -Serialization of `MyStruct { a: 2, b: 5, c: [1,2,3] }` results in `[2,0,5,3,1,2,3]`. +### The Unit Type () -**Serialization of Byte Arrays:** +A *unit type* has only one value `()`, represented by an empty tuple. It has zero size and is guaranteed not to exist in compiled code. It is the implicit return value for expressions that return nothing. -A `ByteArray` consists of `data` (an array of 31-byte chunks) and `pending_word` (remaining bytes) with its length. +## The Fixed Size Array Type -**Example 1 (short string):** `hello` (0x68656c6c6f) +Fixed size arrays store multiple values where every element must have the same type. They are written as a comma-separated list inside square brackets. The type is specified as `[ElementType; Length]`. -```cairo,noplayground -0, // Number of 31-byte words in the data array. -0x68656c6c6f, // Pending word -5 // Length of the pending word, in bytes +```cairo +#[executable] +fn main() { + let arr1: [u64; 5] = [1, 2, 3, 4, 5]; +} ``` -## Type Conversion +Arrays are efficient because their size is known at compile-time. They can be initialized concisely using `[initial_value; length]`: -Cairo uses the `try_into` and `into` methods from the `TryInto` and `Into` traits for type conversion. +```cairo + let a = [3; 5]; +``` -### Into +### Accessing Fixed Size Arrays Elements -The `Into` trait is used for fallible conversions where success is guaranteed. Call `var.into()` on the source value. The new variable's type must be explicitly defined. +Elements can be accessed by deconstructing the array: ```cairo #[executable] fn main() { - let my_u8: u8 = 10; - let my_u16: u16 = my_u8.into(); - let my_u32: u32 = my_u16.into(); - let my_u64: u64 = my_u32.into(); - let my_u128: u128 = my_u64.into(); + let my_arr = [1, 2, 3, 4, 5]; - let my_felt252 = 10; - let my_u256: u256 = my_felt252.into(); - let my_other_felt252: felt252 = my_u8.into(); - let my_third_felt252: felt252 = my_u16.into(); + // Accessing elements of a fixed-size array by deconstruction + let [a, b, c, _, _] = my_arr; + println!("c: {}", c); // c: 3 } ``` -### TryInto +## Common Collections -The `TryInto` trait is used for fallible conversions, returning `Option`. Call `var.try_into()` on the source value. The new variable's type must be explicitly defined. +Cairo provides common collection types, primarily Arrays and Dictionaries. + +### Array Operations + +Accessing elements can be done using the subscripting operator or the `at()` method, which both panic if the index is out of bounds (by using `unbox()`): ```cairo #[executable] fn main() { - let my_u256: u256 = 10; - let my_felt252: felt252 = my_u256.try_into().unwrap(); - let my_u128: u128 = my_felt252.try_into().unwrap(); - let my_u64: u64 = my_u128.try_into().unwrap(); - let my_u32: u32 = my_u64.try_into().unwrap(); - let my_16: u16 = my_u32.try_into().unwrap(); - let my_u8: u8 = my_16.try_into().unwrap(); + let mut a = ArrayTrait::new(); + a.append(0); + a.append(1); - let my_large_u16: u16 = 2048; - // This will panic: - // let my_large_u8: u8 = my_large_u16.try_into().unwrap(); + // using the `at()` method + let first = *a.at(0); + assert!(first == 0); + // using the subscripting operator + let second = *a[1]; + assert!(second == 1); } ``` -## Debugging with `Debug` and `Display` Traits +Use `get()` for graceful handling of out-of-bounds access. To determine size, use `len()` (returns `usize`), and check if empty with `is_empty()`. -### `Debug` for Debugging Purposes +The `array!` macro simplifies compile-time array creation: -The `Debug` trait allows printing instances of a type for debugging. It is required for `assert_xx!` macros in tests. +Without `array!`: ```cairo -#[derive(Copy, Drop, Debug)] -struct Point { - x: u8, - y: u8, -} - -#[executable] -fn main() { - let p = Point { x: 1, y: 3 }; - println!("{:?}", p); -} + let mut arr = ArrayTrait::new(); + arr.append(1); + arr.append(2); + arr.append(3); + arr.append(4); + arr.append(5); ``` -### `Display` for User-Facing Output - -The `Display` trait enables formatting output for direct end-user consumption using the `{}` placeholder. It's not automatically derived for structs due to ambiguity in formatting possibilities. +With `array!`: ```cairo -use core::fmt::{Display, Error, Formatter}; + let arr = array![1, 2, 3, 4, 5]; +``` -#[derive(Copy, Drop)] -struct Point { - x: u8, - y: u8, -} +To store multiple types in an array, an `Enum` must be used to define a custom data type: -impl PointDisplay of Display { - fn fmt(self: @Point, ref f: Formatter) -> Result<(), Error> { - let x = *self.x; - let y = *self.y; - writeln!(f, "Point ({x}, {y})") - } +```cairo +#[derive(Copy, Drop)] +enum Data { + Integer: u128, + Felt: felt252, + Tuple: (u32, u32), } #[executable] fn main() { - let p = Point { x: 1, y: 3 }; - println!("{}", p); // Output: Point: (1, 3) + let mut messages: Array = array![]; + messages.append(Data::Integer(100)); + messages.append(Data::Felt('hello world')); + messages.append(Data::Tuple((10, 30))); } ``` -### Print in Hexadecimal - -Integer values can be printed in hexadecimal using the `{:x}` notation. The `LowerHex` trait is implemented for common types. - -### Print Debug Traces - -This refers to using the `Debug` trait for printing detailed information, especially during debugging. +### Span -## `Default` for Default Values +`Span` is a struct representing a snapshot of an `Array`, providing safe, read-only access without modification. All `Array` methods, except `append()`, can be used with `Span`. -The `Default` trait allows the creation of a default value for a type, commonly zero. All primitive types implement `Default`. For composite types, all elements must implement `Default`. For enums, a default variant must be declared with `#[default]`. +To create a `Span` from an `Array`, call the `span()` method: ```cairo +#[executable] +fn main() { + let mut array: Array = ArrayTrait::new(); + array.span(); +} +``` + +## Dictionaries + +The `Felt252Dict` data type represents key-value pairs where the key type is restricted to `felt252`. This structure is useful for organizing data where array indexing is insufficient, and it can simulate mutable memory. + +The core functionality is implemented in `Felt252DictTrait`, including `insert(felt252, T)` and `get(felt252) -> T`. + +```cairo +use core::dict::Felt252Dict; + +#[executable] +fn main() { + let mut balances: Felt252Dict = Default::default(); + + balances.insert('Alex', 100); + balances.insert('Maria', 200); + + let alex_balance = balances.get('Alex'); + assert!(alex_balance == 100, "Balance is not 100"); + + let maria_balance = balances.get('Maria'); + assert!(maria_balance == 200, "Balance is not 200"); +} +``` + +--- + +Sources: + +- https://www.starknet.io/cairo-book/appendix-03-derivable-traits.html +- https://www.starknet.io/cairo-book/ch02-02-data-types.html +- https://www.starknet.io/cairo-book/ch05-02-an-example-program-using-structs.html + +--- + +### Type System Utilities: Traits and Conversions + +#### Copy Trait + +The `Copy` trait allows for the duplication of values. It can be derived on any type whose parts all implement `Copy`. + +Example: +```rust +#[derive(Copy, Drop)] +struct A { + item: felt252, +} + +#[executable] +fn main() { + let first_struct = A { item: 2 }; + let second_struct = first_struct; + // Copy Trait prevents first_struct from moving into second_struct + assert!(second_struct.item == 2, "Not equal"); + assert!(first_struct.item == 2, "Not Equal"); +} +``` + +#### Debug for Printing and Debugging + +The `Debug` trait enables debug formatting in format strings, indicated by adding `:?` within `{}` placeholders. It allows printing instances for inspection. The `Debug` trait is required when using `assert_xx!` macros in tests to print failing values. + +Example: +```rust +#[derive(Copy, Drop, Debug)] +struct Point { + x: u8, + y: u8, +} + +#[executable] +fn main() { + let p = Point { x: 1, y: 3 }; + println!("{:?}", p); +} +``` +Output from running this example: +``` +scarb execute +Point { x: 1, y: 3 } +``` + +#### Default for Default Values + +The `Default` trait allows creation of a default value of a type, most commonly zero for primitive types. If deriving `Default` on a composite type, all its elements must implement `Default`. For an `enum`, the default value must be declared using the `#[default]` attribute on one of its variants. + +Example: +```rust #[derive(Default, Drop)] struct A { item1: felt252, @@ -1211,15 +1271,192 @@ fn main() { } ``` -## `PartialEq` for Equality Comparisons +#### PartialEq for Equality Comparisons + +The `PartialEq` trait allows comparison between instances using `==` and `!=`. When derived, two instances are equal only if all their fields are equal (for structs), or if they are the same variant (for enums). It is required for the `assert_eq!` macro in tests. Custom implementations are possible; for instance, two rectangles can be considered equal if they have the same area. + +Example of custom implementation: +```rust +#[derive(Copy, Drop)] +struct Rectangle { + width: u64, + height: u64, +} + +impl PartialEqImpl of PartialEq { + fn eq(lhs: @Rectangle, rhs: @Rectangle) -> bool { + (*lhs.width) * (*lhs.height) == (*rhs.width) * (*rhs.height) + } + + fn ne(lhs: @Rectangle, rhs: @Rectangle) -> bool { + (*lhs.width) * (*lhs.height) != (*rhs.width) * (*rhs.height) + } +} + +#[executable] +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + let rect2 = Rectangle { width: 50, height: 30 }; + + println!("Are rect1 and rect2 equal? {}", rect1 == rect2); +} +``` + +Example of derived usage: +```rust +#[derive(PartialEq, Drop)] +struct A { + item: felt252, +} + +#[executable] +fn main() { + let first_struct = A { item: 2 }; + let second_struct = A { item: 2 }; + assert!(first_struct == second_struct, "Structs are different"); +} +``` + +#### Type Conversion + +Cairo uses the `TryInto` and `Into` traits for type conversion. + +##### Into + +The `Into` trait is used for infallible conversions, typically when the source type is smaller than the destination type. Conversion is done via `var.into()`, and the new variable's type must be explicitly defined. + +Example with built-in types: +```rust +#[executable] +fn main() { + let my_u8: u8 = 10; + let my_u16: u16 = my_u8.into(); + let my_u32: u32 = my_u16.into(); + let my_u64: u64 = my_u32.into(); + let my_u128: u128 = my_u64.into(); + + let my_felt252 = 10; + // As a felt252 is smaller than a u256, we can use the into() method + let my_u256: u256 = my_felt252.into(); + let my_other_felt252: felt252 = my_u8.into(); + let my_third_felt252: felt252 = my_u16.into(); +} +``` + +Implementing `Into` for custom types: +```rust +#[derive(Drop, PartialEq)] +struct Rectangle { + width: u64, + height: u64, +} + +#[derive(Drop)] +struct Square { + side_length: u64, +} + +impl SquareIntoRectangle of Into { + fn into(self: Square) -> Rectangle { + Rectangle { width: self.side_length, height: self.side_length } + } +} + +#[executable] +fn main() { + let square = Square { side_length: 5 }; + // Compiler will complain if you remove the type annotation + let result: Rectangle = square.into(); + let expected = Rectangle { width: 5, height: 5 }; + assert!( + result == expected, + "A square is always convertible to a rectangle with the same width and height!", + ); +} +``` + +##### TryInto + +The `TryInto` trait is used for fallible conversions (e.g., when the target type might not fit the source value), returning `Option`. Conversion is performed via `var.try_into()`. + +Example with built-in types: +```rust +#[executable] +fn main() { + let my_u256: u256 = 10; + + // Since a u256 might not fit in a felt252, we need to unwrap the Option type + let my_felt252: felt252 = my_u256.try_into().unwrap(); + let my_u128: u128 = my_felt252.try_into().unwrap(); + let my_u64: u64 = my_u128.try_into().unwrap(); + let my_u32: u32 = my_u64.try_into().unwrap(); + let my_u16: u16 = my_u32.try_into().unwrap(); + let my_u8: u8 = my_u16.try_into().unwrap(); + + let my_large_u16: u16 = 2048; + let my_large_u8: u8 = my_large_u16.try_into().unwrap(); // panics with 'Option::unwrap failed.' +} +``` + +Implementing `TryInto` for custom types: +```rust +#[derive(Drop)] +struct Rectangle { + width: u64, + height: u64, +} + +#[derive(Drop, PartialEq)] +struct Square { + side_length: u64, +} + +impl RectangleIntoSquare of TryInto { + fn try_into(self: Rectangle) -> Option { + if self.height == self.width { + Some(Square { side_length: self.height }) + } else { + None + } + } +} + +#[executable] +fn main() { + let rectangle = Rectangle { width: 8, height: 8 }; + let result: Square = rectangle.try_into().unwrap(); + let expected = Square { side_length: 8 }; + assert!( + result == expected, + "Rectangle with equal width and height should be convertible to a square.", + ); + + let rectangle = Rectangle { width: 5, height: 8 }; + let result: Option = rectangle.try_into(); + assert!( + result.is_none(), + "Rectangle with different width and height should not be convertible to a square.", + ); +} +``` + +--- + +Sources: + +- https://www.starknet.io/cairo-book/ch02-03-functions.html +- https://www.starknet.io/cairo-book/ch04-01-what-is-ownership.html +- https://www.starknet.io/cairo-book/ch08-00-generic-types-and-traits.html + +--- -The `PartialEq` trait enables equality comparisons using the `==` and `!=` operators. +## Functions: Definition and Execution -Functions +Cairo code conventionally uses *snake case* for function and variable names (lowercase with underscores separating words). -# Functions +### Function Definition and Execution -Functions are a fundamental part of Cairo code. The `main` function serves as the entry point for many programs, and the `fn` keyword is used to declare new functions. Cairo conventionally uses snake case for function and variable names. +Functions are declared using the `fn` keyword, followed by the name, parentheses, and curly brackets for the body: ```cairo fn another_function() { @@ -1233,11 +1470,13 @@ fn main() { } ``` -Functions are defined using `fn` followed by the function name, parentheses, and a body enclosed in curly brackets. Functions can be called by their name followed by parentheses. The order of definition does not matter as long as they are in a visible scope. +Functions are called by their name followed by parentheses. The definition order does not matter as long as the function is defined within the scope of the caller. -## Parameters +### Parameters -Functions can accept parameters, which are variables declared in the function's signature. Arguments are the concrete values passed to parameters when a function is called. +Functions can accept *parameters*, which are variables in the function signature. The concrete values passed are called *arguments*. Parameter types must be declared in the signature. + +Example with a single parameter: ```cairo #[executable] @@ -1250,8 +1489,6 @@ fn another_function(x: felt252) { } ``` -Parameters must have their types declared in the function signature. This aids the compiler in providing better error messages and reduces the need for type annotations elsewhere. - Multiple parameters are separated by commas: ```cairo @@ -1265,9 +1502,11 @@ fn print_labeled_measurement(value: u128, unit_label: ByteArray) { } ``` -### Named Parameters +`ByteArray` is Cairo's internal type for string literals. -Named parameters allow specifying argument names during function calls for improved readability: +#### Named Parameters + +Named parameters allow specifying arguments by name using the syntax `parameter_name: value`. If the variable name matches the parameter name, shorthand `:parameter_name` can be used: ```cairo fn foo(x: u8, y: u8) {} @@ -1283,11 +1522,11 @@ fn main() { } ``` -## Statements and Expressions +### Statements and Expressions -Statements, like `let y = 6`, do not return a value. Expressions, such as `5 + 6`, evaluate to a value. Function calls and code blocks enclosed in curly brackets are also expressions. +Function bodies consist of a series of statements optionally ending in an expression. Statements (like `let y = 6;`) do not return a value, whereas expressions (like `5 + 6`) evaluate to a value. Function calls are expressions that evaluate to the function's return value or `()` (the unit type) if no value is returned. -A code block can be an expression: +A new scope block created with curly brackets is an expression, provided the last line does not end with a semicolon: ```cairo #[executable] @@ -1301,11 +1540,11 @@ fn main() { } ``` -In this example, the block evaluates to `4`, which is then bound to `y`. Expressions do not end with a semicolon; adding one turns them into statements. +Adding a semicolon to the last line of a block turns it into a statement, making it return `()`, which can cause errors if a return value is expected. -## Functions with Return Values +### Functions with Return Values -Functions can return values to the caller. The return type is specified after an arrow (`->`), and the value of the last expression in the function body is implicitly returned. The `return` keyword can be used for early returns. +Functions return values implicitly as the value of the final expression in the body, or explicitly using the `return` keyword. The return type is specified after an arrow (`->`): ```cairo fn five() -> u32 { @@ -1319,66 +1558,97 @@ fn main() { } ``` -The `five` function returns `5` as a `u32`. +If a function is declared to return a type (e.g., `u32`), the final expression must evaluate to that type. If it ends in a semicolon (becoming a statement), it returns `()`, causing an error if `u32` was expected. + +#### Const Functions + +Functions marked with `const fn` can be evaluated at compile time, restricting their body and types to constant expressions. This allows their use in constant contexts: ```cairo -#[executable] -fn main() { - let x = plus_one(5); +use core::num::traits::Pow; - println!("The value of x is: {}", x); -} +const BYTE_MASK: u16 = 2_u16.pow(8) - 1; -fn plus_one(x: u32) -> u32 { - x + 1 +#[executable] +fn main() { + let my_value = 12345; + let first_byte = my_value & BYTE_MASK; + println!("first_byte: {}", first_byte); } ``` -Adding a semicolon to the last expression in a function that's supposed to return a value will cause a compilation error, as it turns the expression into a statement, which returns the unit type `()`. +#### Returning Multiple Values -```cairo,does_not_compile +Cairo supports returning multiple values using a tuple. However, to avoid moving values passed into a function when they are needed afterwards, Cairo offers *references* and *snapshots*. + +Example returning multiple values via a tuple: + +```cairo #[executable] fn main() { - let x = plus_one(5); + let arr1: Array = array![]; - println!("The value of x is: {}", x); + let (arr2, len) = calculate_length(arr1); } -fn plus_one(x: u32) -> u32 { - x + 1; // This semicolon causes an error +fn calculate_length(arr: Array) -> (Array, usize) { + let length = arr.len(); // len() returns the length of an array + + (arr, length) } ``` -### Const Functions - -Functions evaluable at compile time can be declared with `const fn`. This restricts the types and expressions allowed within the function body. +Functions can also accept parameters by reference (using `ref`) to avoid moving the value out of the caller's scope, which is useful when the function needs to read the value but not consume it, or when abstracting logic over types like arrays: ```cairo -use core::num::traits::Pow; +fn largest(ref number_list: Array) -> u8 { + let mut largest = number_list.pop_front().unwrap(); -const BYTE_MASK: u16 = 2_u16.pow(8) - 1; + for number in number_list.span() { + if *number > largest { + largest = *number; + } + } + + largest +} #[executable] fn main() { - let my_value = 12345; - let first_byte = my_value & BYTE_MASK; - println!("first_byte: {}", first_byte); + let mut number_list = array![34, 50, 25, 100, 65]; + + let result = largest(ref number_list); + println!("The largest number is {}", result); + + let mut number_list = array![102, 34, 255, 89, 54, 2, 43, 8]; + + let result = largest(ref number_list); + println!("The largest number is {}", result); } ``` -The `pow` function, marked as `const`, can be used in constant expressions. +--- + +Sources: -Control Flow +- https://www.starknet.io/cairo-book/ch02-05-control-flow.html +- https://www.starknet.io/cairo-book/ch06-03-concise-control-flow-with-if-let-and-while-let.html + +--- # Control Flow -The ability to run some code depending on whether a condition is true and to run some code repeatedly while a condition is true are basic building blocks in most programming languages. The most common constructs that let you control the flow of execution of Cairo code are if expressions and loops. +Control flow allows running code based on conditions or repeating execution. The most common constructs in Cairo are `if` expressions and loops. ## `if` Expressions -An `if` expression allows you to branch your code depending on conditions. You provide a condition and then state, “If this condition is met, run this block of code. If the condition is not met, do not run this block of code.” +An `if` expression branches code execution based on conditions. -```cairo +### Basic Structure and Alternatives + +An `if` expression starts with the keyword `if`, followed by a condition in curly brackets. An optional `else` expression provides code to run if the condition is false. If no `else` is provided, execution continues past the `if` block if the condition is false. + +```rust #[executable] fn main() { let number = 3; @@ -1391,9 +1661,35 @@ fn main() { } ``` -All `if` expressions start with the keyword `if`, followed by a condition. The condition must be a `bool`. Optionally, an `else` expression can be included to provide an alternative block of code to execute should the condition evaluate to `false`. If no `else` expression is provided and the condition is `false`, the program skips the `if` block. +Running with `number = 3` yields: +``` +$ scarb execute + Compiling no_listing_24_if v0.1.0 (listings/ch02-common-programming-concepts/no_listing_27_if/Scarb.toml) + Finished `dev` profile target(s) in 1 second + Executing no_listing_24_if +condition was false and number = 3 +``` + +### Condition Requirements and Errors -```cairo +The condition in an `if` expression must evaluate to a `bool`. Cairo does not automatically convert non-Boolean types. + +Attempting to use a non-Boolean value (like an integer) directly as a condition results in an error: + +```rust +#[executable] +fn main() { + let number = 3; + + if number { + println!("number was three"); + } +} +``` + +This results in an error because Cairo infers the type should be `bool` but cannot create a `bool` from the numeric literal `3`. To check a number's value, explicit comparison is needed: + +```rust #[executable] fn main() { let number = 3; @@ -1406,9 +1702,9 @@ fn main() { ### Handling Multiple Conditions with `else if` -You can use multiple conditions by combining `if` and `else` in an `else if` expression. Cairo checks each `if` expression in turn and executes the first body for which the condition evaluates to `true`. +Multiple conditions can be chained using `else if`. Cairo executes the block for the first condition that evaluates to `true` and skips the rest. -```cairo +```rust #[executable] fn main() { let number = 3; @@ -1425,11 +1721,20 @@ fn main() { } ``` +Output when `number = 3`: +``` +$ scarb execute + Compiling no_listing_25_else_if v0.1.0 (listings/ch02-common-programming-concepts/no_listing_30_else_if/Scarb.toml) + Finished `dev` profile target(s) in 1 second + Executing no_listing_25_else_if +number is 3 +``` + ### Using `if` in a `let` Statement -Because `if` is an expression, it can be used on the right side of a `let` statement to assign the outcome to a variable. +Because `if` is an expression, its result can be assigned to a variable using `let`. -```cairo +```rust #[executable] fn main() { let condition = true; @@ -1447,16 +1752,30 @@ fn main() { ## Repetition with Loops -Cairo provides `loop`, `while`, and `for` loops for executing code blocks repeatedly. +Cairo provides three loop constructs: `loop`, `while`, and `for`. ### Repeating Code with `loop` -The `loop` keyword executes a block of code over and over again until explicitly stopped with `break` or until the program runs out of gas. +The `loop` keyword runs a block of code repeatedly until explicitly stopped using `break`. -```cairo +```rust #[executable] fn main() { - let mut i: usize = 0; + loop { + println!("again!"); + } +} +``` + +Infinite loops are prevented by a gas meter. If gas runs out, the program stops. To run long loops, use `--available-gas`. + +The `break` keyword exits the loop. The `continue` keyword skips the rest of the current iteration and proceeds to the next one. + +Example using `break` and `continue`: +```rust +#[executable] +fn main() { + let mut i: usize = 0; loop { if i > 10 { break; @@ -1470,12 +1789,13 @@ fn main() { } } ``` +Executing this program skips printing when `i` equals 5. -#### Returning Values from Loops +### Returning Values from Loops -A `loop` can return a value by specifying it after the `break` expression. +A value can be returned from a `loop` by placing the value after the `break` keyword. -```cairo +```rust #[executable] fn main() { let mut counter = 0; @@ -1490,12 +1810,13 @@ fn main() { println!("The result is {}", result); } ``` +This prints: `The result is 20`. ### Conditional Loops with `while` -A `while` loop runs a block of code as long as a condition remains `true`. +A `while` loop executes code as long as its condition remains `true`. -```cairo +```rust #[executable] fn main() { let mut number = 3; @@ -1511,9 +1832,11 @@ fn main() { ### Looping Through a Collection with `for` -The `for` loop provides a concise and safe way to iterate over the elements of a collection. +Using a `while` loop to iterate over collections by index (`while index < array_length`) is error-prone and slow due to runtime bounds checking. -```cairo +The `for` loop is a safer and more concise alternative for iterating over collection elements. + +```rust #[executable] fn main() { let a = [10, 20, 30, 40, 50].span(); @@ -1524,9 +1847,9 @@ fn main() { } ``` -Ranges can also be used with `for` loops. +`for` loops can also use a `Range` to execute code a specific number of times: -```cairo +```rust #[executable] fn main() { for number in 1..4_u8 { @@ -1538,9 +1861,25 @@ fn main() { ## Equivalence Between Loops and Recursive Functions -Loops and recursive functions can achieve similar repetition of code. A `loop` can be transformed into a recursive function by calling the function within itself. +Loops and recursive functions are conceptually equivalent and compile down to similar low-level representations in Sierra. -```cairo +The following `loop` example: +```rust +#[executable] +fn main() -> felt252 { + let mut x: felt252 = 0; + loop { + if x == 2 { + break; + } else { + x += 1; + } + } + x +} +``` +Is equivalent to this recursive function: +```rust #[executable] fn main() -> felt252 { recursive_function(0) @@ -1554,464 +1893,301 @@ fn recursive_function(mut x: felt252) -> felt252 { } } ``` +Both run until `x == 2` is met. -Enums and Pattern Matching - -# Enums and Pattern Matching - -Enums allow you to create a type that has a few possible variants. You can define enums and instantiate them with specific values. +## Concise Control Flow with `if let` and `while let` -## Enum Variants with Data +### `if let` -Each enum variant can hold associated data. This data can be of different types for different variants. +`if let` combines `if` and `let` to handle values matching one pattern while ignoring others, reducing boilerplate compared to an exhaustive `match`. -```cairo, noplayground -# #[derive(Drop)] -# enum Direction { -# North: u128, -# East: u128, -# South: u128, -# West: u128, -# } -# -# #[executable] -# fn main() { - let direction = Direction::North(10); -# } +```rust +#[executable] +fn main() { + let number = Some(5); + if let Some(max) = number { + println!("The maximum is configured to be {}", max); + } +} ``` -### Enums Combined with Custom Types +### `while let` -Enums can store custom data types, including tuples or other structs/enums. +`while let` loops as long as an expression matches a specified pattern. -```cairo, noplayground -#[derive(Drop)] -enum Message { - Quit, - Echo: felt252, - Move: (u128, u128), +```rust +#[executable] +fn main() { + let mut arr = array![1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut sum = 0; + while let Some(value) = arr.pop_front() { + sum += value; + } + println!("{}", sum); } ``` -### Trait Implementations for Enums +### Let Chains -Traits can be implemented for enums to define associated methods. +Let chains combine multiple pattern matches and boolean conditions within an `if let` or `while let` structure without nesting. Note that `else` is not yet supported for let chain expressions. -```cairo, noplayground -trait Processing { - fn process(self: Message); +```rust +fn get_x() -> Option { + Some(5) } -impl ProcessingImpl of Processing { - fn process(self: Message) { - match self { - Message::Quit => { println!("quitting") }, - Message::Echo(value) => { println!("echoing {}", value) }, - Message::Move((x, y)) => { println!("moving from {} to {}", x, y) }, - } - } +fn get_y() -> Option { + Some(8) } -``` -## The `Option` Enum - -The `Option` enum represents an optional value, with variants `Some(T)` and `None`. - -```cairo,noplayground -enum Option { - Some: T, - None, +#[executable] +fn main() { + // Using a let chain to combine pattern matching and additional conditions + if let Some(x) = get_x() && x > 0 && let Some(y) = get_y() && y > 0 { + let sum: u32 = x + y; + println!("sum: {}", sum); + return; + } + println!("x or y is not positive"); + // else is not supported yet; } ``` -It helps prevent bugs by explicitly handling the absence of a value. +### `let else` -### Example: Finding a Value +`let else` allows refutable pattern matching in a `let` binding. If the pattern fails to match, the `else` block executes, which must diverge (e.g., using `return`, `break`, `panic!`). -Functions can return `Option` to indicate success or failure. - -```cairo,noplayground -fn find_value_recursive(mut arr: Span, value: felt252, index: usize) -> Option { - match arr.pop_front() { - Some(index_value) => { if (*index_value == value) { - return Some(index); - } }, - None => { return None; }, - } +```rust +#[derive(Drop)] +enum MyEnum { + A: u32, + B: u32, +} - find_value_recursive(arr, value, index + 1) +fn foo(a: MyEnum) { + let MyEnum::A(x) = a else { + println!("Called with B"); + return; + }; + println!("Called with A({x})"); } -fn find_value_iterative(mut arr: Span, value: felt252) -> Option { - for (idx, array_value) in arr.into_iter().enumerate() { - if (*array_value == value) { - return Some(idx); - } - } - None +#[executable] +fn main() { + foo(MyEnum::A(42)); + foo(MyEnum::B(7)); } ``` -## The `Match` Control Flow Construct +## Practice Summary -The `match` expression allows comparing a value against a series of patterns and executing code based on the match. It ensures all possible cases are handled. +To practice these concepts, try building programs to: +* Generate the $n$-th Fibonacci number. +* Compute the factorial of a number $n$. -### Basic `match` Example +--- -```cairo,noplayground -enum Coin { - Penny, - Nickel, - Dime, - Quarter, -} +Sources: -fn value_in_cents(coin: Coin) -> felt252 { - match coin { - Coin::Penny => 1, - Coin::Nickel => 5, - Coin::Dime => 10, - Coin::Quarter => 25, - } -} -``` +- https://www.starknet.io/cairo-book/ch05-01-defining-and-instantiating-structs.html +- https://www.starknet.io/cairo-book/ch05-00-using-structs-to-structure-related-data.html +- https://www.starknet.io/cairo-book/ch05-02-an-example-program-using-structs.html -### Patterns That Bind to Values +--- -`match` arms can bind to parts of values, allowing extraction of data from enum variants. +## Structs and Custom Data Grouping -```cairo,noplayground +Structs are custom data types that package together and name multiple related values, forming a meaningful group. They are comparable to an object's data attributes in object-oriented languages. Structs, along with enums, are fundamental for creating new types and leveraging Cairo's compile-time type checking. -#[derive(Drop, Debug)] // Debug so we can inspect the state in a minute -enum UsState { - Alabama, - Alaska, -} +### Structs Versus Tuples -#[derive(Drop)] -enum Coin { - Penny, - Nickel, - Dime, - Quarter: UsState, +Structs are similar to tuples in that they hold multiple related values of potentially different types. However, structs name each piece of data (fields), making them more flexible than tuples, as access does not rely solely on data order. + +The tuple approach can lead to less clear code, as seen when calculating the area of a rectangle using indices: + +Filename: src/lib.cairo + +```c +#[executable] +fn main() { + let rectangle = (30, 10); + let area = area(rectangle); + println!("Area is {}", area); } -fn value_in_cents(coin: Coin) -> felt252 { - match coin { - Coin::Penny => 1, - Coin::Nickel => 5, - Coin::Dime => 10, - Coin::Quarter(state) => { - println!("State quarter from {:?}!", state); - 25 - }, - } +fn area(dimension: (u64, u64)) -> u64 { + let (x, y) = dimension; + x * y } ``` -### Matching with `Option` +Using structs adds meaning by labeling the data, transforming the tuple into a named type where fields are explicitly named, such as `width` and `height`. -`match` can be used to handle `Option` variants. +Filename: src/lib.cairo -```cairo -fn plus_one(x: Option) -> Option { - match x { - Some(val) => Some(val + 1), - None => None, - } +```c +struct Rectangle { + width: u64, + height: u64, } #[executable] fn main() { - let five: Option = Some(5); - let six: Option = plus_one(five); - let none = plus_one(None); + let rectangle = Rectangle { width: 30, height: 10 }; + let area = area(rectangle); + println!("Area is {}", area); } -``` - -### Matches Are Exhaustive - -Cairo requires `match` patterns to cover all possible cases, preventing bugs like unhandled `None` values. -```cairo,noplayground -fn plus_one(x: Option) -> Option { - match x { - Some(val) => Some(val + 1), - } +fn area(rectangle: Rectangle) -> u64 { + rectangle.width * rectangle.height } ``` -This code will produce a compilation error because the `None` case is not handled. - -### Catch-all with the `_` Placeholder - -The `_` pattern matches any value without binding it, useful for default actions. - -```cairo,noplayground -fn vending_machine_accept(coin: Coin) -> bool { - match coin { - Coin::Dime => true, - _ => false, - } -} -``` +### Defining and Instantiating Structs -### Multiple Patterns with the `|` Operator +#### Struct Definition -The `|` operator allows matching multiple patterns in a single arm. +To define a struct, use the `struct` keyword followed by the struct name. Inside curly brackets, define the names and types of the fields. -```cairo,noplayground -fn vending_machine_accept(coin: Coin) -> bool { - match coin { - Coin::Dime | Coin::Quarter => true, - _ => false, - } +```c +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, } ``` +You can derive multiple traits on structs, such as `Drop`, `PartialEq` for comparison, and `Debug` for debug-printing. -### Matching Tuples +#### Instance Creation -Tuples can be matched, allowing complex pattern matching. +An instance is created by stating the struct name followed by curly brackets containing `key: value` pairs for each field. The order of fields in the instance definition does not need to match the order in the struct declaration. -```cairo,noplayground +```c #[derive(Drop)] -enum DayType { - Week, - Weekend, - Holiday, +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, } -fn vending_machine_accept(c: (DayType, Coin)) -> bool { - match c { - (DayType::Week, _) => true, - (_, Coin::Dime) | (_, Coin::Quarter) => true, - (_, _) => false, - } +#[executable] +fn main() { + let user1 = User { + active: true, username: "someusername123", email: "[email protected]", sign_in_count: 1, + }; + let user2 = User { + sign_in_count: 1, username: "someusername123", active: true, email: "[email protected]", + }; } ``` -```cairo,noplayground -fn vending_week_machine(c: (DayType, Coin)) -> bool { - match c { - (DayType::Week, Coin::Quarter) => true, - _ => false, - } -} -``` +### Accessing and Modifying Struct Data -### Matching `felt252` and Integer Variables +#### Accessing Fields -`felt252` and integer variables can be matched, with restrictions on ranges and sequential coverage. The first arm must be 0. +Specific values are accessed using dot notation: `instance.field_name`. -Traits and Generic Programming +#### Mutability -# Traits and Generic Programming +If an instance is mutable (declared with `let mut`), you can change a field's value using dot notation assignment. The entire instance must be mutable; Cairo does not allow marking only specific fields as mutable. -Generics allow us to replace specific types with a placeholder that represents multiple types, thereby removing code duplication. Functions can accept parameters of a generic type, enabling the same code to operate on various concrete types. Traits, on the other hand, define shared behavior in an abstract way. By combining traits with generic types, we can constrain generic types to accept only those that exhibit specific behaviors. +Filename: src/lib.cairo -## Removing Duplication with Functions and Generics +```c +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, +} +#[executable] +fn main() { + let mut user1 = User { + active: true, username: "someusername123", email: "[email protected]", sign_in_count: 1, + }; + user1.email = "[email protected]"; +} -To reduce code duplication, we can first extract common logic into functions. This technique can then be extended to generic functions. For instance, a function to find the largest number in an array can be generalized to work with any type that supports comparison. +fn build_user(email: ByteArray, username: ByteArray) -> User { + User { active: true, username: username, email: email, sign_in_count: 1 } +} -## Trait Bounds +fn build_user_short(email: ByteArray, username: ByteArray) -> User { + User { active: true, username, email, sign_in_count: 1 } +} +``` +A new instance can be constructed as the last expression in a function body to implicitly return it. -Trait bounds are constraints applied to generic types, specifying the traits a type must implement to be used with a particular function or type. For example, to find the smallest element in a list of generic type `T`, `T` must implement `PartialOrd` for comparison, `Copy` for element access, and `Drop` for proper memory management. +### Struct Construction Shorthands -### Anonymous Generic Implementation Parameters +#### Field Init Shorthand -When a trait implementation is only used as a constraint and not directly in the function body, we can use the `+` operator for anonymous generic implementation parameters. For example, `+PartialOrd` is equivalent to `impl TPartialOrd: PartialOrd`. +When a function parameter name is identical to the corresponding struct field name, you can use a shorthand syntax, omitting the `: field_name` part. -## Structs with Generic Types +```c +fn build_user_short(email: ByteArray, username: ByteArray) -> User { + User { active: true, username, email, sign_in_count: 1 } +} +``` +This is equivalent to `username: username` and `email: email`. -Structs can also utilize generic type parameters. A struct definition can include type parameters within angle brackets, which can then be used for fields within the struct. For example, a `Wallet` struct can have a `balance` field of type `T`. +#### Struct Update Syntax -## Generic Methods +To create a new instance based on an existing one, while overriding only a few fields, use struct update syntax (`..instance_name`). The remaining fields take their values from the specified instance. The `..` expression must come last. -Methods can be implemented on structs and enums using generic types. Traits can define methods that operate on generic types, and implementations provide the specific behavior for those methods. Constraints can be applied to these generic types within method definitions. +Filename: src/lib.cairo -## Defining a Trait +```c +use core::byte_array; +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, +} -A trait defines a set of method signatures that represent shared behavior. Types can implement these traits to provide their own specific behavior for these methods. Traits are similar to interfaces in other languages. +#[executable] +fn main() { + // --snip-- -### Generic Traits + let user1 = User { + email: "[email protected]", username: "someusername123", active: true, sign_in_count: 1, + }; -Traits can be generic, accepting type parameters. This allows a trait to define behavior for a generic type `T`. Implementations of such traits provide the concrete behavior for specific types. + let user2 = User { email: "[email protected]", ..user1 }; +} +``` +This syntax uses `=` like an assignment because it moves data. If a field contains a non-`Copy` type (like `ByteArray`), the data for that field is moved from the original instance (`user1`) into the new instance (`user2`), invalidating `user1` for that field. If fields implement `Copy` (like `u64` or `bool`), the values are copied instead. -## Implementing a Trait on a Type +--- -Implementing a trait on a type involves using the `impl` keyword, followed by an implementation name, the `of` keyword, and the trait name. If the trait is generic, the generic type is specified in angle brackets. The `impl` block contains the method signatures from the trait, with the specific behavior for that type filled in. +Sources: -### Default Implementations +- https://www.starknet.io/cairo-book/ch05-03-method-syntax.html -Traits can provide default implementations for methods. Implementors can either use the default implementation or override it with their own. Default implementations can call other methods within the same trait, potentially simplifying the required implementation for concrete types. +--- -## Associated Items +### Struct Methods and Associated Functions -Associated items are definitions tied to a trait, including associated types and associated constants. +Methods are declared using the `fn` keyword within a struct's context (via an `impl` block for a trait). Their first parameter must always be `self`, representing the instance on which the method is called. -### Associated Types +#### Defining Methods -Associated types are type aliases within traits, allowing implementers to choose the concrete types to use. This makes trait definitions cleaner and more flexible. For example, a `Pack` trait might have an associated type `Result` whose concrete type is determined by the implementation. +Methods are defined within an `impl` block for a trait associated with the struct. If a method takes ownership of the instance, it uses just `self` as the first parameter, typically when transforming the instance. -### Associated Constants +Listing 5-11 shows defining an `area` method: -Associated constants are constants defined within a trait and implemented for specific types. They provide a way to bind constant values to traits, ensuring consistency and improving code organization. For instance, a `Shape` trait could have an associated constant `SIDES`. - -### Associated Implementations - -Associated implementations allow declaring that a trait implementation must exist for an associated type, enforcing relationships between types and implementations at the trait level. - -Ownership, Memory, and References - -# Ownership, Memory, and References - -Cairo utilizes a linear type system where each value must be used exactly once, either by being destroyed or moved. This system statically prevents operations that could lead to runtime errors, such as writing to the same memory cell twice. - -## Cairo's Ownership System - -In Cairo, ownership applies to variables, not values. Values themselves are immutable and can be referenced by multiple variables. The linear type system serves two primary purposes: - -- Ensuring code is provable and verifiable. -- Abstracting the immutable memory of the Cairo VM. - -### Moving Values - -Moving a value means passing it to another function. The original variable is then destroyed and can no longer be used. Complex types like `Array` are moved when passed to functions. Attempting to use a variable after its value has been moved results in a compile-time error, enforcing that types must implement the `Copy` trait to be passed by value multiple times. - -```cairo -// Example of moving an array -fn foo(mut arr: Array) { - arr.pop_front(); -} - -#[executable] -fn main() { - let arr: Array = array![]; - // foo(arr); // This line would cause a compile error if uncommented, as 'arr' is moved - // foo(arr); // This second call would also fail -} -``` - -The compilation error for attempting to move a value twice highlights the need for the `Copy` trait: - -```shell -$ scarb execute - Compiling no_listing_02_pass_array_by_value v0.1.0 (...) -warn: Unhandled `#[must_use]` type `core::option::Option::` - --> .../lib.cairo:3:5 - arr.pop_front(); - ^^^^^^^^^^^^^^^ - -error: Variable was previously moved. - --> .../lib.cairo:10:9 - foo(arr); - ^^^ -note: variable was previously used here: - --> .../lib.cairo:9:9 - foo(arr); - ^^^ -note: Trait has no implementation in context: core::traits::Copy::>. - -error: could not compile `no_listing_02_pass_array_by_value` due to previous error -error: `scarb` command exited with error -``` - -### Value Destruction - -Values can also be _destroyed_. This process ensures that resources are correctly released. For example, `Felt252Dict` must be "squashed" upon destruction for provability, a process enforced by the type system. - -#### No-op Destruction: The `Drop` Trait - -Types implementing the `Drop` trait are automatically destroyed when they go out of scope. This destruction is a no-op, simply indicating to the compiler that the type can be safely discarded. - -```cairo -#[derive(Drop)] -struct A {} - -#[executable] -fn main() { - A {}; // No error, 'A' is automatically dropped. -} -``` - -Without `#[derive(Drop)]`, attempting to let a type go out of scope without explicit destruction would result in a compile error. - -#### Destruction with Side-effects: The `Destruct` Trait - -When a value is destroyed, the compiler first attempts to call its `drop` method. If that method doesn't exist, it calls the `destruct` method provided by the `Destruct` trait. - -### References and Snapshots - -References allow data to be accessed without copying, improving performance by passing pointers instead of entire data structures. - -#### Using Boxes for Performance - -`Box` acts as a smart pointer, allowing data to be passed by reference (pointer) instead of by value. This is particularly beneficial for large data structures, as it avoids costly memory copies. When data within a `Box` is mutated, a new `Box` is created, requiring a copy of the data. - -```cairo -#[derive(Drop)] -struct Cart { - paid: bool, - items: u256, - buyer: ByteArray, -} - -fn pass_data(cart: Cart) { - println!("{} is shopping today and bought {} items", cart.buyer, cart.items); -} - -fn pass_pointer(cart: Box) { - let cart = cart.unbox(); - println!("{} is shopping today and bought {} items", cart.buyer, cart.items); -} - -#[executable] -fn main() { - let new_struct = Cart { paid: true, items: 1, buyer: "Eli" }; - pass_data(new_struct); - - let new_box = BoxTrait::new(Cart { paid: false, items: 2, buyer: "Uri" }); - pass_pointer(new_box); -} -``` - -Smart pointers in Cairo offer memory management features beyond simple references, enforcing type checking and ownership rules for memory safety. They can prevent issues like null dereferences and provide efficient data passing. - -**Note on References vs. Snapshots:** - -- References (`ref`) must be used on mutable variables and allow mutation. -- Snapshots (`@`) are immutable views of data and cannot be mutated directly. Operations like `*n += 1` are invalid on references; `n += 1` should be used. - -When attempting to modify an array passed to a function, a mutable reference (`ref`) is necessary. Snapshots are unsuitable for mutation. - -```cairo -// Example using mutable references for array modification -fn give_and_take(ref arr: Array, n: u128) -> u128 { - arr.append(n); - arr.pop_front().unwrap_or(0) -} - -fn main() { - let mut arr1: Array = array![1, 2, 3]; - let elem = give_and_take(ref arr1, 4); - println!("{}", elem); // Output: 1 -} -``` - -Structs, Methods, and Associated Functions - -# Structs, Methods, and Associated Functions - -### Defining Methods - -Methods are similar to functions but are defined within the context of a struct (or enum) and have `self` as their first parameter, representing the instance of the type. They are organized using traits and `impl` blocks. - -```cairo, noplayground -#[derive(Copy, Drop)] -struct Rectangle { - width: u64, - height: u64, -} +``` +#[derive(Copy, Drop)] +struct Rectangle { + width: u64, + height: u64, +} trait RectangleTrait { fn area(self: @Rectangle) -> u64; @@ -2030,13 +2206,9 @@ fn main() { } ``` -The `self` parameter can be passed by ownership, snapshot (`@`), or reference (`ref`). Using methods provides a clear way to organize functionality related to a type. - -### Methods with Several Parameters +Methods can accept additional parameters, such as the `can_hold` method which compares two `Rectangle` instances: -Methods can accept additional parameters. For instance, a `can_hold` method can determine if one rectangle fits within another. - -```cairo +``` #[generate_trait] impl RectangleImpl of RectangleTrait { fn area(self: @Rectangle) -> u64 { @@ -2064,13 +2236,13 @@ fn main() { } ``` -### Associated Functions +#### Associated Functions -Associated functions are functions defined within an `impl` block that are not methods (i.e., they don't take `self` as the first parameter). They are often used as constructors. +Associated functions are functions defined inside an `impl` block that do not take `self` as their first parameter. They are associated with the type but do not require an instance to operate. -To call an associated function, use the `::` syntax with the type name (e.g., `RectangleTrait::new(30, 50)`). +Associated functions are commonly used for constructors (often named `new`). They are called using the `::` syntax on the trait name. -```cairo +``` #[generate_trait] impl RectangleImpl of RectangleTrait { fn area(self: @Rectangle) -> u64 { @@ -2106,2778 +2278,1788 @@ fn main() { } ``` -### Struct Update Syntax - -This syntax allows creating a new struct instance by copying most fields from an existing instance, while specifying new values for a subset of fields. - -```cairo -#[derive(Drop)] -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, -} +--- -#[executable] -fn main() { - let user1 = User { - email: "someone@example.com", username: "someusername123", active: true, sign_in_count: 1, - }; +Sources: - let user2 = User { - email: "another@example.com", - ..user1 // Uses remaining fields from user1 - }; -} -``` +- https://www.starknet.io/cairo-book/ch06-02-the-match-control-flow-construct.html +- https://www.starknet.io/cairo-book/ch06-01-enums.html +- https://www.starknet.io/cairo-book/ch06-00-enums-and-pattern-matching.html +- https://www.starknet.io/cairo-book/ch06-03-concise-control-flow-with-if-let-and-while-let.html -### Field Init Shorthand +--- -When struct fields and function parameters share the same names, you can use shorthand to initialize the struct. +# Enums and Pattern Matching -```cairo -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, -} +Enums (enumerations) allow defining a type by enumerating its possible variants. Enums are useful for representing a collection of related values where each value is distinct and has a specific meaning. -fn build_user_short(email: ByteArray, username: ByteArray) -> User { - User { active: true, username, email, sign_in_count: 1 } -} -``` +## Enum Variants and Data Association -### Dictionaries and the `Destruct` Trait +Enum variants can exist without associated data, or they can store associated values. -Dictionaries in Cairo must be "squashed" when destructed. To enforce this, they implement the `Destruct` trait. If a struct contains a dictionary and does not implement `Destruct`, it will not compile when going out of scope. Deriving `Destruct` resolves this by automatically squashing the dictionary. +### Variants Without Associated Data -```cairo -use core::dict::Felt252Dict; +Variants are named using PascalCase. -#[derive(Destruct)] -struct A { - dict: Felt252Dict, +```rust +#[derive(Drop)] +enum Direction { + North, + East, + South, + West, } #[executable] fn main() { - A { dict: Default::default() }; // Compiles after deriving Destruct + let direction = Direction::North; } ``` -Error Handling - -## Error Handling - -This chapter explores Cairo's error handling techniques, focusing on creating adaptable and maintainable programs. We'll cover pattern matching with the `Result` enum, ergonomic error propagation using the `?` operator, and handling recoverable errors with `unwrap` or `expect`. - -## Unrecoverable Errors with `panic` - -In Cairo, unexpected issues can lead to runtime errors that terminate the program. The `panic` function from the core library acknowledges these errors and stops execution. Panics can occur inadvertently (e.g., array out-of-bounds access) or deliberately by calling `panic`. - -When a panic occurs, the program terminates abruptly. The `panic` function accepts an array, which can include an error message, and initiates an unwind process to ensure program soundness before termination. +### Variants With Associated Data -Here's how to call `panic` and return the error code `2`: +Variants can be associated with specific data types, which can be the same type for all variants or different types for different variants. -Filename: src/lib.cairo +Example associating a `u128` value: +```rust +#[derive(Drop)] +enum Direction { + North: u128, + East: u128, + South: u128, + West: u128, +} -```cairo #[executable] fn main() { - let mut data = array![2]; - - if true { - panic(data); - } - println!("This line isn't reached"); + let direction = Direction::North(10); } ``` -## Recoverable Errors with `Result` +Enums can store more complex, custom data structures: + +```rust +#[derive(Drop)] +enum Message { + Quit, + Echo: felt252, + Move: (u128, u128), +} +``` +Here, `Quit` has no associated value, `Echo` holds a `felt252`, and `Move` holds a tuple of two `u128` values. -Many errors are not severe enough to warrant program termination. Functions might fail for reasons that can be handled gracefully, such as integer overflow during addition. Cairo uses the `Result` enum for this purpose. +### Trait Implementations for Enums -### The `Result` Enum +Traits can be defined and implemented for custom enums, allowing methods to be associated with the enum type. -The `Result` enum is defined with two variants: `Ok(T)` for success and `Err(E)` for failure, where `T` is the success type and `E` is the error type. +```rust +trait Processing { + fn process(self: Message); +} -```cairo,noplayground -enum Result { - Ok: T, - Err: E, +impl ProcessingImpl of Processing { + fn process(self: Message) { + match self { + Message::Quit => { println!("quitting") }, + Message::Echo(value) => { println!("echoing {}", value) }, + Message::Move((x, y)) => { println!("moving from {} to {}", x, y) }, + } + } } ``` -### The `ResultTrait` +## The `Option` Enum -The `ResultTrait` provides methods for interacting with `Result` values: +The standard `Option` enum expresses the concept of an optional value, preventing bugs from using uninitialized or unexpected null values. It has two variants: -```cairo,noplayground -trait ResultTrait { - fn expect<+Drop>(self: Result, err: felt252) -> T; - fn unwrap<+Drop>(self: Result) -> T; - fn expect_err<+Drop>(self: Result, err: felt252) -> E; - fn unwrap_err<+Drop>(self: Result) -> E; - fn is_ok(self: @Result) -> bool; - fn is_err(self: @Result) -> bool; +```rust +enum Option { + Some: T, + None, } ``` -- **`expect` and `unwrap`**: Both extract the value from `Ok(x)`. `expect` allows a custom panic message, while `unwrap` uses a default one. If the `Result` is `Err`, they panic. -- **`expect_err` and `unwrap_err`**: Both extract the value from `Err(x)`. `expect_err` allows a custom panic message, while `unwrap_err` uses a default one. If the `Result` is `Ok`, they panic. -- **`is_ok`**: Returns `true` if the `Result` is `Ok`, `false` otherwise. -- **`is_err`**: Returns `true` if the `Result` is `Err`, `false` otherwise. +This is useful when a function might not return a value, such as finding an element's index in an array: -The `<+Drop>` and `<+Drop>` constraints indicate that these methods require a `Drop` trait implementation for the generic types. +```rust +fn find_value_recursive(mut arr: Span, value: felt252, index: usize) -> Option { + match arr.pop_front() { + Some(index_value) => { if (*index_value == value) { + return Some(index); + } }, + None => { return None; }, + } -Consider a function that handles potential overflow during `u128` addition: + find_value_recursive(arr, value, index + 1) +} -```cairo,noplayground -fn u128_overflowing_add(a: u128, b: u128) -> Result; +fn find_value_iterative(mut arr: Span, value: felt252) -> Option { + for (idx, array_value) in arr.into_iter().enumerate() { + if (*array_value == value) { + return Some(idx); + } + } + None +} ``` -This function returns `Ok(sum)` if successful or `Err(overflowed_value)` if an overflow occurs. +## The `match` Control Flow Construct -Example of converting `Result` to `Option`: +The `match` expression compares a value against a series of patterns and executes code based on the first matching pattern. It functions like a coin-sorting machine: the value falls into the first pattern it fits. -```cairo,noplayground -fn u128_checked_add(a: u128, b: u128) -> Option { - match u128_overflowing_add(a, b) { - Ok(r) => Some(r), - Err(r) => None, +An arm consists of a pattern, the `=>` operator, and the code to run. The result of the expression in the matching arm is the result of the entire `match` expression. + +Example using a simple `Coin` enum: +```rust +enum Coin { + Penny, + Nickel, + Dime, + Quarter, +} + +fn value_in_cents(coin: Coin) -> felt252 { + match coin { + Coin::Penny => 1, + Coin::Nickel => 5, + Coin::Dime => 10, + Coin::Quarter => 25, } } ``` -Example of parsing a `felt252` to `u8`: +If multiple lines of code are needed in an arm, curly brackets must be used, and the last expression's value is returned: -```cairo,noplayground -fn parse_u8(s: felt252) -> Result { - match s.try_into() { - Some(value) => Ok(value), - None => Err('Invalid integer'), +```rust +fn value_in_cents(coin: Coin) -> felt252 { + match coin { + Coin::Penny => { + println!("Lucky penny!"); + 1 + }, + Coin::Nickel => 5, + Coin::Dime => 10, + Coin::Quarter => 25, } } ``` -### Propagating Errors +### Patterns That Bind to Values -Instead of handling errors within a function, you can return the error to the calling code for it to decide how to manage it. This is known as error propagation. +Patterns can bind to parts of the value to extract inner data. For example, if a `Quarter` variant holds a `UsState`: -Listing 9-1 demonstrates propagating errors using a `match` expression: +```rust +#[derive(Drop, Debug)] // Debug so we can inspect the state in a minute +enum UsState { + Alabama, + Alaska, +} -```cairo, noplayground -// A hypothetical function that might fail -fn parse_u8(input: felt252) -> Result { - let input_u256: u256 = input.into(); - if input_u256 < 256 { - Result::Ok(input.try_into().unwrap()) - } else { - Result::Err('Invalid Integer') - } -} - -fn mutate_byte(input: felt252) -> Result { - let input_to_u8 = match parse_u8(input) { - Result::Ok(num) => num, - Result::Err(err) => { return Result::Err(err); }, - }; - let res = input_to_u8 - 1; - Result::Ok(res) -} -``` - -Listing 9-1: A function that returns errors to the calling code using a `match` expression. - -### A Shortcut for Propagating Errors: the `?` Operator - -The `?` operator simplifies error propagation. Listing 9-2 shows the `mutate_byte` function using the `?` operator: - -```cairo, noplayground -# // A hypothetical function that might fail -# fn parse_u8(input: felt252) -> Result { -# let input_u256: u256 = input.into(); -# if input_u256 < 256 { -# Result::Ok(input.try_into().unwrap()) -# } else { -# Result::Err('Invalid Integer') -# } -# } -# -fn mutate_byte(input: felt252) -> Result { - let input_to_u8: u8 = parse_u8(input)?; - let res = input_to_u8 - 1; - Ok(res) -} -# -# #[cfg(test)] -# mod tests { -# use super::*; -# #[test] -# fn test_function_2() { -# let number: felt252 = 258; -# match mutate_byte(number) { -# Ok(value) => println!("Result: {}", value), -# Err(e) => println!("Error: {}", e), -# } -# } -# } -``` - -Listing 9-2: A function that returns errors to the calling code using the `?` operator. - -The `?` operator, when applied to a `Result`, unwraps the `Ok` value or returns the `Err` value early from the function. If applied to an `Option`, it unwraps the `Some` value or returns `None` early. - -#### Where The `?` Operator Can Be Used - -The `?` operator can only be used in functions whose return type is compatible with the `Result` or `Option` being operated on. Using `?` on a `Result` requires the function to return a `Result`, and using `?` on an `Option` requires the function to return an `Option`. - -Listing 9-3 demonstrates a compilation error when using `?` in a `main` function (which returns `()`): - -```cairo -# //TAG: does_not_compile -# -#[executable] -fn main() { - let some_num = parse_u8(258)?; -} -# -fn parse_u8(input: felt252) -> Result { - let input_u256: u256 = input.into(); - if input_u256 < 256 { - Result::Ok(input.try_into().unwrap()) - } else { - Result::Err('Invalid Integer') - } -} -``` - -Listing 9-3: Attempting to use the `?` in a `main` function that returns `()` won’t compile. - -The error message indicates that `?` is restricted to functions returning `Option` or `Result`. To fix this, either change the function's return type to be compatible or use a `match` expression to handle the `Result` or `Option` explicitly. - -### Summary - -Cairo handles errors using the `Result` enum (`Ok` or `Err`) and the `panic` function for unrecoverable errors. The `ResultTrait` provides methods for managing `Result` values. Error propagation is facilitated by the `?` operator, which simplifies handling errors by returning them to the caller or unwrapping successful values, leading to more concise and ergonomic code. - -Advanced Features and Patterns - -# Advanced Features and Patterns - -Contract Development - -# Contract Development - -## Storage Variables - -Storage variables are used to store persistent data on the blockchain. They are defined within a special `Storage` struct annotated with the `#[storage]` attribute. - -When accessing a storage variable, such as `let x = self.owner;`, we interact with a `StorageBase` type. This `StorageBase` represents the base location of the variable in the contract's storage. From this base address, pointers to the variable's fields or the variable itself can be obtained. Operations like `read` and `write`, defined in the `Store` trait, are then used on these pointers. These operations are transparent to the developer, as the compiler translates struct field accesses into the appropriate `StoragePointer` manipulations. - -## Storage Mappings - -For storage mappings, an intermediate type called `StoragePath` is introduced. A `StoragePath` represents a chain of storage nodes and struct fields that form a path to a specific storage slot. - -The process to access a value within a mapping, for example, a `Map`, involves the following steps: - -1. Start at the `StorageBase` of the `Map` and convert it into a `StoragePath`. -2. Traverse the `StoragePath` to reach the desired value using the `entry` method. For a `Map`, the `entry` method hashes the current path with the next key to generate the subsequent `StoragePath`. -3. Repeat step 2 until the `StoragePath` points to the target value. The final value is then converted into a `StoragePointer`. -4. Finally, the value can be read or written at that pointer. - -Note that types like `ContractAddress` must be converted to a `StoragePointer` before they can be read from or written to. - -Cairo Circuits and Low-Level Operations - -# Cairo Circuits and Low-Level Operations - -## Combining Circuit Elements and Gates - -Circuit elements can be combined using predefined functions like `circuit_add`, `circuit_sub`, `circuit_mul`, and `circuit_inverse`. For example, to represent the circuit `a * (a + b)`: - -```cairo, noplayground -# use core::circuit::{ -# AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, -# CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, -# }; -# -# // Circuit: a * (a + b) -# // witness: a = 10, b = 20 -# // expected output: 10 * (10 + 20) = 300 -# fn eval_circuit() -> (u384, u384) { -# let a = CircuitElement::> {}; -# let b = CircuitElement::> {}; -# - let add = circuit_add(a, b); - let mul = circuit_mul(a, add); -# -# let output = (mul,); -# -# let mut inputs = output.new_inputs(); -# inputs = inputs.next([10, 0, 0, 0]); -# inputs = inputs.next([20, 0, 0, 0]); -# -# let instance = inputs.done(); -# -# let bn254_modulus = TryInto::< -# _, CircuitModulus, -# >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) -# .unwrap(); -# -# let res = instance.eval(bn254_modulus).unwrap(); -# -# let add_output = res.get_output(add); -# let circuit_output = res.get_output(mul); -# -# assert!(add_output == u384 { limb0: 30, limb1: 0, limb2: 0, limb3: 0 }, "add_output"); -# assert!(circuit_output == u384 { limb0: 300, limb1: 0, limb2: 0, limb3: 0 }, "circuit_output"); -# -# (add_output, circuit_output) -# } -# -# #[executable] -# fn main() { -# eval_circuit(); -# } -``` - -## Assigning Input Values - -To assign values to circuit inputs, the `new_inputs` function is called on the circuit's output, followed by `next` calls for each input. The `AddInputResult` enum manages the state of input assignment: - -```cairo -pub enum AddInputResult { - /// All inputs have been filled. - Done: CircuitData, - /// More inputs are needed to fill the circuit instance's data. - More: CircuitInputAccumulator, -} -``` - -A `u384` value is represented as an array of four `u96`. For instance, initializing `a` to 10 and `b` to 20: - -```cairo, noplayground -# use core::circuit::{ -# AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, -# CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, -# }; -# -# // Circuit: a * (a + b) -# // witness: a = 10, b = 20 -# // expected output: 10 * (10 + 20) = 300 -# fn eval_circuit() -> (u384, u384) { -# let a = CircuitElement::> {}; -# let b = CircuitElement::> {}; -# -# let add = circuit_add(a, b); -# let mul = circuit_mul(a, add); -# -# let output = (mul,); -# - let mut inputs = output.new_inputs(); - inputs = inputs.next([10, 0, 0, 0]); - inputs = inputs.next([20, 0, 0, 0]); - - let instance = inputs.done(); -# -# let bn254_modulus = TryInto::< -# _, CircuitModulus, -# >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) -# .unwrap(); -# -# let res = instance.eval(bn254_modulus).unwrap(); -# -# let add_output = res.get_output(add); -# let circuit_output = res.get_output(mul); -# -# assert!(add_output == u384 { limb0: 30, limb1: 0, limb2: 0, limb3: 0 }, "add_output"); -# assert!(circuit_output == u384 { limb0: 300, limb1: 0, limb2: 0, limb3: 0 }, "circuit_output"); -# -# (add_output, circuit_output) -# } -# -# #[executable] -# fn main() { -# eval_circuit(); -# } -``` - -## Low-Level Modular Arithmetic Operations - -The `add` function demonstrates a low-level modular addition using `UInt384` and the `ModBuiltin`. It utilizes `run_mod_p_circuit` to perform the calculation `c = x + y (mod p)`. - -```cairo -from starkware.cairo.common.cairo_builtins import UInt384, ModBuiltin -from starkware.cairo.common.modulo import run_mod_p_circuit -from starkware.cairo.lang.compiler.lib.registers import get_fp_and_pc - -func add{range_check96_ptr: felt*, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}( - x: UInt384*, y: UInt384*, p: UInt384* -) -> UInt384* { - let (_, pc) = get_fp_and_pc(); - - // Define pointers to the offsets tables, which come later in the code - pc_label: - let add_mod_offsets_ptr = pc + (add_offsets - pc_label); - let mul_mod_offsets_ptr = pc + (mul_offsets - pc_label); - - // Load x and y into the range_check96 segment, which doubles as our values table - // x takes slots 0-3, y takes 4-7—each UInt384 is 4 words of 96 bits - assert [range_check96_ptr + 0] = x.d0; - assert [range_check96_ptr + 1] = x.d1; - assert [range_check96_ptr + 2] = x.d2; - assert [range_check96_ptr + 3] = x.d3; - assert [range_check96_ptr + 4] = y.d0; - assert [range_check96_ptr + 5] = y.d1; - assert [range_check96_ptr + 6] = y.d2; - assert [range_check96_ptr + 7] = y.d3; - - // Fire up the modular circuit: 1 addition, no multiplications - // The builtin deduces c = x + y (mod p) and writes it to offsets 8-11 - run_mod_p_circuit( - p=[p], - values_ptr=cast(range_check96_ptr, UInt384*), - add_mod_offsets_ptr=add_mod_offsets_ptr, - add_mod_n=1, - mul_mod_offsets_ptr=mul_mod_offsets_ptr, - mul_mod_n=0, - ); - - // Bump the range_check96_ptr forward: 8 input words + 4 output words = 12 total - let range_check96_ptr = range_check96_ptr + 12; - - // Return a pointer to the result, sitting in the last 4 words - return cast(range_check96_ptr - 4, UInt384*); - - // Offsets for AddMod: point to x (0), y (4), and the result (8) - add_offsets: - dw 0; // x starts at offset 0 - dw 4; // y starts at offset 4 - dw 8; // result c starts at offset 8 - - // No offsets needed for MulMod here - mul_offsets: -} -``` - -Data Structures and Collections - -Arrays - -# Arrays - -An array is a collection of elements of the same type. In Cairo, arrays are implemented using the `ArrayTrait` from the core library. It's important to note that arrays in Cairo have limited modification options; they function as queues where values cannot be directly modified after being added. Elements can only be appended to the end and removed from the front. - -## Creating an Array - -Arrays are created using the `ArrayTrait::new()` call. You can optionally specify the type of elements the array will hold. - -```cairo -#[executable] -fn main() { - let mut a = ArrayTrait::new(); - a.append(0); - a.append(1); - a.append(2); -} -``` - -To explicitly define the type of elements: - -```cairo, noplayground -let mut arr = ArrayTrait::::new(); -``` - -Or: - -```cairo, noplayground -let mut arr:Array = ArrayTrait::new(); -``` - -The `array!` macro provides a more concise way to initialize arrays with known values at compile time: - -Without `array!`: - -```cairo - let mut arr = ArrayTrait::new(); - arr.append(1); - arr.append(2); - arr.append(3); - arr.append(4); - arr.append(5); -``` - -With `array!`: - -```cairo - let arr = array![1, 2, 3, 4, 5]; -``` - -## Updating an Array - -### Adding Elements - -Elements are added to the end of an array using the `append()` method. - -```cairo -# #[executable] -# fn main() { -# let mut a = ArrayTrait::new(); -# a.append(0); - a.append(1); -# a.append(2); -# } -``` - -### Removing Elements - -Elements can only be removed from the front of an array using the `pop_front()` method. This method returns an `Option` containing the removed element or `None` if the array is empty. - -```cairo -#[executable] -fn main() { - let mut a = ArrayTrait::new(); - a.append(10); - a.append(1); - a.append(2); - - let first_value = a.pop_front().unwrap(); - println!("The first value is {}", first_value); -} -``` - -Cairo's memory immutability means array elements cannot be modified in place. Operations like `append` and `pop_front` do not mutate memory but update pointers. - -## Reading Elements from an Array - -Elements can be accessed using the `get()` or `at()` methods, or the subscripting operator `[]`. - -### `get()` Method - -The `get()` method returns an `Option>`. It returns `Some(snapshot)` if the element exists at the given index, and `None` otherwise. This is useful for handling potential out-of-bounds access gracefully. - -```cairo -#[executable] -fn main() -> u128 { - let mut arr = ArrayTrait::::new(); - arr.append(100); - let index_to_access = - 1; // Change this value to see different results, what would happen if the index doesn't exist? - match arr.get(index_to_access) { - Some(x) => { - *x - .unbox() // Don't worry about * for now, if you are curious see Chapter 4.2 #desnap operator - // It basically means "transform what get(idx) returned into a real value" - }, - None => { panic!("out of bounds") }, - } -} -``` - -### `at()` Method - -The `at()` method and the `[]` operator provide direct access to an element. They return a snapshot of the element, unwrapped from its box. If the index is out of bounds, a panic occurs. Use this when out-of-bounds access should be a fatal error. - -```cairo -#[executable] -fn main() { - let mut a = ArrayTrait::new(); - a.append(0); - a.append(1); - - // using the `at()` method - let first = *a.at(0); - assert!(first == 0); - // using the subscripting operator - let second = *a[1]; - assert!(second == 1); -} -``` - -## Size-related Methods - -- `len()`: Returns the number of elements in the array (type `usize`). -- `is_empty()`: Returns `true` if the array is empty, `false` otherwise. - -## Storing Multiple Types with Enums - -To store elements of different types in an array, you can define an `Enum` to represent the different possible data types. - -```cairo -#[derive(Copy, Drop)] -enum Data { - Integer: u128, - Felt: felt252, - Tuple: (u32, u32), -} - -#[executable] -fn main() { - let mut messages: Array = array![]; - messages.append(Data::Integer(100)); - messages.append(Data::Felt('hello world')); - messages.append(Data::Tuple((10, 30))); -} -``` - -## Span - -`Span` is a struct representing a snapshot of an `Array`, providing safe, read-only access. It supports all `Array` methods except `append()`. - -To create a `Span` from an `Array`, use the `span()` method: - -```cairo -#[executable] -fn main() { - let mut array: Array = ArrayTrait::new(); - array.span(); -} -``` - -Dictionaries (Felt252Dict) - -# Dictionaries (Felt252Dict) - -Cairo provides a dictionary-like type, `Felt252Dict`, which stores unique key-value pairs. Keys are restricted to `felt252`, while the value type `T` can be specified. This data structure is useful for organizing data when arrays are insufficient and for simulating mutability. - -## Basic Use of Dictionaries - -The `Felt252DictTrait` trait provides core dictionary operations: - -1. `insert(felt252, T) -> ()`: Writes values to a dictionary. -2. `get(felt252) -> T`: Reads values from a dictionary. - -New dictionaries can be created using `Default::default()`. When a key is accessed for the first time, its value is initialized to zero. There is no direct way to delete data from a dictionary. - -### Example: Basic Dictionary Operations - -```cairo -use core::dict::Felt252Dict; - -#[executable] -fn main() { - let mut balances: Felt252Dict = Default::default(); - - balances.insert('Alex', 100); - balances.insert('Maria', 200); - - let alex_balance = balances.get('Alex'); - assert!(alex_balance == 100, "Balance is not 100"); - - let maria_balance = balances.get('Maria'); - assert!(maria_balance == 200, "Balance is not 200"); -} -``` - -### Example: Updating Dictionary Values - -`Felt252Dict` allows updating values for existing keys: - -```cairo -use core::dict::Felt252Dict; - -#[executable] -fn main() { - let mut balances: Felt252Dict = Default::default(); - - // Insert Alex with 100 balance - balances.insert('Alex', 100); - // Check that Alex has indeed 100 associated with him - let alex_balance = balances.get('Alex'); - assert!(alex_balance == 100, "Alex balance is not 100"); - - // Insert Alex again, this time with 200 balance - balances.insert('Alex', 200); - // Check the new balance is correct - let alex_balance_2 = balances.get('Alex'); - assert!(alex_balance_2 == 200, "Alex balance is not 200"); -} -``` - -## Dictionaries Underneath - -Cairo's memory is immutable. `Felt252Dict` simulates mutability by storing a list of entries. Each entry represents an access (read/write/update) and contains: - -1. `key`: The key for the pair. -2. `previous_value`: The value held at `key` before this entry. -3. `new_value`: The new value held at `key` after this entry. - -An `Entry` struct is defined as: - -```cairo,noplayground -struct Entry { - key: felt252, - previous_value: T, - new_value: T, -} -``` - -Each interaction with the dictionary registers a new entry. A `get` operation records an entry with no change, while an `insert` records the new value and the previous one. This approach avoids rewriting memory, instead creating new memory cells per interaction. - -### Example: Entry List Generation - -Consider the following operations: - -```cairo -# use core::dict::Felt252Dict; -# -# struct Entry { -# key: felt252, -# previous_value: T, -# new_value: T, -# } -# -# #[executable] -# fn main() { -# let mut balances: Felt252Dict = Default::default(); - balances.insert('Alex', 100_u64); - balances.insert('Maria', 50_u64); - balances.insert('Alex', 200_u64); - balances.get('Maria'); -# } -``` - -This produces the following entry list: - -| key | previous | new | -| :---: | -------- | --- | -| Alex | 0 | 100 | -| Maria | 0 | 50 | -| Alex | 100 | 200 | -| Maria | 50 | 50 | - -This implementation has a worst-case time complexity of O(n) for each operation, where n is the number of entries, due to the need to scan the entry list. This is necessary for the STARK proof system's verification process, specifically for "dictionary squashing". - -## Squashing Dictionaries - -Squashing verifies the integrity of dictionary operations by reviewing the entry list. For a given key, it checks that the `new_value` of the i-th entry matches the `previous_value` of the (i+1)-th entry. This process reduces the entry list to its final state. - -### Example: Squashing Reduction - -Given this entry list: - -| key | previous | new | -| :-----: | -------- | --- | -| Alex | 0 | 150 | -| Maria | 0 | 100 | -| Charles | 0 | 70 | -| Maria | 100 | 250 | -| Alex | 150 | 40 | -| Alex | 40 | 300 | -| Maria | 250 | 190 | -| Alex | 300 | 90 | - -After squashing, it becomes: - -| key | previous | new | -| :-----: | -------- | --- | -| Alex | 0 | 90 | -| Maria | 0 | 190 | -| Charles | 0 | 70 | - -Squashing is automatically called via the `Destruct` trait implementation when a `Felt252Dict` goes out of scope. - -## Entry and Finalize - -The `entry` and `finalize` methods allow manual interaction with dictionary entries, mimicking internal operations. - -- `entry(self: Felt252Dict, key: felt252) -> (Felt252DictEntry, T)`: Creates a new entry for a given key, taking ownership of the dictionary and returning the entry and its previous value. -- `finalize(self: Felt252DictEntry, new_value: T) -> Felt252Dict`: Inserts the updated entry back into the dictionary and returns ownership. - -### Example: Custom `get` Implementation - -```cairo,noplayground -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; - -fn custom_get, +Drop, +Copy>( - ref dict: Felt252Dict, key: felt252, -) -> T { - // Get the new entry and the previous value held at `key` - let (entry, prev_value) = dict.entry(key); - - // Store the value to return - let return_value = prev_value; - - // Update the entry with `prev_value` and get back ownership of the dictionary - dict = entry.finalize(prev_value); - - // Return the read value - return_value -} -``` - -### Example: Custom `insert` Implementation - -```cairo,noplayground -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; - -fn custom_insert, +Destruct, +Drop>( - ref dict: Felt252Dict, key: felt252, value: T, -) { - // Get the last entry associated with `key` - // Notice that if `key` does not exist, `_prev_value` will - // be the default value of T. - let (entry, _prev_value) = dict.entry(key); - - // Insert `entry` back in the dictionary with the updated value, - // and receive ownership of the dictionary - dict = entry.finalize(value); -} -``` - -## Dictionaries of Types not Supported Natively - -The `Felt252Dict` requires `T` to implement `Felt252DictValue`, which provides a `zero_default` method. This is implemented for basic types but not for complex types like arrays or structs (`u256`). - -To store unsupported types, wrap them in `Nullable`, which uses `Box` to manage memory in a dedicated segment. - -### Example: Storing `Span` in a Dictionary - -```cairo -use core::dict::Felt252Dict; -use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; - -#[executable] -fn main() { - // Create the dictionary - let mut d: Felt252Dict>> = Default::default(); - - // Create the array to insert - let a = array![8, 9, 10]; - - // Insert it as a `Span` - d.insert(0, NullableTrait::new(a.span())); - - // Get value back - let val = d.get(0); - - // Search the value and assert it is not null - let span = match match_nullable(val) { - FromNullableResult::Null => panic!("No value found"), - FromNullableResult::NotNull(val) => val.unbox(), - }; - - // Verify we are having the right values - assert!(*span.at(0) == 8, "Expecting 8"); - assert!(*span.at(1) == 9, "Expecting 9"); - assert!(*span.at(2) == 10, "Expecting 10"); -} -``` - -## Using Arrays inside Dictionaries - -Storing and modifying arrays in dictionaries requires careful handling due to `Array` not implementing the `Copy` trait. - -Directly using `get` on an array in a dictionary will cause a compiler error. Instead, dictionary entries must be used to access and modify arrays. - -### Example: Reading an Array Entry - -```cairo,noplayground -fn get_array_entry(ref dict: Felt252Dict>>, index: felt252) -> Span { - let (entry, _arr) = dict.entry(index); - let mut arr = _arr.deref_or(array![]); - let span = arr.span(); - dict = entry.finalize(NullableTrait::new(arr)); - span -} -``` - -### Example: Appending to an Array in a Dictionary - -```cairo,noplayground -fn append_value(ref dict: Felt252Dict>>, index: felt252, value: u8) { - let (entry, arr) = dict.entry(index); - let mut unboxed_val = arr.deref_or(array![]); - unboxed_val.append(value); - dict = entry.finalize(NullableTrait::new(unboxed_val)); -} -``` - -### Complete Example: Array Manipulation in Dictionary - -```cairo -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; -use core::nullable::NullableTrait; - -fn append_value(ref dict: Felt252Dict>>, index: felt252, value: u8) { - let (entry, arr) = dict.entry(index); - let mut unboxed_val = arr.deref_or(array![]); - unboxed_val.append(value); - dict = entry.finalize(NullableTrait::new(unboxed_val)); -} - -fn get_array_entry(ref dict: Felt252Dict>>, index: felt252) -> Span { - let (entry, _arr) = dict.entry(index); - let mut arr = _arr.deref_or(array![]); - let span = arr.span(); - dict = entry.finalize(NullableTrait::new(arr)); - span -} - -#[executable] -fn main() { - let arr = array![20, 19, 26]; - let mut dict: Felt252Dict>> = Default::default(); - dict.insert(0, NullableTrait::new(arr)); - println!("Before insertion: {:?}", get_array_entry(ref dict, 0)); - - append_value(ref dict, 0, 30); - - println!("After insertion: {:?}", get_array_entry(ref dict, 0)); -} -``` - -## Nested Mappings - -Dictionaries can be nested to create multi-dimensional mappings. For example, `Map>` can represent a mapping from user addresses to their warehouses, where each warehouse maps item IDs to quantities. - -```cairo, noplayground -# use starknet::ContractAddress; -# -# #[starknet::interface] -# trait IWarehouseContract { -# fn set_quantity(ref self: TState, item_id: u64, quantity: u64); -# fn get_item_quantity(self: @TState, address: ContractAddress, item_id: u64) -> u64; -# } -# -# #[starknet::contract] -# mod WarehouseContract { -# use starknet::storage::{ -# Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, -# }; -# use starknet::{ContractAddress, get_caller_address}; -# - #[storage] - struct Storage { - user_warehouse: Map>, - } -# -# #[abi(embed_v0)] -# impl WarehouseContractImpl of super::IWarehouseContract { -# fn set_quantity(ref self: ContractState, item_id: u64, quantity: u64) { -# let caller = get_caller_address(); -# self.user_warehouse.entry(caller).entry(item_id).write(quantity); -# } -# -# fn get_item_quantity(self: @ContractState, address: ContractAddress, item_id: u64) -> u64 { -# self.user_warehouse.entry(address).entry(item_id).read() -# } -# } -# } -``` - -## Storage Address Computation for Mappings - -The storage address for a mapping value is computed using `h(sn_keccak(variable_name), k)` for a single key `k`, and `h(...h(h(sn_keccak(variable_name), k_1), k_2), ..., k_n)` for multiple keys, where `h` is the Pedersen hash. If a key is a struct, its elements are used as keys. The struct must implement the `Hash` trait. - -## Summary - -- `Felt252Dict` provides key-value storage with `felt252` keys. -- It simulates mutability using an entry list. -- Operations like `insert`, `get`, `entry`, and `finalize` are available. -- Squashing verifies data integrity. -- Complex types can be stored using `Nullable`. -- Arrays can be manipulated within dictionaries using entry-based access. -- Nested mappings are supported for complex data structures. - -Structs - -# Structs - -## Using Structs to Structure Related Data - -A struct, or structure, is a custom data type that lets you package together and name multiple related values that make up a meaningful group. If you’re familiar with an object-oriented language, a struct is like an object’s data attributes. Structs, along with enums, are the building blocks for creating new types in your program’s domain to take full advantage of Cairo's compile-time type checking. - -Structs are similar to tuples in that both hold multiple related values, and the pieces can be different types. Unlike with tuples, in a struct you’ll name each piece of data, which we call fields, so it’s clear what the values mean. This naming makes structs more flexible than tuples, as you don’t have to rely on the order of the data to specify or access the values of an instance. - -## Defining and Instantiating Structs - -To define a struct, we use the keyword `struct` and name the entire struct. A struct’s name should describe the significance of the pieces of data being grouped together. Inside curly brackets, we define the names and types of the fields. - -```cairo, noplayground -#[derive(Drop)] -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, -} -``` - -You can derive multiple traits on structs, such as `Drop`, `PartialEq` for comparison, and `Debug` for debug-printing. - -To use a struct after defining it, we create an instance by specifying concrete values for each field. We do this by stating the struct name followed by curly brackets containing `key: value` pairs, where keys are field names. The fields don't need to be in the same order as declared in the struct definition. - -```cairo #[derive(Drop)] -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, -} - -#[executable] -fn main() { - let user1 = User { - active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, - }; - let user2 = User { - sign_in_count: 1, username: "someusername123", active: true, email: "someone@example.com", - }; +enum Coin { + Penny, + Nickel, + Dime, + Quarter: UsState, +} + +fn value_in_cents(coin: Coin) -> felt252 { + match coin { + Coin::Penny => 1, + Coin::Nickel => 5, + Coin::Dime => 10, + Coin::Quarter(state) => { + println!("State quarter from {:?}!", state); + 25 + }, + } } ``` +When `Coin::Quarter(state)` matches, `state` binds to the inner `UsState` value. -## Accessing, Mutating, and Updating Structs +### Matching with `Option` -To get a specific value from a struct, we use dot notation. For example, to access `user1`'s email address, we use `user1.email`. +`match` is used to safely handle `Option` variants: -If an instance is mutable, we can change a value using dot notation and assignment. The entire instance must be mutable; Cairo doesn’t allow marking only certain fields as mutable. +```rust +fn plus_one(x: Option) -> Option { + match x { + Some(val) => Some(val + 1), + None => None, + } +} -```cairo -# #[derive(Drop)] -# struct User { -# active: bool, -# username: ByteArray, -# email: ByteArray, -# sign_in_count: u64, -# } #[executable] fn main() { - let mut user1 = User { - active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, - }; - user1.email = "anotheremail@example.com"; + let five: Option = Some(5); + let six: Option = plus_one(five); + let none = plus_one(None); } ``` -We can use struct update syntax to create a new instance using most of the values from an existing instance. The `..instance` syntax must come last. +### Exhaustiveness and the `_` Placeholder -```cairo -# use core::byte_array; -# #[derive(Drop)] -# struct User { -# active: bool, -# username: ByteArray, -# email: ByteArray, -# sign_in_count: u64, -# } -#[executable] -fn main() { - let user1 = User { - email: "someone@example.com", username: "someusername123", active: true, sign_in_count: 1, - }; +Matches in Cairo must be exhaustive; all possible cases must be covered. Forgetting a case results in a compilation error: - let user2 = User { email: "another@example.com", ..user1 }; +```rust +fn plus_one(x: Option) -> Option { + match x { + Some(val) => Some(val + 1), + } } +// This code will cause an error: error: Missing match arm: `None` not covered. ``` -The struct update syntax moves data. If fields of moved types (like `ByteArray`) are not re-specified in the new instance, the original instance becomes invalid. Fields implementing `Copy` are copied. - -## Example: Structs vs. Separate Variables and Tuples - -Consider a program to calculate the area of a rectangle. Without structs, we might use separate variables. - -```cairo -#[executable] -fn main() { - let width = 30; - let height = 10; - let area = area(width, height); - println!("Area is {}", area); -} +The `_` pattern acts as a catch-all for any value that hasn't matched previous arms, satisfying exhaustiveness without binding the value. -fn area(width: u64, height: u64) -> u64 { - width * height +```rust +fn vending_machine_accept(coin: Coin) -> bool { + match coin { + Coin::Dime => true, + _ => false, + } } ``` +Note: There is no catch-all pattern in Cairo that allows you to use the value of the pattern. -This works, but the `area` function signature `fn area(width: u64, height: u64) -> u64` doesn't clearly indicate that `width` and `height` are related to a single rectangle. - -Refactoring with tuples can group them: +### Multiple Patterns with the `|` Operator -```cairo -#[executable] -fn main() { - let rectangle = (30, 10); - let area = area(rectangle); - println!("Area is {}", area); -} +The `|` (or) operator allows matching multiple patterns in a single arm: -fn area(dimension: (u64, u64)) -> u64 { - let (x, y) = dimension; - x * y +```rust +fn vending_machine_accept(coin: Coin) -> bool { + match coin { + Coin::Dime | Coin::Quarter => true, + _ => false, + } } ``` -While better, using named fields in structs provides more clarity and flexibility than tuples for grouping related data. - -Enums - -# Enums - -Enums, short for "enumerations," are a way to define a custom data type that consists of a fixed set of named values, called _variants_. Enums are useful for representing a collection of related values where each value is distinct and has a specific meaning. - -## Enum Variants and Values +### Matching Tuples -Here's a simple example of an enum with variants that do not have associated values: +Tuples can be matched against specific structures: -```cairo, noplayground +```rust #[derive(Drop)] -enum Direction { - North, - East, - South, - West, +enum DayType { + Week, + Weekend, + Holiday, } -``` - -A variant can be instantiated using the following syntax: -```cairo, noplayground -# #[derive(Drop)] -# enum Direction { -# North, -# East, -# South, -# West, -# } -# -# #[executable] -# fn main() { - let direction = Direction::North; -# } +fn vending_machine_accept(c: (DayType, Coin)) -> bool { + match c { + (DayType::Week, _) => true, + (_, Coin::Dime) | (_, Coin::Quarter) => true, + (_, _) => false, + } +} ``` +The `_` can be used within tuple matching to ignore parts of the tuple. + +### Matching `felt252` and Integer Variables -Variants can also have associated values. For example, to store the degree of a direction: +Integers fitting into a single `felt252` can be matched, but restrictions apply: +1. The first arm must start at 0. +2. Each arm must cover a sequential segment contiguously. -```cairo, noplayground -#[derive(Drop)] -enum Direction { - North: u128, - East: u128, - South: u128, - West: u128, +```rust +fn roll(value: u8) { + match value { + 0 | 1 | 2 => println!("you won!"), + 3 => println!("you can roll again!"), + _ => println!("you lost..."), + } } -# -# #[executable] -# fn main() { -# let direction = Direction::North(10); -# } ``` -A common and particularly useful enum is `Option`, which represents that a value can either be something (`Some(T)`) or nothing (`None`). Cairo does not have null pointers; `Option` should be used to represent the possibility of a missing value. - -## Recursive Types - -Recursive types, where a type can contain another value of the same type, pose a compile-time challenge because Cairo needs to know the size of a type. To enable recursive types, a `box` (which has a known size) can be inserted into the type definition. +## `if let` as an Alternative -An example of a recursive type is a binary tree. The following code demonstrates an attempt to implement a binary tree that won't compile initially due to the recursive nature: +`if let` is syntactic sugar for a `match` that runs code for one pattern and ignores all others, thus losing the exhaustive checking that `match` enforces. An `else` block corresponds to the `_` case in a `match`. -```cairo, noplayground -#[derive(Copy, Drop)] -enum BinaryTree { - Leaf: u32, - Node: (u32, BinaryTree, BinaryTree), +```rust +#[derive(Drop)] +enum Coin { + Penny, + Nickel, + Dime, + Quarter, } #[executable] fn main() { - let leaf1 = BinaryTree::Leaf(1); - let leaf2 = BinaryTree::Leaf(2); - let leaf3 = BinaryTree::Leaf(3); - let node = BinaryTree::Node((4, leaf2, leaf3)); - let _root = BinaryTree::Node((5, leaf1, node)); + let coin = Coin::Quarter; + let mut count = 0; + if let Coin::Quarter = coin { + println!("You got a quarter!"); + } else { + count += 1; + } + println!("{}", count); } ``` -Smart Pointers and Memory Management - -# Smart Pointers and Memory Management - -Cairo's memory is organized into segments. Smart pointers, such as `Felt252Dict` and `Array`, manage these memory segments, offering metadata and additional guarantees. For instance, `Array` tracks its length to prevent overwrites and ensure elements are appended correctly. +--- -## The `Box` Type for Pointer Manipulation +Sources: -The primary smart pointer in Cairo is `Box`. It allows data to be stored in a dedicated "boxed segment" of the Cairo VM, with only a pointer residing in the execution segment. Instantiating a `Box` appends the data of type `T` to the boxed segment. +- https://www.starknet.io/cairo-book/ch09-02-recoverable-errors.html -`Box` is useful in two main scenarios: +--- -1. When a type's size is unknown at compile time, and a fixed size is required. -2. For efficiently transferring ownership of large amounts of data without copying, by storing the data in the boxed segment and only moving the pointer. +### Advanced Pattern Matching and Error Handling -### Storing Data in the Boxed Segment with `Box` +#### Checking Result Variants Without Consumption -The `Box` type facilitates storing data in the boxed segment. +The `is_err` method takes a snapshot of a `Result` value and returns `true` if the `Result` is the `Err` variant, and `false` if it is the `Ok` variant. These methods are useful for checking success or failure without consuming the `Result` value, allowing for further decision-making based on the variant. -```cairo -#[executable] -fn main() { - let b = BoxTrait::new(5_u128); - println!("b = {}", b.unbox()) -} -``` - -This example stores the `u128` value `5` in the boxed segment. While storing a single value this way is uncommon, `Box` is crucial for enabling complex type definitions. +The implementation of the `ResultTrait` can be found elsewhere. -### Enabling Recursive Types with Boxes - -Types with recursive structures can be problematic if they directly contain themselves, as their size cannot be determined at compile time. Using `Box` resolves this by storing recursive data in the boxed segment. The `Box` acts as a pointer, and its size is constant regardless of the data it points to. +#### Example: Handling Overflowing Addition -Consider the `BinaryTree` enum: - -```cairo -mod display; -use display::DebugBinaryTree; +A function signature demonstrating error return for overflow might look like this: -#[derive(Copy, Drop)] -enum BinaryTree { - Leaf: u32, - Node: (u32, Box, Box), -} +```rust +fn u128_overflowing_add(a: u128, b: u128) -> Result; +``` +This function returns the sum in the `Ok` variant if addition succeeds, or the overflowed value in the `Err` variant if it overflows. -#[executable] -fn main() { - let leaf1 = BinaryTree::Leaf(1); - let leaf2 = BinaryTree::Leaf(2); - let leaf3 = BinaryTree::Leaf(3); - let node = BinaryTree::Node((4, BoxTrait::new(leaf2), BoxTrait::new(leaf3))); - let root = BinaryTree::Node((5, BoxTrait::new(leaf1), BoxTrait::new(node))); +This `Result` can be used to create an `Option`-returning function that avoids panicking on overflow: - println!("{:?}", root); +```rust +fn u128_checked_add(a: u128, b: u128) -> Option { + match u128_overflowing_add(a, b) { + Ok(r) => Some(r), + Err(r) => None, + } } ``` -In this modified `BinaryTree` definition, the `Node` variant contains `(u32, Box, Box)`. This structure ensures that the `Node` variant has a known, fixed size (a `u32` plus two pointers), breaking the infinite recursion and allowing the compiler to determine the size of `BinaryTree`. +In `u128_checked_add`, the `match` expression inspects the `Result`. If `Ok(r)`, it returns `Some(r)`; if `Err(r)`, it returns `None`. -### Implementing `Destruct` for Memory Management +#### Example: Parsing with Error Handling -For structs containing types like `Felt252Dict`, the `Destruct` trait must be implemented to define how the struct goes out of scope. +Another common use case involves parsing, where failure results in an error type: -```cairo -# -# use core::dict::Felt252Dict; -# use core::nullable::NullableTrait; -# use core::num::traits::WrappingAdd; -# -# trait MemoryVecTrait { -# fn new() -> V; -# fn get(ref self: V, index: usize) -> Option; -# fn at(ref self: V, index: usize) -> T; -# fn push(ref self: V, value: T) -> (); -# fn set(ref self: V, index: usize, value: T); -# fn len(self: @V) -> usize; -# } -# -# struct MemoryVec { -# data: Felt252Dict>, -# len: usize, -# } -# -impl DestructMemoryVec> of Destruct> { - fn destruct(self: MemoryVec) nopanic { - self.data.squash(); +```rust +fn parse_u8(s: felt252) -> Result { + match s.try_into() { + Some(value) => Ok(value), + None => Err('Invalid integer'), } } ``` -Serialization and Iteration - -# Serialization and Iteration +--- -## Serialization of `u256` +Sources: -A `u256` value in Cairo is serialized as two `felt252` values: +- https://www.starknet.io/cairo-book/ch08-01-generic-data-types.html -- The first `felt252` contains the 128 least significant bits (low part). -- The second `felt252` contains the 128 most significant bits (high part). +--- -Examples: +### Generics and References -- `u256` with value 2 serializes as `[2,0]`. -- `u256` with value \( 2^{128} \) serializes as `[0,1]`. -- `u256` with value \( 2^{129}+2^{128}+20 \) serializes as `[20,3]`. +#### Example: Finding the Smallest Element with Generics -## Serialization of `u512` - -The `u512` type is a struct containing four `felt252` members, each representing a 128-bit limb. +The following code attempts to find the smallest element in a list of generic type `T`, where `T` implements `PartialOrd`. The function receives a snapshot of the array (`@Array`), which avoids the need for `T` to implement `Drop` for the array itself. -## Serialization of Arrays and Spans +```rust +// Given a list of T get the smallest one +// The PartialOrd trait implements comparison operations for T +fn smallest_element>(list: @Array) -> T { + // This represents the smallest element through the iteration + // Notice that we use the desnap (*) operator + let mut smallest = *list[0]; + + // The index we will use to move through the list + let mut index = 1; + + // Iterate through the whole list storing the smallest + while index < list.len() { + if *list[index] < smallest { + smallest = *list[index]; + } + index = index + 1; + } -Arrays and spans are serialized as: -`, ,..., ` + smallest +} -For example, an array of `u256` values `array![10, 20, POW_2_128]` (where `POW_2_128` is \( 2^{128} \)) serializes to `[3,10,0,20,0,0,1]`. +#[executable] +fn main() { + let list: Array = array![5, 3, 10]; -```cairo, noplayground -let POW_2_128: u256 = 0x100000000000000000000000000000000 -let array: Array = array![10, 20, POW_2_128] + // We need to specify that we are passing a snapshot of `list` as an argument + let s = smallest_element(@list); + assert!(s == 3); +} ``` -## Serialization of Enums - -Enums are serialized as: -`,` +#### Trait Requirements for Generic Functions with Desnapping -Enum variant indices are 0-based. +When indexing on `list`, the result is a snap (`@T`). Using the desnap operator (`*`) converts `@T` to `T`. This copy operation requires that `T` implements the `Copy` trait. Furthermore, since variables of type `T` are now created within the function scope (via copying), `T` must also implement the `Drop` trait. -**Example 1:** +#### Corrected Function Signature -```cairo,noplayground -enum Week { - Sunday: (), // Index=0. - Monday: u256, // Index=1. -} -``` +To make the function compile, both `Copy` and `Drop` traits must be explicitly required for the generic type `T`: -| Instance | Index | Type | Serialization | -| ----------------- | ----- | ------ | ------------- | -| `Week::Sunday` | `0` | unit | `[0]` | -| `Week::Monday(5)` | `1` | `u256` | `[1,5,0]` | +```rust +fn smallest_element, impl TCopy: Copy, impl TDrop: Drop>( + list: @Array, +) -> T { + let mut smallest = *list[0]; + let mut index = 1; -**Example 2:** + while index < list.len() { + if *list[index] < smallest { + smallest = *list[index]; + } + index = index + 1; + } -```cairo,noplayground -enum MessageType { - A, - #[default] - B: u128, - C + smallest } ``` -| Instance | Index | Type | Serialization | -| ------------------- | ----- | ------ | ------------- | -| `MessageType::A` | `1` | unit | `[0]` | -| `MessageType::B(6)` | `0` | `u128` | `[1,6]` | -| `MessageType::C` | `2` | unit | `[2]` | +--- -The `#[default]` attribute does not affect serialization. +Sources: -## Serialization of Structs and Tuples +- https://www.starknet.io/cairo-book/ch11-01-closures.html +- https://www.starknet.io/cairo-book/ch11-00-functional-features.html -Structs and tuples are serialized by serializing their members sequentially in the order they appear in their definition. +--- -## Iteration Traits (`Iterator` and `IntoIterator`) +# Closures -The `Iterator` and `IntoIterator` traits facilitate iteration over collections in Cairo. +Cairo's design takes strong inspiration from functional programming, featuring constructs like closures and iterators. Closures are function-like constructs you can store in a variable or pass as arguments to other functions. -- **`Iterator` trait:** Defines a `next` function that returns an `Option`. -- **`IntoIterator` trait:** Defines an `into_iter` method that converts a collection into an iterator. It has an associated type `IntoIter` representing the iterator type. -- **Associated Implementation:** The `Iterator: Iterator` declaration within `IntoIterator` ensures that the returned iterator type (`IntoIter`) itself implements the `Iterator` trait. +## Understanding Closures -This design guarantees type-safe iteration and improves code ergonomics. +Closures are anonymous functions that can capture values from the scope in which they are defined, allowing for code reuse and behavior customization. They were introduced in Cairo 2.9. -```cairo, noplayground -// Collection type that contains a simple array -#[derive(Drop)] -pub struct ArrayIter { - array: Array, -} +When writing Cairo programs, closures provide a way to define behavior inline without creating a separate named function, which is particularly valuable when working with collections or passing behavior as a parameter. + +The following example demonstrates basic usage, array methods utilizing closures (`map`, `filter`), and capturing an environment variable (`x`): -// T is the collection type -pub trait Iterator { - type Item; - fn next(ref self: T) -> Option; +```cairo +#[generate_trait] +impl ArrayExt of ArrayExtTrait { + // Needed in Cairo 2.11.4 because of a bug in inlining analysis. + #[inline(never)] + fn map, F, +Drop, impl func: core::ops::Fn, +Drop>( + self: Array, f: F, + ) -> Array { + let mut output: Array = array![]; + for elem in self { + output.append(f(elem)); + } + output + } } -impl ArrayIterator of Iterator> { - type Item = T; - fn next(ref self: ArrayIter) -> Option { - self.array.pop_front() +#[generate_trait] +impl ArrayFilterExt of ArrayFilterExtTrait { + // Needed in Cairo 2.11.4 because of a bug in inlining analysis. + #[inline(never)] + fn filter< + T, + +Copy, + +Drop, + F, + +Drop, + impl func: core::ops::Fn[Output: bool], + +Drop, + >( + self: Array, f: F, + ) -> Array { + let mut output: Array = array![]; + for elem in self { + if f(elem) { + output.append(elem); + } + } + output } } -/// Turns a collection of values into an iterator -pub trait IntoIterator { - /// The iterator type that will be created - type IntoIter; - impl Iterator: Iterator; +#[executable] +fn main() { + let double = |value| value * 2; + println!("Double of 2 is {}", double(2_u8)); + println!("Double of 4 is {}", double(4_u8)); - fn into_iter(self: T) -> Self::IntoIter; -} + // This won't work because `value` type has been inferred as `u8`. + //println!("Double of 6 is {}", double(6_u16)); -impl ArrayIntoIterator of IntoIterator> { - type IntoIter = ArrayIter; - fn into_iter(self: Array) -> ArrayIter { - ArrayIter { array: self } - } -} -``` + let sum = |x: u32, y: u32, z: u16| { + x + y + z.into() + }; + println!("Result: {}", sum(1, 2, 3)); -Advanced Language Features + let x = 8; + let my_closure = |value| { + x * (value + 3) + }; -Ownership, References, and Snapshots + println!("my_closure(1) = {}", my_closure(1)); -# Ownership, References, and Snapshots + let double = array![1, 2, 3].map(|item: u32| item * 2); + let another = array![1, 2, 3].map(|item: u32| { + let x: u64 = item.into(); + x * x + }); -Cairo's ownership system can be inconvenient when you need to use a value in a function without moving it or returning it explicitly. To address this, Cairo provides references and snapshots. + println!("double: {:?}", double); + println!("another: {:?}", another); -## References and Snapshots + let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); + println!("even: {:?}", even); +} +``` -Passing values into and out of functions can be tedious, especially when you want to retain ownership of the original value. While returning multiple values using tuples is possible, it's verbose. References and snapshots allow functions to use values without taking ownership, eliminating the need to return them. +## Closure Syntax and Type Inference -### Snapshots +The closure's arguments are placed between pipes (`|`). Types for arguments and the return value are usually inferred from usage, unlike named functions where types form an explicit interface. The body is an expression, optionally enclosed in curly braces `{}` if it spans multiple lines. -Snapshots provide an immutable view of a value at a specific point in execution, bypassing the strict rules of the linear type system. They are passed by value, meaning the entire snapshot is copied to the function's stack. For large data structures, `Box` can be used to avoid copying if mutation is not required. +Type annotations can be added for clarity: +```cairo +fn add_one_v1 (x: u32) -> u32 { x + 1 } +let add_one_v2 = |x: u32| -> u32 { x + 1 }; +let add_one_v3 = |x| { x + 1 }; +let add_one_v4 = |x| x + 1 ; +``` -The `@` syntax creates a snapshot, and functions can accept snapshots as parameters, indicated by `@` in the function signature. When a function parameter that is a snapshot goes out of scope, the snapshot is dropped, but the underlying original value remains unaffected. +The compiler infers one concrete type for parameters and the return value upon first use, locking the closure to that type. Subsequent attempts to use a different type will result in a type error. ```cairo +//TAG: does_not_compile #[executable] fn main() { - let arr1: Array = array![]; - - let (arr2, len) = calculate_length(arr1); -} - -fn calculate_length(arr: Array) -> (Array, usize) { - let length = arr.len(); // len() returns the length of an array + let example_closure = |x| x; - (arr, length) + let s = example_closure(5_u64); + let n = example_closure(5_u32); } ``` +The compiler infers `u64` from the first call (`s`), and the second call (`n`) fails because the closure implementation is locked to the `Fn` trait for `u64` arguments. -```cairo -let second_length = calculate_length(@arr1); // Calculate the current length of the array -``` - -```cairo, noplayground -fn calculate_area( - rec_snapshot: @Rectangle // rec_snapshot is a snapshot of a Rectangle -) -> u64 { - *rec_snapshot.height * *rec_snapshot.width -} // Here, rec_snapshot goes out of scope and is dropped. -// However, because it is only a view of what the original `rec` contains, the original `rec` can still be used. -``` +## Capturing the Environment -### Desnap Operator +Closures can include bindings from their enclosing scope. For example, `my_closure` in the first code block uses the captured binding `x = 8`. -The `desnap` operator `*` converts a snapshot back into a regular variable. This operation is only possible for `Copy` types and is a free operation as it reuses the old value without modification. +Note that, at the moment, closures are still not allowed to capture mutable variables, but this is expected to be supported in future Cairo versions. -```cairo -#[derive(Drop)] -struct Rectangle { - height: u64, - width: u64, -} +--- -#[executable] -fn main() { - let rec = Rectangle { height: 3, width: 10 }; - let area = calculate_area(@rec); - println!("Area: {}", area); -} +Sources: -fn calculate_area(rec: @Rectangle) -> u64 { - // As rec is a snapshot to a Rectangle, its fields are also snapshots of the fields types. - // We need to transform the snapshots back into values using the desnap operator `*`. - // This is only possible if the type is copyable, which is the case for u64. - // Here, `*` is used for both multiplying the height and width and for desnapping the snapshots. - *rec.height * *rec.width -} -``` +- https://www.starknet.io/cairo-book/ch12-04-hash.html +- https://www.starknet.io/cairo-book/ch102-04-serialization-of-cairo-types.html +- https://www.starknet.io/cairo-book/appendix-03-derivable-traits.html -Generics +--- -# Generics +# Data Serialization and Hashing -Generics enable the creation of definitions for item declarations, such as structs and functions, that can operate on many different concrete data types. This allows for writing reusable code that works with multiple types, thus avoiding code duplication and enhancing maintainability. In Cairo, generics can be used when defining functions, structs, enums, traits, implementations, and methods. +## Data Serialization using Serde -## Generic Functions +Serialization transforms data structures into a format for storage or transmission (e.g., an array), while deserialization reverses this process. The `Serde` trait provides `serialize` and `deserialize` implementations. -Making a function generic means it can operate on different types, avoiding the need for multiple, type-specific implementations. Generics are placed in the function signature. +Deriving `Drop` is required when serializing a structure owned by the current scope, as `serialize` takes a snapshot. -For example, a function to find the largest list can be implemented once using generics: +### Example: Serializing a Struct -```cairo -// Specify generic type T between the angulars -fn largest_list(l1: Array, l2: Array) -> Array { - if l1.len() > l2.len() { - l1 - } else { - l2 - } +```rust +#[derive(Serde, Drop)] +struct A { + item_one: felt252, + item_two: felt252, } #[executable] fn main() { - let mut l1 = array![1, 2]; - let mut l2 = array![3, 4, 5]; - - // There is no need to specify the concrete type of T because - // it is inferred by the compiler - let l3 = largest_list(l1, l2); + let first_struct = A { item_one: 2, item_two: 99 }; + let mut output_array = array![]; + first_struct.serialize(ref output_array); + panic(output_array); } ``` +Running this results in `Run panicked with [2, 99 ('c'), ]`. Deserialization can convert the array back into the struct using `Serde::::deserialize(ref span_array).unwrap()`. -## Generic Methods and Traits +## Serialization Details for Complex Types -Cairo allows defining generic methods within generic traits. Consider a `Wallet` struct with generic types for balance and address: +### Types with Non-Trivial Serialization -```cairo,noplayground -struct Wallet { - balance: T, - address: U, -} -``` +Data types using more than 252 bits require special serialization handling: +* Unsigned integers larger than 252 bits: `u256` and `u512`. +* Arrays and spans. +* Enums. +* Structs and tuples. +* Byte arrays (strings). -A trait can be defined to mix two wallets of potentially different generic types. An initial naive implementation might not compile due to how instances are dropped: +Basic types are serialized as a single-member list containing one `felt252` value. -```cairo,noplayground -// This does not compile! -trait WalletMixTrait { - fn mixup(self: Wallet, other: Wallet) -> Wallet; -} +### Serialization of `u256` -impl WalletMixImpl of WalletMixTrait { - fn mixup(self: Wallet, other: Wallet) -> Wallet { - Wallet { balance: self.balance, address: other.address } - } -} -``` +A `u256` value is represented by two `felt252` values: +1. The 128 least significant bits (low part). +2. The 128 most significant bits (high part). -This fails because the compiler needs to know how to drop the generic types `T2` and `U2` if they are not used or if they implement `Drop`. The corrected implementation requires specifying that the generic types implement the `Drop` trait: +| Decimal Value | Binary Split (High | Low) | Serialized Array | +| :--- | :--- | :--- | :--- | +| 2 | 0...0 | 0...10 | `[2, 0]` | +| $2^{128}$ | 0...01 | 0...0 | `[0, 1]` | +| $2^{129}+2^{128}+20$ | 0...011 | 0...10100 | `[20, 3]` | -```cairo -trait WalletMixTrait { - fn mixup, U2, +Drop>( - self: Wallet, other: Wallet, - ) -> Wallet; -} +### Serialization of `u512` -impl WalletMixImpl, U1, +Drop> of WalletMixTrait { - fn mixup, U2, +Drop>( - self: Wallet, other: Wallet, - ) -> Wallet { - Wallet { balance: self.balance, address: other.address } - } -} -``` +The `u512` type is a struct containing four `felt252` members, each representing a 128-bit limb. -Traits and Implementations +### Serialization of Structs and Tuples -# Traits and Implementations +Members are serialized in the order they appear in the definition. -## `#[generate_trait]` Attribute +For `struct MyStruct { a: u256, b: felt252, c: Array }`, if $a=2$, $b=5$, and $c=[1,2,3]$, the serialization is: +| Member | Type | Serialization | +| :--- | :--- | :--- | +| `a: 2` | `u256` | `[2,0]` | +| `b: 5` | `felt252` | `5` | +| `c: [1,2,3]` | `felt252` array of size 3 | `[3,1,2,3]` | -The `#[generate_trait]` attribute can be placed above a trait implementation. It instructs the compiler to automatically generate the corresponding trait definition, eliminating the need for explicit trait definition when the trait is not intended for reuse. +Total serialization: `[2,0,5,3,1,2,3]`. -```cairo -#[derive(Copy, Drop)] -struct Rectangle { - width: u64, - height: u64, -} +### Serialization of Byte Arrays -#[generate_trait] -impl RectangleImpl of RectangleTrait { - fn area(self: @Rectangle) -> u64 { - (*self.width) * (*self.height) - } -} +A `ByteArray` (string) is serialized via a struct containing: +* `data: Array`: Contains 31-byte chunks, where each `felt252` holds 31 bytes. +* `pending_word: felt252`: Remaining bytes (at most 30). +* `pending_word_len: usize`: Length of `pending_word` in bytes. -#[executable] -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - println!("Area is {}", rect1.area()); -} +**Example (`hello`, 5 bytes: `0x68656c6c6f`):** +``` +0, // Number of 31-byte words in the data array. +0x68656c6c6f, // Pending word +5 // Length of the pending word, in bytes ``` -## Snapshots and References +## Hashing in Cairo -Methods can accept `self` as a snapshot (`@`) if they don't modify the instance, or as a mutable reference (`ref self`) to allow modifications. +Hashing converts input data (message) of any length into a fixed-size hash value deterministically. This is crucial for Merkle trees and data integrity. -```cairo -#[generate_trait] -impl RectangleImpl of RectangleTrait { - fn area(self: @Rectangle) -> u64 { - (*self.width) * (*self.height) - } - fn scale(ref self: Rectangle, factor: u64) { - self.width *= factor; - self.height *= factor; - } -} +### Hash Functions in Cairo -#[executable] -fn main() { - let mut rect2 = Rectangle { width: 10, height: 20 }; - rect2.scale(2); - println!("The new size is (width: {}, height: {})", rect2.width, rect2.height); -} -``` +The Cairo core library provides two native hash functions: -## Default Implementations +* **Pedersen**: A cryptographic algorithm based on elliptic curve cryptography and the Elliptic Curve Discrete Logarithm Problem (ECDLP), ensuring one-way security. +* **Poseidon**: Designed for efficiency in algebraic circuits, particularly for Zero-Knowledge proof systems like STARKs. It uses a sponge construction with a three-element state permutation. -Traits can provide default behavior for methods, allowing implementers to either use the default or override it. +### Using Hash Functions -```cairo -// In src/lib.cairo -mod aggregator { - pub trait Summary { - fn summarize(self: @T) -> ByteArray { - "(Read more...)" - } - } +To hash, import relevant traits. Hashing involves initialization, updating the state, and finalizing the result. - #[derive(Drop)] - pub struct NewsArticle { - pub headline: ByteArray, - pub location: ByteArray, - pub author: ByteArray, - pub content: ByteArray, - } +1. **Initialization**: `PoseidonTrait::new() -> HashState` or `PedersenTrait::new(base: felt252) -> HashState`. +2. **Update**: `update(self: HashState, value: felt252) -> HashState` or `update_with(self: S, value: T) -> S`. +3. **Finalization**: `finalize(self: HashState) -> felt252`. - impl NewsArticleSummary of Summary {} +#### Poseidon Hashing Example (Requires `#[derive(Hash)]`) - #[derive(Drop)] - pub struct Tweet { - pub username: ByteArray, - pub content: ByteArray, - pub reply: bool, - pub retweet: bool, - } +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; - impl TweetSummary of Summary { - fn summarize(self: @Tweet) -> ByteArray { - format!("{}: {}", self.username, self.content) - } - } +#[derive(Drop, Hash)] +struct StructForHash { + first: felt252, + second: felt252, + third: (u32, u32), + last: bool, } -use aggregator::{NewsArticle, Summary}; - #[executable] -fn main() { - let news = NewsArticle { - headline: "Cairo has become the most popular language for developers", - location: "Worldwide", - author: "Cairo Digger", - content: "Cairo is a new programming language for zero-knowledge proofs", - }; +fn main() -> felt252 { + let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; - println!("New article available! {}", news.summarize()); + let hash = PoseidonTrait::new().update_with(struct_to_hash).finalize(); + hash } ``` -This code prints `New article available! (Read more...)`. - -## Managing and Using External Traits - -To use trait methods, ensure the relevant traits and their implementations are imported. This might involve importing both the trait and its implementation if they are in separate modules. - -## Impl Aliases +#### Pedersen Hashing Example -Implementations can be aliased upon import, which is useful for instantiating generic implementations with concrete types. +Pedersen requires an initial `felt252` base state. Hashing a struct can involve serializing it first, or using `update_with` with an arbitrary base state. -```cairo -trait Two { - fn two() -> T; -} +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::pedersen::PedersenTrait; -mod one_based { - pub impl TwoImpl< - T, +Copy, +Drop, +Add, impl One: core::num::traits::One, - > of super::Two { - fn two() -> T { - One::one() + One::one() - } - } +#[derive(Drop, Hash, Serde, Copy)] +struct StructForHash { + first: felt252, + second: felt252, + third: (u32, u32), + last: bool, } -pub impl U8Two = one_based::TwoImpl; -pub impl U128Two = one_based::TwoImpl; -``` - -This allows a generic implementation to be defined privately while exposing specific instantiations publicly. +#[executable] +fn main() -> (felt252, felt252) { + let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; -## Negative Impls + // hash1 is the result of hashing a struct with a base state of 0 + let hash1 = PedersenTrait::new(0).update_with(struct_to_hash).finalize(); -Negative implementations allow a trait to be implemented for types that _do not_ implement another specified trait. For example, a `Consumer` trait could be implemented for any type `T` that does not implement the `Producer` trait. + let mut serialized_struct: Array = ArrayTrait::new(); + Serde::serialize(@struct_to_hash, ref serialized_struct); + let first_element = serialized_struct.pop_front().unwrap(); + let mut state = PedersenTrait::new(first_element); -## Associated Types (Experimental) + for value in serialized_struct { + state = state.update(value); + } -Cairo 2.9 offers an experimental feature to specify associated types of traits using `experimental-features = ["associated_item_constraints"]` in `Scarb.toml`. This can be used, for instance, to define a `filter` function for arrays where the closure's return type is constrained to `bool`. + // hash2 is the result of hashing only the fields of the struct + let hash2 = state.finalize(); -```cairo -#[generate_trait] -impl ArrayFilterExt of ArrayFilterExtTrait { - fn filter< - T, - +Copy, - +Drop, - F, - +Drop, - impl func: core::ops::Fn[Output: bool], - +Drop, - >( - self: Array, f: F, - ) -> Array { - let mut output: Array = array![]; - for elem in self { - if f(elem) { - output.append(elem); - } - } - output - } + (hash1, hash2) } ``` -## Manual `Destruct` Implementation - -For generic structs containing types that require specific cleanup (like `Felt252Dict`), a manual implementation of the `Destruct` trait might be necessary if `#[derive(Destruct)]` is not sufficient or applicable. +### Advanced Hashing: Hashing Arrays with Poseidon -```cairo -impl UserDatabaseDestruct, +Felt252DictValue> of Destruct> { - fn destruct(self: UserDatabase) nopanic { - self.balances.squash(); - } -} -``` +For hashing `Span` or `Array` within a structure (where deriving `Hash` might fail due to the array type), use the built-in function `poseidon_hash_span`. -Advanced Type System Features +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::{PoseidonTrait, poseidon_hash_span}; -# Negative Implementations +#[derive(Drop)] +struct StructForHashArray { + first: felt252, + second: felt252, + third: Array, +} -Negative implementations, also known as negative traits or negative bounds, allow expressing that a type does not implement a certain trait when defining a generic trait implementation. This enables writing implementations applicable only when another implementation does not exist in the current scope. +#[executable] +fn main() { + let struct_to_hash = StructForHashArray { first: 0, second: 1, third: array![1, 2, 3, 4, 5] }; -To use this feature, you must enable it in your `Scarb.toml` file with `experimental-features = ["negative_impls"]` under the `[package]` section. + let mut hash = PoseidonTrait::new().update(struct_to_hash.first).update(struct_to_hash.second); + let hash_felt252 = hash.update(poseidon_hash_span(struct_to_hash.third.span())).finalize(); +} +``` -Consider a scenario where you want all types to implement the `Consumer` trait by default, but restrict types that are already `Producer`s from being `Consumer`s. Negative implementations can enforce this. +--- -```cairo -#[derive(Drop)] -struct ProducerType {} +Sources: -#[derive(Drop, Debug)] -struct AnotherType {} +- https://www.starknet.io/cairo-book/ch12-08-printing.html -#[derive(Drop, Debug)] -struct AThirdType {} +--- -trait Producer { - fn produce(self: T) -> u32; -} +### Printing Standard Data Types -trait Consumer { - fn consume(self: T, input: u32); -} +Cairo uses two macros for printing standard data types: -impl ProducerImpl of Producer { - fn produce(self: ProducerType) -> u32 { - 42 - } -} +* `println!`: prints on a new line. +* `print!`: prints inline. -// This implementation consumes if the type T does NOT implement Producer -impl TConsumerImpl, +Drop, -Producer> of Consumer { - fn consume(self: T, input: u32) { - println!("{:?} consumed value: {}", self, input); - } -} +Both take a `ByteArray` string as the first parameter, which can contain placeholders `{}` for values given as subsequent parameters, or named placeholders `{variable_name}`. +```cairo #[executable] fn main() { - let producer = ProducerType {}; - let another_type = AnotherType {}; - let third_type = AThirdType {}; - let production = producer.produce(); + let a = 10; + let b = 20; + let c = 30; - // producer.consume(production); // Invalid: ProducerType does not implement Consumer - another_type.consume(production); - third_type.consume(production); + println!("Hello world!"); + println!("{} {} {}", a, b, c); // 10 20 30 + println!("{c} {a} {}", b); // 30 10 20 } ``` -In this example, `ProducerType` implements `Producer`. `AnotherType` and `AThirdType` do not. The `Consumer` trait is implemented for any type `T` that derives `Debug` and `Drop`, and crucially, does _not_ implement `Producer`. Consequently, `producer.consume(production)` is invalid, while `another_type.consume(production)` and `third_type.consume(production)` are valid. +These macros use the `Display` trait. Attempting to print complex data types without implementing `Display` results in an error. -Function Safety and Panics +### Formatting -## Function Safety and Panics +The `format!` macro handles string formatting, returning a `ByteArray` instead of printing to the screen. It is preferred over string concatenation using the `+` operator because `format!` does not consume its parameters (it uses snapshots). -## `nopanic` Notation +```cairo +use core::fmt::ByteArray; -The `nopanic` notation indicates that a function will never panic. Only `nopanic` functions can be called within another function annotated as `nopanic`. +#[executable] +fn main() { + let s1: ByteArray = "tic"; + let s2: ByteArray = "tac"; + let s3: ByteArray = "toe"; + let s = s1 + "-" + s2 + "-" + s3; + // using + operator consumes the strings, so they can't be used again! -A function guaranteed to never panic: + let s1: ByteArray = "tic"; + let s2: ByteArray = "tac"; + let s3: ByteArray = "toe"; + let s = format!("{s1}-{s2}-{s3}"); // s1, s2, s3 are not consumed by format! + // or + let s = format!("{}-{}-{}", s1, s2, s3); -```cairo,noplayground -fn function_never_panic() -> felt252 nopanic { - 42 + println!("{}", s); } ``` -This function will always return `42` and is guaranteed not to panic. +### Printing Custom Data Types -Conversely, a function declared as `nopanic` but containing code that might panic will result in a compilation error. For example, using `assert!` or equality checks (`==`) within a `nopanic` function is not allowed: +Custom data types require manual implementation of the `Display` trait to be used with `{}` placeholders in printing macros. The trait signature is: -```cairo,noplayground -fn function_never_panic() nopanic { - assert!(1 == 1, "what"); +```cairo +trait Display { + fn fmt(self: @T, ref f: Formatter) -> Result<(), Error>; } ``` -Compiling such a function yields an error indicating that a `nopanic` function calls another function that may panic. +The `Formatter` struct contains the `buffer: ByteArray` where the formatted output is appended. -## `panic_with` Attribute +Implementing `Display` for a custom `Point` struct using `format!`: -The `panic_with` attribute can be applied to functions returning `Option` or `Result`. It takes two arguments: the data to be used as the panic reason and the name for a generated wrapper function. This creates a wrapper that panics with the specified data if the annotated function returns `None` or `Err`. +```cairo +use core::fmt::{Display, Error, Formatter}; -Example: +#[derive(Copy, Drop)] +struct Point { + x: u8, + y: u8, +} -```cairo -#[panic_with('value is 0', wrap_not_zero)] -fn wrap_if_not_zero(value: u128) -> Option { - if value == 0 { - None - } else { - Some(value) +impl PointDisplay of Display { + fn fmt(self: @Point, ref f: Formatter) -> Result<(), Error> { + let str: ByteArray = format!("Point ({}, {})", *self.x, *self.y); + f.buffer.append(@str); + Ok(()) } } #[executable] fn main() { - wrap_if_not_zero(0); // this returns None - wrap_not_zero(0); // this panics with 'value is 0' + let p = Point { x: 1, y: 3 }; + println!("{}", p); // Point: (1, 3) } ``` -Storage Optimization and Modularity +The `write!` and `writeln!` macros can also be used within the `fmt` implementation to write formatted strings directly to the `Formatter`'s buffer. -# Storage Optimization and Modularity - -## Storage Packing +```cairo +use core::fmt::Formatter; -The `StorePacking` trait allows for optimizing storage by packing multiple fields into a single storage variable. This is achieved using bitwise operations: +#[executable] +fn main() { + let mut formatter: Formatter = Default::default(); + let a = 10; + let b = 20; + write!(formatter, "hello"); + write!(formatter, "world"); + write!(formatter, " {a} {b}"); -- **Shifts:** `TWO_POW_8` and `TWO_POW_40` are used for left shifts during packing and right shifts during unpacking. -- **Masks:** `MASK_8` and `MASK_32` are used to isolate specific variables during unpacking. -- **Type Conversion:** Variables are converted to `u128` to enable bitwise operations. + println!("{}", formatter.buffer); // helloworld 10 20 +} +``` -This technique is applicable to any set of fields whose combined bit sizes fit within a packed storage type (e.g., `u256`, `u512`). Custom structs and packing/unpacking logic can be defined. +### Print in Hexadecimal -The compiler automatically utilizes the `StoreUsingPacking` implementation of the `Store` trait if a type implements `StorePacking`. A crucial detail is that the type produced by `StorePacking::pack` must also implement `Store` for `StoreUsingPacking` to function correctly. Typically, packing is done into `felt252` or `u256`, but custom types must also implement `Store`. +Integer values can be printed in hexadecimal format using the `{:x}` notation, which relies on the `LowerHex` trait. This trait is implemented for basic types, `felt252`, `NonZero`, and Starknet types like `ContractAddress` and `ClassHash`. The `LowerHex` trait can also be implemented for custom types. -```rust -// Example demonstrating storage packing (conceptual, actual implementation details may vary) -const TWO_POW_8: u128 = 1 << 8; -const TWO_POW_40: u128 = 1 << 40; -const MASK_8: u128 = (1 << 8) - 1; -const MASK_32: u128 = (1 << 32) - 1; +### Print Debug Traces -#[derive(Copy, Drop, Serde)] -struct MyStruct { - field1: u8, - field2: u32, - field3: u8, -} +The `Debug` trait allows printing complex data types, especially useful for debugging. It is used by adding `:?` within placeholders (e.g., `println!("{:?}", my_struct)`). -impl MyStruct { - fn pack(self: MyStruct) -> u128 { - let mut packed: u128 = 0; - packed |= (self.field1 as u128) << 40; // field1 at bits 40-47 - packed |= (self.field2 as u128) << 8; // field2 at bits 8-39 - packed |= self.field3 as u128; // field3 at bits 0-7 - packed - } +* It is implemented by default for basic data types. +* For complex data types, it can be derived using `#[derive(Debug)]`, provided all contained types also implement `Debug`. +* `assert_xx!` macros in tests require implemented `Debug` traits for provided values. - fn unpack(packed: u128) -> MyStruct { - let field1 = ((packed >> 40) & MASK_8) as u8; - let field2 = ((packed >> 8) & MASK_32) as u32; - let field3 = (packed & MASK_8) as u8; - MyStruct { field1, field2, field3 } - } -} -``` +--- -## Components +Sources: -Components offer a Lego-like approach to building smart contracts by providing modular add-ons. They encapsulate reusable logic and storage, allowing developers to easily incorporate specific functionalities into their contracts without reimplementing them. This separation of core contract logic from additional features enhances modularity and reduces potential bugs. +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html +- https://www.starknet.io/cairo-book/ch12-01-custom-data-structures.html +- https://www.starknet.io/cairo-book/ch04-01-what-is-ownership.html +- https://www.starknet.io/cairo-book/ch04-02-references-and-snapshots.html +- https://www.starknet.io/cairo-book/ch12-02-smart-pointers.html +- https://www.starknet.io/cairo-book/ch03-01-arrays.html +- https://www.starknet.io/cairo-book/ch102-04-serialization-of-cairo-types.html +- https://www.starknet.io/cairo-book/ch04-00-understanding-ownership.html +- https://www.starknet.io/cairo-book/ch05-02-an-example-program-using-structs.html -Functional Programming: Closures and Iterators +--- -# Functional Programming: Closures and Iterators +--- -Closures are anonymous functions that can be stored in variables or passed as arguments to other functions. Unlike regular functions, closures have the ability to capture values from the scope in which they are defined. This allows for code reuse and behavior customization. +Sources: -> Note: Closures were introduced in Cairo 2.9 and are still under development. +- https://www.starknet.io/cairo-book/ch102-04-serialization-of-cairo-types.html +- https://www.starknet.io/cairo-book/ch03-01-arrays.html +- https://www.starknet.io/cairo-book/ch05-02-an-example-program-using-structs.html -## Understanding Closures +--- -Closures provide a way to define behavior inline, without needing to create a separate named function. They are particularly useful when working with collections, error handling, or any situation where a function needs to be passed as a parameter to customize behavior. +# Core Data Structures and Serialization -Under the hood, closures are implemented using the `FnOnce` and `Fn` traits. `FnOnce` is for closures that consume captured variables, while `Fn` is for closures that capture only copyable variables. +## Arrays in Cairo -## Implementing Your Functional Programming Patterns with Closures +An array in Cairo is a collection of elements of the same type, utilizing the `ArrayTrait` trait from the core library. Arrays function as queues: their values cannot be modified once written, meaning elements can only be appended to the end and removed from the front. -Closures can be passed as function arguments, a mechanism heavily utilized in functional programming through functions like `map`, `filter`, and `reduce`. +### Creating an Array -Here's a potential implementation of `map` to apply a function to all items in an array: +Arrays are instantiated using `ArrayTrait::new()`. The type can be specified during instantiation: -```cairo, noplayground -#[generate_trait] -impl ArrayExt of ArrayExtTrait { - // Needed in Cairo 2.11.4 because of a bug in inlining analysis. - #[inline(never)] - fn map, F, +Drop, impl func: core::ops::Fn, +Drop>( - self: Array, f: F, - ) -> Array { - let mut output: Array = array![]; - for elem in self { - output.append(f(elem)); - } - output - } +```rust +#[executable] +fn main() { + let mut a = ArrayTrait::new(); + a.append(0); + a.append(1); + a.append(2); } +``` -#[generate_trait] -impl ArrayFilterExt of ArrayFilterExtTrait { - // Needed in Cairo 2.11.4 because of a bug in inlining analysis. - #[inline(never)] - fn filter< - T, - +Copy, - +Drop, - F, - +Drop, - impl func: core::ops::Fn[Output: bool], - +Drop, - >( - self: Array, f: F, - ) -> Array { - let mut output: Array = array![]; - for elem in self { - if f(elem) { - output.append(elem); - } - } - output - } +Type specification examples: +```rust +let mut arr = ArrayTrait::::new(); +``` +or +```rust +let mut arr:Array = ArrayTrait::new(); +``` + +### Updating an Array + +Elements are added to the end using `append()`: + +```rust +#[executable] +fn main() { + let mut a = ArrayTrait::new(); + a.append(0); + a.append(1); + a.append(2); } +``` +Elements are removed only from the front using `pop_front()`, which returns an `Option` containing the removed element or `None` if empty: + +```rust #[executable] fn main() { - let double = |value| value * 2; - println!("Double of 2 is {}", double(2_u8)); - println!("Double of 4 is {}", double(4_u8)); + let mut a = ArrayTrait::new(); + a.append(10); + a.append(1); + a.append(2); - // This won't work because `value` type has been inferred as `u8`. - //println!("Double of 6 is {}", double(6_u16)); + let first_value = a.pop_front().unwrap(); + println!("The first value is {}", first_value); +} +``` - let sum = |x: u32, y: u32, z: u16| { - x + y + z.into() - }; - println!("Result: {}", sum(1, 2, 3)); +## Serialization Formats - let x = 8; - let my_closure = |value| { - x * (value + 3) - }; +### Array Serialization - println!("my_closure(1) = {}", my_closure(1)); +Arrays are serialized in the format: `, ,..., `. - let double = array![1, 2, 3].map(|item: u32| item * 2); - let another = array![1, 2, 3].map(|item: u32| { - let x: u64 = item.into(); - x * x - }); +For an array of `u256` values like `array![10, 20, POW_2_128]`, where each `u256` is two `felt252` values, the serialization is: `[3,10,0,20,0,0,1]`. - println!("double: {:?}", double); - println!("another: {:?}", another); +### Enum Serialization - let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); - println!("even: {:?}", even); +An enum is serialized as: `,`. Enum variant indices are 0-based. + +**Example 1 (`Week` enum):** +```rust +enum Week { + Sunday: (), // Index=0. + Monday: u256, // Index=1. +} +``` +| Instance | Index | Type | Serialization | +| --- | --- | --- | --- | +| `Week::Sunday` | `0` | unit | `[0]` | +| `Week::Monday(5)` | `1` | `u256` | `[1,5,0]` | + +**Example 2 (`MessageType` enum):** +```rust +enum MessageType { + A, + #[default] + B: u128, + C } ``` +| Instance | Index | Type | Serialization | +| --- | --- | --- | --- | +| `MessageType::A` | `0` | unit | `[0]` | +| `MessageType::B(6)` | `1` | `u128` | `[1,6]` | +| `MessageType::C` | `2` | unit | `[2]` | -Resource Management and Memory Safety +### String Serialization (Long Strings) -# Resource Management and Memory Safety +For strings longer than 31 bytes, serialization involves 31-byte words, followed by a pending word and its length: -While Cairo's memory model is immutable, the `Felt252Dict` type can be used to simulate mutable data structures, effectively hiding the complexity of the underlying memory model. +For the string `Long string, more than 31 characters.`: +``` +1, // Number of 31-byte words in the array construct. +0x4c6f6e6720737472696e672c206d6f7265207468616e203331206368617261, // 31-byte word. +0x63746572732e, // Pending word +6 // Length of the pending word, in bytes +``` -## Smart Pointers +--- -Smart pointers are data structures that act like pointers but include additional metadata and capabilities. They originated in C++ and are also found in languages like Rust. In Cairo, smart pointers ensure memory is not addressed in an unsafe manner that could lead to unprovable programs. They achieve this through strict type checking and ownership rules, providing a safe way to access memory. +Sources: -Operator Overloading and Hashing +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html +- https://www.starknet.io/cairo-book/ch12-01-custom-data-structures.html +- https://www.starknet.io/cairo-book/ch04-01-what-is-ownership.html -# Operator Overloading and Hashing +--- -## Operator Overloading +## Felt252Dict: Key-Value Storage Implementation -Operator overloading allows redefining standard operators (like `+`, `-`) for user-defined types, making code syntax more intuitive. In Cairo, this is achieved by implementing specific traits associated with each operator. However, it should be used judiciously to avoid confusion and maintainability issues. +`Felt252Dict` is a built-in dictionary type in Cairo that overcomes the limitation of immutable memory by simulating mutable key-value storage behavior. -For example, combining two `Potion` structs, which have `health` and `mana` fields, can be done using the `+` operator by implementing the `Add` trait: +### Basic Usage and Value Rewriting -```cairo -struct Potion { - health: felt252, - mana: felt252, -} +Instances of `Felt252Dict` can be created using `Default::default()`. Operations like insertion and retrieval are defined in the `Felt252DictTrait` trait, utilizing methods like `insert` and `get`. -impl PotionAdd of Add { - fn add(lhs: Potion, rhs: Potion) -> Potion { - Potion { health: lhs.health + rhs.health, mana: lhs.mana + rhs.mana } - } -} +`Felt252Dict` effectively allows overwriting stored values for a given key. For example: -#[executable] -fn main() { - let health_potion: Potion = Potion { health: 100, mana: 0 }; - let mana_potion: Potion = Potion { health: 0, mana: 100 }; - let super_potion: Potion = health_potion + mana_potion; - // Both potions were combined with the `+` operator. - assert!(super_potion.health == 100); - assert!(super_potion.mana == 100); -} ``` +use core::dict::Felt252Dict; -## Hashing +#[executable] +fn main() { + let mut balances: Felt252Dict = Default::default(); -### When to Use Them? + // Insert Alex with 100 balance + balances.insert('Alex', 100); + // Check that Alex has indeed 100 associated with him + let alex_balance = balances.get('Alex'); + assert!(alex_balance == 100, "Alex balance is not 100"); -Pedersen was the first hash function used on Starknet, often for computing storage variable addresses. However, Poseidon is now the recommended hash function as it is cheaper and faster when working with STARK proofs. + // Insert Alex again, this time with 200 balance + balances.insert('Alex', 200); + // Check the new balance is correct + let alex_balance_2 = balances.get('Alex'); + assert!(alex_balance_2 == 200, "Alex balance is not 200"); +} +``` -### Working with Hashes +### Zero Initialization and Immutability Constraints -The `Hash` trait is implemented for all types that can be converted to `felt252`. For custom structs, deriving `Hash` allows them to be hashed easily, provided all their fields are themselves hashable. Structs containing unhashable types like `Array` or `Felt252Dict` cannot derive `Hash`. +When a `Felt252Dict` is instantiated, all keys are internally initialized to zero. Consequently, querying a non-existent key returns 0. This also means there is no mechanism to delete data from a dictionary. -The `HashStateTrait` and `HashStateExTrait` define basic methods for managing hash states: initializing, updating with values, and finalizing the computation. +### Dictionaries Underneath -```cairo,noplayground -/// A trait for hash state accumulators. -trait HashStateTrait { - fn update(self: S, value: felt252) -> S; - fn finalize(self: S) -> felt252; -} +To simulate mutability within Cairo's immutable memory system, `Felt252Dict` is implemented as a list of entries, conceptually similar to `Array>`. Each `Entry` records an interaction and has three fields: -/// Extension trait for hash state accumulators. -trait HashStateExTrait { - /// Updates the hash state with the given value. - fn update_with(self: S, value: T) -> S; -} +1. `key`: Identifies the key. +2. `previous_value`: The value held at `key` before this interaction. +3. `new_value`: The value held at `key` after this interaction. -/// A trait for values that can be hashed. -trait Hash> { - /// Updates the hash state with the given value. - fn update_state(state: S, value: T) -> S; +``` +struct Entry { + key: felt252, + previous_value: T, + new_value: T, } ``` -### Advanced Hashing: Hashing Arrays with Poseidon - -To hash a `Span` or a struct containing one, use the built-in function `poseidon_hash_span`. This is necessary for types like `Array` which cannot derive `Hash` directly. - -First, import the necessary traits and function: +Every interaction generates a new `Entry`: +* `get`: Registers an entry where `previous_value` equals `new_value`. +* `insert`: Registers an entry where `new_value` is the inserted element, and `previous_value` is the last value associated with the key (or zero if it's the first entry). -```cairo,noplayground -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::{PoseidonTrait, poseidon_hash_span}; -``` +This approach avoids rewriting memory cells, instead creating new entries for every access. For example, sequential operations on 'Alex' (insert 100, insert 200) and 'Maria' (insert 50, then read) yield entries like: -Define the struct. Deriving `Hash` on this struct would fail due to the `Array` field. +| key | previous | new | +| --- | --- | --- | +| Alex | 0 | 100 | +| Maria | 0 | 50 | +| Alex | 100 | 200 | +| Maria | 50 | 50 | -```cairo, noplayground -#[derive(Drop)] -struct StructForHashArray { - first: felt252, - second: felt252, - third: Array, -} -``` +### Dictionary Destruction and Squashing -The following example demonstrates hashing this struct. A `HashState` is initialized and updated with the `felt252` fields. Then, `poseidon_hash_span` is used on the `Span` of the `Array` to compute its hash, which is then used to update the main hash state. Finally, `finalize()` computes the overall hash. +For provability, dictionaries must be 'squashed' when destructed. Unlike the `Drop` trait, which is a no-op for dictionaries, the `Destruct` trait enforces this necessary side-effect. -```cairo -# use core::hash::{HashStateExTrait, HashStateTrait}; -# use core::poseidon::{PoseidonTrait, poseidon_hash_span}; -# -# #[derive(Drop)] -# struct StructForHashArray { -# first: felt252, -# second: felt252, -# third: Array, -# } -# -#[executable] -fn main() { - let struct_to_hash = StructForHashArray { first: 0, second: 1, third: array![1, 2, 3, 4, 5] }; +Types containing dictionaries cannot derive `Drop`. If a struct contains a `Felt252Dict`, it must implement `Destruct` manually to call `self.balances.squash()`: - let mut hash = PoseidonTrait::new().update(struct_to_hash.first).update(struct_to_hash.second); - let hash_felt252 = hash.update(poseidon_hash_span(struct_to_hash.third.span())).finalize(); +``` +impl UserDatabaseDestruct, +Felt252DictValue> of Destruct> { + fn destruct(self: UserDatabase) nopanic { + self.balances.squash(); + } } ``` -Function Inlining and Output +### Interacting with Entries using `entry` and `finalize` -# Function Inlining and Output +The `entry` method allows creating a new entry for a key, taking ownership of the dictionary and returning the entry type (`Felt252DictEntry`) and the previous value. -## Function Inlining +``` +fn entry(self: Felt252Dict, key: felt252) -> (Felt252DictEntry, T) nopanic +``` -Function inlining is an optimization technique where the body of a function is directly inserted into the code at the call site, rather than making a traditional function call. This can improve performance by eliminating the overhead associated with function calls, such as stack manipulation and jumps. +The `finalize` method inserts the entry back and returns ownership of the dictionary: -### How Inlining Works +``` +fn finalize(self: Felt252DictEntry, new_value: T) -> Felt252Dict +``` -The Cairo compiler handles inlining by default. When a function is inlined, its instructions are directly executed at the point of the call. This is evident in the generated Sierra and Casm code, where inlined functions do not involve a `call` instruction. +Implementing a custom `get` method using these tools involves reading the previous value and finalizing the entry with that same value: -**Example:** +``` +use core::dict::{Felt252Dict, Felt252DictEntryTrait}; -Consider a program with an inlined function and a non-inlined function: +fn custom_get, +Drop, +Copy>( + ref dict: Felt252Dict, key: felt252, +) -> T { + // Get the new entry and the previous value held at `key` + let (entry, prev_value) = dict.entry(key); -```cairo -#[executable] -fn main() { - inlined(); - not_inlined(); -} + // Store the value to return + let return_value = prev_value; -#[inline(always)] -fn inlined() -> felt252 { - 'inlined' -} + // Update the entry with `prev_value` and get back ownership of the dictionary + dict = entry.finalize(prev_value); -#[inline(never)] -fn not_inlined() -> felt252 { - 'not inlined' + // Return the read value + return_value } ``` -The corresponding Sierra code for this example shows a `function_call` for `not_inlined` but not for `inlined`. - -### Benefits and Drawbacks +Implementing custom `insert` is similar, but the `finalize` call uses the new `value` instead of the previous one. -- **Benefits:** +### Dictionaries in Custom Structures - - Reduces function call overhead. - - Can enable further compiler optimizations by exposing the inlined code to the surrounding context. +`Felt252Dict` is fundamental for creating mutable custom data structures, such as implementing a mutable vector (`MemoryVec`) or a user database (`UserDatabase`). -- **Drawbacks:** - - Can increase code size if the inlined function is large and used multiple times. - - Manual application of the `#[inline(always)]` attribute is generally not recommended, as the compiler is effective at determining when inlining is beneficial. Use it only for fine-tuning. +For instance, in `MemoryVec`, `set` overwrites a value at an index by inserting a new entry into the underlying dictionary: -## Printing +``` +fn set(ref self: MemoryVec, index: usize, value: T) { + assert!(index < self.len(), "Index out of bounds"); + self.data.insert(index.into(), NullableTrait::new(value)); +} +``` -Cairo provides macros for printing data to the console, useful for program execution and debugging. +--- -### Standard Data Types +Sources: -Two macros are available for printing standard data types: +- https://www.starknet.io/cairo-book/ch04-01-what-is-ownership.html +- https://www.starknet.io/cairo-book/ch04-00-understanding-ownership.html +- https://www.starknet.io/cairo-book/ch04-02-references-and-snapshots.html -- `println!`: Prints output followed by a newline. -- `print!`: Prints output on the same line. +--- -Both macros accept a `ByteArray` string as the first argument. This string can be a simple message or include placeholders for formatting values. +# Ownership Model and Type Traits -#### Placeholders +Cairo utilizes a linear type system where every value must be used exactly once, either by being *destroyed* or *moved*. This system statically ensures that operations that could cause runtime errors, such as writing twice to the same memory cell, are caught at compile time. -Placeholders within the string can be used in two ways: +## Linear Type System and Usage -- Empty curly brackets `{}`: These are replaced by the subsequent arguments in order. -- Curly brackets with variable names `{variable_name}`: These are replaced by the value of the specified variable. +In a linear type system, any value must be used once. "Used" means the value is either destroyed or moved: +* **Destruction** occurs when a variable goes out of scope, is destructured, or explicitly destroyed via `destruct()`. +* **Moving** a value means passing it to another function. -These placeholder methods can be mixed. +Cairo's ownership model focuses on *variables*, not values, because the underlying memory is immutable. A value can be safely referred to by many variables, but variables themselves follow strict ownership rules: +1. Each variable has exactly one owner at any time. +2. When the owner goes out of scope, the variable is destroyed. -**Example:** +This system serves to ensure all code is provable and verifiable, abstracting the immutable memory of the Cairo VM. Attempting to use a variable after it has been moved results in a compile-time error, as demonstrated when trying to pass an `Array` to a function twice without implementing `Copy`: ```cairo +fn foo(mut arr: Array) { + arr.pop_front(); +} + #[executable] fn main() { - let a = 10; - let b = 20; - let c = 30; - - println!("Hello world!"); - println!("{} {} {}", a, b, c); // Output: 10 20 30 - println!("{c} {a} {}", b); // Output: 30 10 20 + let arr: Array = array![]; + foo(arr); + foo(arr); } ``` -Deref Coercion - -# Deref Coercion +## The `Copy` Trait -Deref coercion is a mechanism in Cairo that allows types implementing the `Deref` trait to be treated as instances of their target types. This is particularly useful for simplifying access to nested data structures and enabling method calls defined on the target type. +The `Copy` trait allows simple types to be duplicated by copying felts without allocating new memory segments, bypassing default move semantics. -## Understanding Deref Coercion with an Example +* Basic types implement `Copy` by default. +* Types like `Array` and `Felt252Dict` cannot implement `Copy` because manipulating them across different scopes is forbidden by the type system. +* Custom types that do not contain non-Copy components can implement `Copy` using `#[derive(Copy)]`. -Let's consider a generic wrapper type `Wrapper` that wraps a value of type `T`. By implementing the `Deref` trait for `Wrapper`, we can access the members of the wrapped type `T` directly through an instance of `Wrapper`. +If a type implements `Copy`, passing the variable transfers a copy, and the original variable remains valid, retaining ownership. For example, with a `Point` struct implementing `Copy`: ```cairo -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, +#[derive(Copy, Drop)] +struct Point { + x: u128, + y: u128, } -#[derive(Drop, Copy)] -struct Wrapper { - value: T, +#[executable] +fn main() { + let p1 = Point { x: 5, y: 10 }; + foo(p1); + foo(p1); } -impl DerefWrapper of Deref> { - type Target = T; - fn deref(self: Wrapper) -> T { - self.value - } +fn foo(p: Point) { // do something with p } ``` +If `Copy` is not derived, attempting to pass `p1` twice results in a compile-time error. -This implementation of `Deref` for `Wrapper` simply returns the wrapped `value`. +## Cloning and Return Values -## Practical Application of Deref Coercion - -When you have an instance of `Wrapper`, deref coercion allows you to access fields like `username` and `age` directly, as if you were operating on a `UserProfile` instance. +For complex types like `Array` that cannot implement `Copy`, deep copying is achieved using the `.clone()` method. A call to `clone()` executes arbitrary code to copy the underlying value to new memory cells. ```cairo #[executable] fn main() { - let wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; - // Access fields directly via deref coercion - println!("Username: {}", wrapped_profile.username); - println!("Current age: {}", wrapped_profile.age); + let arr1: Array = array![]; + let arr2 = arr1.clone(); } ``` -## Restricting Deref Coercion to Mutable Variables +Returning a value from a function is equivalent to *moving* it to the caller. The following example illustrates scope and moves: -The `DerefMut` trait, similar to `Deref`, enables coercion but is specifically applicable to mutable variables. It's important to note that `DerefMut` does not inherently provide mutable access to the underlying data; it's about the mutability context of the variable itself. +```cairo +#[derive(Drop)] +struct A {} -```cairo, noplayground -//TAG: does_not_compile +#[executable] +fn main() { + let a1 = gives_ownership(); // gives_ownership moves its return + // value into a1 -use core::ops::DerefMut; + let a2 = A {}; // a2 comes into scope -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, -} + let a3 = takes_and_gives_back(a2); // a2 is moved into + // takes_and_gives_back, which also + // moves its return value into a3 -#[derive(Drop, Copy)] -struct Wrapper { - value: T, -} +} // Here, a3 goes out of scope and is dropped. a2 was moved, so nothing + // happens. a1 goes out of scope and is dropped. -impl DerefMutWrapper> of DerefMut> { - type Target = T; - fn deref_mut(ref self: Wrapper) -> T { - self.value - } -} +fn gives_ownership() -> A { // gives_ownership will move its + // return value into the function + // that calls it -fn error() { - let wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; - // Uncommenting the next line will cause a compilation error - println!("Username: {}", wrapped_profile.username); + let some_a = A {}; // some_a comes into scope + + some_a // some_a is returned and + // moves ownership to the calling + // function } -#[executable] -fn main() { - let mut wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; +// This function takes an instance some_a of A and returns it +fn takes_and_gives_back(some_a: A) -> A { // some_a comes into scope - println!("Username: {}", wrapped_profile.username); - println!("Current age: {}", wrapped_profile.age); + some_a // some_a is returned and + // moves ownership to the calling + // function } ``` -Attempting to use `DerefMut` with an immutable variable will result in a compilation error, as the compiler cannot find the member `username` on the `Wrapper` type directly. +## Passing Variables to Functions -```plaintext -$ scarb build - Compiling no_listing_09_deref_coercion_example v0.1.0 (listings/ch12-advanced-features/no_listing_09_deref_mut_example/Scarb.toml) -error: Type "no_listing_09_deref_coercion_example::Wrapper::" has no member "username" - --> listings/ch12-advanced-features/no_listing_09_deref_mut_example/src/lib.cairo:32:46 - println!("Username: {}", wrapped_profile.username); - ^^^^^^^^ +When passing a variable to a function, ownership rules dictate how the variable can be used afterward: +* **Pass-by-value**: Ownership of the variable is transferred to the function. +* **Snapshot (`@`)**: Ownership is retained by the caller, and the function receives an immutable view. +* **Mutable Reference (`ref`)**: Ownership is retained by the caller, and the function receives a mutable view. -error: could not compile `no_listing_09_deref_coercion_example` due to previous error -``` +--- -To resolve this, the variable `wrapped_profile` must be declared as mutable. +Sources: -```cairo, noplayground -//TAG: does_not_compile +- https://www.starknet.io/cairo-book/ch04-02-references-and-snapshots.html +- https://www.starknet.io/cairo-book/ch03-01-arrays.html -use core::ops::DerefMut; +--- -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, -} +## Data Access Mechanisms: Snapshots and References -#[derive(Drop, Copy)] -struct Wrapper { - value: T, -} +### Reading Elements from an Array -impl DerefMutWrapper> of DerefMut> { - type Target = T; - fn deref_mut(ref self: Wrapper) -> T { - self.value - } -} +To access array elements, you can use `get()` or `at()` array methods. `arr.at(index)` is equivalent to using the subscripting operator `arr[index]`. -fn error() { - let wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; - // Uncommenting the next line will cause a compilation error - println!("Username: {}", wrapped_profile.username); -} +#### `get()` Method -#[executable] -fn main() { - let mut wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; +The `get` function returns an `Option>`. This returns a snapshot of the element if it exists, or `None` if the index is out of bounds, preventing panics. - println!("Username: {}", wrapped_profile.username); - println!("Current age: {}", wrapped_profile.age); -} -``` +Here is an example with the `get()` method: -## Calling Methods via Deref Coercion +```\n#[executable]\nfn main() -> u128 {\n let mut arr = ArrayTrait::::new();\n arr.append(100);\n let index_to_access =\n 1; // Change this value to see different results, what would happen if the index doesn't exist?\n match arr.get(index_to_access) {\n Some(x) => {\n *x\n .unbox() // Don't worry about * for now, if you are curious see Chapter 4.2 #desnap operator\n // It basically means "transform what get(idx) returned into a real value"\n },\n None => { panic!("out of bounds") },\n }\n}\n``` -Deref coercion extends beyond member access to method calls. If a type `A` dereferences to type `B`, and `B` has a method, you can call that method directly on an instance of `A`. +### Snapshots -```cairo -struct MySource { - pub data: u8, -} +A snapshot (`@T`) is an immutable view of a value at a certain point in execution. They allow retaining ownership when passing data to functions, avoiding the need to return values to restore ownership. Snapshots are passed by value (copied), meaning the size of `@T` is the same as `T`. -struct MyTarget { - pub data: u8, -} +Accessing fields of a snapshot yields snapshots of those fields, which must be desnapped using `*` to get the values, provided the field type implements `Copy`. -#[generate_trait] -impl TargetImpl of TargetTrait { - fn foo(self: MyTarget) -> u8 { - self.data - } -} +Attempting to modify values associated with snapshots results in a compiler error: -impl SourceDeref of Deref { - type Target = MyTarget; - fn deref(self: MySource) -> MyTarget { - MyTarget { data: self.data } - } -} +```\n#[derive(Copy, Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let rec = Rectangle { height: 3, width: 10 };\n flip(@rec);\n}\n\nfn flip(rec: @Rectangle) {\n let temp = rec.height;\n rec.height = rec.width;\n rec.width = temp;\n}\n```\n +#### Desnap Operator -#[executable] -fn main() { - let source = MySource { data: 5 }; - // Thanks to the Deref impl, we can call foo directly on MySource - let res = source.foo(); - assert!(res == 5); -} -``` +The desnap operator `*` is the opposite of the `@` operator, used to convert a snapshot back into a regular variable. This is only possible for `Copy` types, and it is a completely free operation. -In this example, `MySource` dereferences to `MyTarget`, which has a method `foo`. This allows `foo` to be called directly on `source`. +```\n#[derive(Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let rec = Rectangle { height: 3, width: 10 };\n let area = calculate_area(@rec);\n println!("Area: {}", area);\n}\n\nfn calculate_area(rec: @Rectangle) -> u64 {\n // As rec is a snapshot to a Rectangle, its fields are also snapshots of the fields types.\n // We need to transform the snapshots back into values using the desnap operator `*`.\n // This is only possible if the type is copyable, which is the case for u64.\n // Here, `*` is used for both multiplying the height and width and for desnapping the snapshots.\n *rec.height * *rec.width\n}\n```\n +### Mutable References -## Summary +To allow mutation while keeping ownership in the calling context, a *mutable reference* is used with the `ref` modifier. The variable must be declared mutable (`mut`) in the caller. Like snapshots, `ref` arguments are passed by value (copied) to the function, ensuring the function operates on a local version which is implicitly returned at the end of execution. -The `Deref` and `DerefMut` traits facilitate deref coercion, enabling transparent conversion between types. This simplifies access to underlying data in nested or wrapped structures and allows calling methods defined on the target type. Deref coercion is particularly beneficial when working with generic types and abstractions, reducing boilerplate code. +In Listing 4-5, a mutable reference swaps fields: -Associated Types and Data Packing +```\n#[derive(Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let mut rec = Rectangle { height: 3, width: 10 };\n flip(ref rec);\n println!("height: {}, width: {}", rec.height, rec.width);\n}\n\nfn flip(ref rec: Rectangle) {\n let temp = rec.height;\n rec.height = rec.width;\n rec.width = temp;\n}\n``` -# Associated Types and Data Packing +--- -## Constraint Traits on Associated Items +Sources: -Associated items are an experimental feature. To use them, add `experimental-features = ["associated_item_constraints"]` to your `Scarb.toml`. +- https://www.starknet.io/cairo-book/ch12-01-custom-data-structures.html -You can constrain associated items of a trait based on a generic parameter's type using the `[AssociatedItem: ConstrainedValue]` syntax after a trait bound. +--- -For example, to implement an `extend` method for collections that ensures the iterator's element type matches the collection's element type, you can use `[Iterator::Item: A]` as a constraint. +# Simulating Dynamic Structures with Dictionaries -```cairo -trait Extend { - fn extend[Item: A], +Destruct>(ref self: T, iterator: I); -} +### Simulating a Dynamic Array with Dicts -impl ArrayExtend> of Extend, T> { - fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { - for item in iterator { - self.append(item); - } - } +A mutable dynamic array should support appending items, accessing items by index, setting values by index, and returning the current length. This interface can be defined as follows: + +```rust +trait MemoryVecTrait { + fn new() -> V; + fn get(ref self: V, index: usize) -> Option; + fn at(ref self: V, index: usize) -> T; + fn push(ref self: V, value: T) -> (); + fn set(ref self: V, index: usize, value: T); + fn len(self: @V) -> usize; } ``` -## `TypeEqual` Trait for Type Equality Constraints - -The `TypeEqual` trait from `core::metaprogramming` allows constraints based on type equality. +To implement this, we use a `Felt252Dict` to map indices to values, and a separate `len` field. The type `T` is wrapped in `Nullable` to allow using any type in the structure. -### Excluding Specific Types +The structure definition and implementation are provided below: -`TypeEqual` can be used with negative implementations to exclude specific types from a trait implementation. +```rust +use core::dict::Felt252Dict; +use core::nullable::NullableTrait; +use core::num::traits::WrappingAdd; -```cairo -trait SafeDefault { - fn safe_default() -> T; +trait MemoryVecTrait { + fn new() -> V; + fn get(ref self: V, index: usize) -> Option; + fn at(ref self: V, index: usize) -> T; + fn push(ref self: V, value: T) -> (); + fn set(ref self: V, index: usize, value: T); + fn len(self: @V) -> usize; } -#[derive(Drop, Default)] -struct SensitiveData { - secret: felt252, +struct MemoryVec { + data: Felt252Dict>, + len: usize, } -// Implement SafeDefault for all types EXCEPT SensitiveData -impl SafeDefaultImpl< - T, +Default, -core::metaprogramming::TypeEqual, -> of SafeDefault { - fn safe_default() -> T { - Default::default() +impl DestructMemoryVec> of Destruct> { + fn destruct(self: MemoryVec) nopanic { + self.data.squash(); } } -#[executable] -fn main() { - let _safe: u8 = SafeDefault::safe_default(); - let _unsafe: SensitiveData = Default::default(); // Allowed - // This would cause a compile error: - // let _dangerous: SensitiveData = SafeDefault::safe_default(); -} -``` +impl MemoryVecImpl, +Copy> of MemoryVecTrait, T> { + fn new() -> MemoryVec { + MemoryVec { data: Default::default(), len: 0 } + } -### Ensuring Matching Associated Types + fn get(ref self: MemoryVec, index: usize) -> Option { + if index < self.len() { + Some(self.data.get(index.into()).deref()) + } else { + None + } + } -`TypeEqual` is useful for ensuring two types have equal associated types, especially in generic functions. + fn at(ref self: MemoryVec, index: usize) -> T { + assert!(index < self.len(), "Index out of bounds"); + self.data.get(index.into()).deref() + } -```cairo -trait StateMachine { - type State; - fn transition(ref state: Self::State); + fn push(ref self: MemoryVec, value: T) -> () { + self.data.insert(self.len.into(), NullableTrait::new(value)); + self.len.wrapping_add(1_usize); + } + fn set(ref self: MemoryVec, index: usize, value: T) { + assert!(index < self.len(), "Index out of bounds"); + self.data.insert(index.into(), NullableTrait::new(value)); + } + fn len(self: @MemoryVec) -> usize { + *self.len + } } +``` -#[derive(Copy, Drop)] -struct StateCounter { - counter: u8, -} +### Simulating a Stack with Dicts -impl TA of StateMachine { - type State = StateCounter; - fn transition(ref state: StateCounter) { - state.counter += 1; - } -} +A Stack is a LIFO (Last-In, First-Out) collection. We define the necessary interface: -impl TB of StateMachine { - type State = StateCounter; - fn transition(ref state: StateCounter) { - state.counter *= 2; - } +```rust +trait StackTrait { + fn push(ref self: S, value: T); + fn pop(ref self: S) -> Option; + fn is_empty(self: @S) -> bool; } +``` -fn combine< - impl A: StateMachine, - impl B: StateMachine, - +core::metaprogramming::TypeEqual, ->( - ref self: A::State, -) { - A::transition(ref self); - B::transition(ref self); -} +The stack structure, `NullableStack`, also uses a dictionary for data storage and a length counter: -#[executable] -fn main() { - let mut initial = StateCounter { counter: 0 }; - combine::(ref initial); +```rust +struct NullableStack { + data: Felt252Dict>, + len: usize, } ``` -## Data Packing with Associated Types - -Associated types can simplify function signatures compared to explicitly defining generic type parameters for return types. +The implementation details for the stack trait methods are as follows: -Consider a `PackGeneric` trait that requires explicit generic parameters for the input and output types: +```rust +use core::dict::Felt252Dict; +use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; -```cairo -fn foo>(self: T, other: T) -> U { - self.pack_generic(other) +trait StackTrait { + fn push(ref self: S, value: T); + fn pop(ref self: S) -> Option; + fn is_empty(self: @S) -> bool; } -``` -A `Pack` trait using an associated type `Result` allows for a more concise function signature: +struct NullableStack { + data: Felt252Dict>, + len: usize, +} -```cairo -trait Pack { - type Result; - fn pack(self: T, other: T) -> Self::Result; +impl DestructNullableStack> of Destruct> { + fn destruct(self: NullableStack) nopanic { + self.data.squash(); + } } -impl PackU32Impl of Pack { - type Result = u64; +impl NullableStackImpl, +Copy> of StackTrait, T> { + fn push(ref self: NullableStack, value: T) { + self.data.insert(self.len.into(), NullableTrait::new(value)); + self.len += 1; + } - fn pack(self: u32, other: u32) -> Self::Result { - let shift: u64 = 0x100000000; // 2^32 - self.into() * shift + other.into() + fn pop(ref self: NullableStack) -> Option { + if self.is_empty() { + return None; + } + self.len -= 1; + Some(self.data.get(self.len.into()).deref()) } -} -fn bar>(self: T, b: T) -> PackImpl::Result { - PackImpl::pack(self, b) + fn is_empty(self: @NullableStack) -> bool { + *self.len == 0 + } } ``` -Both approaches achieve the same result: +The `push` function inserts the element at the index equal to the current `len` and then increments `len`. The `pop` function decrements `len` (updating the stack top position) and then retrieves the value at the new `len` index. -```cairo -trait Pack { - type Result; - fn pack(self: T, other: T) -> Self::Result; -} +--- -impl PackU32Impl of Pack { - type Result = u64; +Sources: - fn pack(self: u32, other: u32) -> Self::Result { - let shift: u64 = 0x100000000; // 2^32 - self.into() * shift + other.into() - } -} +- https://www.starknet.io/cairo-book/ch12-02-smart-pointers.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html -fn bar>(self: T, b: T) -> PackImpl::Result { - PackImpl::pack(self, b) -} +--- + +# Smart Pointers and Recursive Types + +## Smart Pointers Overview + +A pointer is a variable holding a memory address. To prevent bugs and security vulnerabilities associated with unsafe memory access, Cairo uses *Smart Pointers*. These act like pointers but include extra metadata and capabilities, ensuring memory is accessed safely via strict type checking and ownership rules. Types like `Felt252Dict` and `Array` are examples of smart pointers, as they own a memory segment and manage access (e.g., arrays track their length). When an array is created, a new segment is allocated for its elements, and the array itself holds a pointer to that segment. + +## The `Box` Type -trait PackGeneric { - fn pack_generic(self: T, other: T) -> U; -} +The principal smart pointer in Cairo is `Box`. Instantiating `Box` stores the data of type `T` in a dedicated memory area called the *boxed segment*. The execution segment only holds a pointer to this boxed data. -impl PackGenericU32 of PackGeneric { - fn pack_generic(self: u32, other: u32) -> u64 { - let shift: u64 = 0x100000000; // 2^32 - self.into() * shift + other.into() - } -} +### Storing Data in the Boxed Segment -fn foo>(self: T, other: T) -> U { - self.pack_generic(other) -} +Boxes have minimal performance overhead but are useful when: +1. The size of a type is unknown at compile time, and a fixed size is required. +2. Transferring ownership of large data volumes, where copying the data is slow. Storing large data in a box means only the small pointer is copied during ownership transfer. +The syntax for creating and accessing a box is shown below: + +```rust #[executable] fn main() { - let a: u32 = 1; - let b: u32 = 1; - - let x = foo(a, b); - let y = bar(a, b); - - // result is 2^32 + 1 - println!("x: {}", x); - println!("y: {}", y); + let b = BoxTrait::new(5_u128); + println!("b = {}", b.unbox()) } ``` +Listing 12-1: Storing a `u128` value in the boxed segment using a box -Modules and Project Organization +When instantiated, the value is stored in the boxed segment, and `b.unbox()` accesses it. -Project Setup with Scarb +### Enabling Recursive Types with Boxes -# Project Setup with Scarb +Recursive types (where a type contains another value of itself) pose a problem because Cairo cannot determine their size at compile time. Since `Box` always has a known size (the size of a pointer), inserting a box breaks the infinite recursive chain. -## Scarb.toml Configuration +An example is defining a binary tree. An initial attempt fails because the `Node` variant holds another `BinaryTree` directly: -The `Scarb.toml` file configures your Scarb project. Key sections include: +```rust +#[derive(Copy, Drop)] +enum BinaryTree { + Leaf: u32, + Node: (u32, BinaryTree, BinaryTree), +} -- **`[package]`**: Defines the package name, version, and Cairo edition. -- **`[cairo]`**: Contains Cairo-specific configurations, like enabling gas. -- **`[dependencies]`**: Lists external packages (crates) your project depends on. These can be specified using Git URLs with optional `rev`, `branch`, or `tag`. -- **`[dev-dependencies]`**: Lists dependencies required only for development and testing (e.g., `snforge_std`, `assert_macros`). -- **`[[target.executable]]`**: Specifies executable targets, including the entry point function. -- **`[[target.starknet-contract]]`**: (Optional) Used for building Starknet smart contracts. -- **`[script]`**: (Optional) Defines custom scripts, often used for testing with `snforge`. +#[executable] +fn main() { + let leaf1 = BinaryTree::Leaf(1); + let leaf2 = BinaryTree::Leaf(2); + let leaf3 = BinaryTree::Leaf(3); + let node = BinaryTree::Node((4, leaf2, leaf3)); + let _root = BinaryTree::Node((5, leaf1, node)); +} +``` -### Example Scarb.toml for a Cairo Program +This is fixed by replacing the recursive type with `Box`: -```toml -[package] -name = "hello_world" -version = "0.1.0" -edition = "2024_07" +```rust +mod display; +use display::DebugBinaryTree; -[cairo] -enable-gas = false +#[derive(Copy, Drop)] +enum BinaryTree { + Leaf: u32, + Node: (u32, Box, Box), +} -[dependencies] -cairo_execute = "2.12.0" +#[executable] +fn main() { + let leaf1 = BinaryTree::Leaf(1); + let leaf2 = BinaryTree::Leaf(2); + let leaf3 = BinaryTree::Leaf(3); + let node = BinaryTree::Node((4, BoxTrait::new(leaf2), BoxTrait::new(leaf3))); + let root = BinaryTree::Node((5, BoxTrait::new(leaf1), BoxTrait::new(node))); -[[target.executable]] -name = "hello_world_main" -function = "hello_world::hello_world::main" + println!("{:?}", root); +} ``` +Listing 12-3: Defining a recursive Binary Tree using Boxes -## Managing Dependencies +The `Node` variant now holds `(u32, Box, Box)`, allowing the compiler to calculate the size. -Scarb manages external packages (crates) through Git repositories. +### Using Boxes to Improve Performance -### Adding Dependencies +Passing pointers via boxes avoids copying large amounts of data when transferring ownership between functions. Only the pointer (a single value) is copied. + +```rust +#[derive(Drop)] +struct Cart { + paid: bool, + items: u256, + buyer: ByteArray, +} -- **Manual Addition**: Declare dependencies in the `[dependencies]` section of `Scarb.toml` with their Git URL. For specific versions, use `rev`, `branch`, or `tag`. - `cairo - [dependencies] -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git" } - ` -- **`scarb add` command**: Use `scarb add ` to automatically update `Scarb.toml`. For development dependencies, use `scarb add --dev `. +fn pass_data(cart: Cart) { + println!("{} is shopping today and bought {} items", cart.buyer, cart.items); +} -### Removing Dependencies +fn pass_pointer(cart: Box) { + let cart = cart.unbox(); + println!("{} is shopping today and bought {} items", cart.buyer, cart.items); +} -- Remove the corresponding line from `Scarb.toml` or use the `scarb rm ` command. +#[executable] +fn main() { + let new_struct = Cart { paid: true, items: 1, buyer: "Eli" }; + pass_data(new_struct); -### Building with Dependencies + let new_box = BoxTrait::new(Cart { paid: false, items: 2, buyer: "Uri" }); + pass_pointer(new_box); +} +``` +Listing 12-4: Storing large amounts of data in a box for performance. -Run `scarb build` to fetch and compile all declared dependencies. +When calling `pass_data`, the entire `Cart` struct is copied. When calling `pass_pointer`, only the pointer stored in `new_box` is copied. If the data in the box is mutated, a new `Box` will be created, requiring the data to be copied into the new box. -## Using the Glob Operator (`*`) +## The `Nullable` Type for Dictionaries -The glob operator (`*`) can be used in `use` statements to bring all public items from a path into the current scope. Use with caution as it can make it harder to track item origins. +`Nullable` is a smart pointer that can point to a value or be `null`. It is used primarily in dictionaries (`Felt252Dict`) for types that do not implement the required `Felt252DictValue` trait (specifically, the `zero_default` method). Complex types like arrays and structs (including `u256`) lack this implementation, making direct storage difficult. -```rust -use core::num::traits::*; -``` +`Nullable` wraps the value inside a `Box`, allowing complex types to be stored in dictionaries by utilizing the boxed segment. -Core Concepts: Packages, Crates, and Modules +For example, to store a `Span` in a dictionary, you must use `Nullable` wrapping a `Box`: -# Core Concepts: Packages, Crates, and Modules +```rust +use core::dict::Felt252Dict; +use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; -As programs grow, organizing code into modules and files becomes crucial for clarity and maintainability. Larger projects can extract parts into separate crates, which act as external dependencies. Cairo's module system facilitates this organization and controls code scope. +#[executable] +fn main() { + // Create the dictionary + let mut d: Felt252Dict>> = Default::default(); -## Key Components of the Module System + // Create the array to insert + let a = array![8, 9, 10]; -- **Packages:** A Scarb feature for building, testing, and sharing crates. -- **Crates:** A compilation unit consisting of a tree of modules. The crate root is typically defined in `lib.cairo`. -- **Modules and `use`:** Keywords that manage item organization and scope. -- **Paths:** Names used to refer to items like structs, functions, or modules. + // Insert it as a `Span` + d.insert(0, NullableTrait::new(a.span())); -## Packages and Crates +//... +``` +This approach is necessary because `Array` does not implement the `Copy` trait required for reading from a dictionary, whereas `Span` does. -### What is a Crate? +--- -A crate is a subset of a package compiled by Cairo. It includes the package's source code, starting from the crate root, and crate-level compiler settings (e.g., `edition` in `Scarb.toml`). Crates can contain modules defined in various files. +Sources: -### What is the Crate Root? +- https://www.starknet.io/cairo-book/ch08-02-traits-in-cairo.html +- https://www.starknet.io/cairo-book/ch12-05-macros.html +- https://www.starknet.io/cairo-book/ch08-01-generic-data-types.html +- https://www.starknet.io/cairo-book/ch12-10-associated-items.html +- https://www.starknet.io/cairo-book/ch12-10-procedural-macros.html +- https://www.starknet.io/cairo-book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html +- https://www.starknet.io/cairo-book/ch07-02-defining-modules-to-control-scope.html +- https://www.starknet.io/cairo-book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html +- https://www.starknet.io/cairo-book/ch12-09-deref-coercion.html +- https://www.starknet.io/cairo-book/ch11-01-closures.html +- https://www.starknet.io/cairo-book/ch05-03-method-syntax.html +- https://www.starknet.io/cairo-book/ch07-05-separating-modules-into-different-files.html +- https://www.starknet.io/cairo-book/ch08-00-generic-types-and-traits.html +- https://www.starknet.io/cairo-book/ch07-00-managing-cairo-projects-with-packages-crates-and-modules.html +- https://www.starknet.io/cairo-book/ch12-03-operator-overloading.html +- https://www.starknet.io/cairo-book/ch12-10-arithmetic-circuits.html +- https://www.starknet.io/cairo-book/appendix-02-operators-and-symbols.html +- https://www.starknet.io/cairo-book/appendix-03-derivable-traits.html +- https://www.starknet.io/cairo-book/ch02-04-comments.html +- https://www.starknet.io/cairo-book/ch12-00-advanced-features.html +- https://www.starknet.io/cairo-book/ch12-01-custom-data-structures.html +- https://www.starknet.io/cairo-book/ch12-02-smart-pointers.html +- https://www.starknet.io/cairo-book/ch12-04-hash.html -The crate root is the `lib.cairo` file, which serves as the entry point for the Cairo compiler and forms the root module of the crate. Modules are further explained in the "Defining Modules to Control Scope" chapter. +--- -### What is a Package? +--- -A Cairo package is a directory containing: +Sources: -- A `Scarb.toml` manifest file with a `[package]` section. -- Associated source code. +- https://www.starknet.io/cairo-book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html +- https://www.starknet.io/cairo-book/ch07-02-defining-modules-to-control-scope.html +- https://www.starknet.io/cairo-book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html +- https://www.starknet.io/cairo-book/ch07-05-separating-modules-into-different-files.html +- https://www.starknet.io/cairo-book/ch07-00-managing-cairo-projects-with-packages-crates-and-modules.html +- https://www.starknet.io/cairo-book/ch02-04-comments.html -A package can contain other packages, each with its own `Scarb.toml`. +--- -### Creating a Package with Scarb +# Modules, Paths, and Project Structure -The `scarb new` command creates a new Cairo package: +As programs grow, organizing code by grouping related functionality into modules and separating concerns into distinct files becomes crucial. This organization clarifies where code resides and how features are implemented, facilitating reuse through encapsulation. -```bash -scarb new my_package -``` +## Project Organization: Packages, Crates, and Modules -This generates a directory structure like: +Cairo uses several concepts to manage large codebases: -``` -my_package/ -├── Scarb.toml -└── src/ - └── lib.cairo -``` +* **Packages:** A Scarb feature enabling building, testing, and sharing crates. +* **Crates:** A tree of modules forming a single compilation unit. A crate has a root directory and a root module defined in `lib.cairo` within that directory. +* **Modules and `use`:** Control the organization and scope of items. +* **Paths:** Name items within the module tree. -- `src/`: Contains all Cairo source files. -- `lib.cairo`: The default crate root module and package entry point. -- `Scarb.toml`: The package manifest file for metadata and configuration. +A related concept is **scope**, the context where code is written, which defines which names are available. You cannot have two items with the same name in the same scope. -Example `Scarb.toml`: +## Defining Modules and Controlling Scope -```toml -[package] -name = "my_package" -version = "0.1.0" -edition = "2024_07" +Modules let us organize code within a crate for readability and control privacy. Items within a module are private by default. -[executable] +### Module Documentation -[cairo] -enable-gas = false +Module documentation comments provide an overview of the entire module, prefixed with `//!`, and are placed above the module they describe. -[dependencies] -cairo_execute = "2.12.0" +```cairo +//! # my_module and implementation +//! +//! This is an example description of my_module and some of its features. +//! +//! # Examples +//! +//! ``` +//! mod my_other_module { +//! use path::to::my_module; +//! +//! fn foo() { +//! my_module.bar(); +//! } +//! } +//! ``` +mod my_module { // rest of implementation... +} ``` -Additional `.cairo` files can be added to `src/` or its subdirectories to organize code into multiple files. +### Modules Cheat Sheet (Rules for Organization) -Module Paths and Navigation +When organizing code, the compiler follows these rules: -# Module Paths and Navigation +1. **Start from the crate root:** Compilation begins in the crate root file (`src/lib.cairo`). +2. **Declaring modules (in crate root):** Use `mod garden;`. The compiler looks: + * Inline, within curly brackets replacing the semicolon. + * In the file `src/garden.cairo`. +3. **Declaring submodules (in files other than crate root):** Use `mod vegetables;` in `src/garden.cairo`. The compiler looks: + * Inline, directly following `mod vegetables`, within curly brackets. + * In the file `src/garden/vegetables.cairo`. +4. **Paths to code in modules:** Items are referenced using paths, e.g., `crate::garden::vegetables::Asparagus`. +5. **Private vs public:** Code is private by default. Use `pub mod` to make a module public. Use `pub` before item declarations to make them public. `pub(crate)` restricts visibility to within the defining crate. +6. **The `use` keyword:** Creates shortcuts to paths within a scope. -When organizing code, Cairo follows specific rules for module declaration and navigation. +The module tree structure mirrors the filesystem directory tree. The crate root file forms the module named after the crate at the root of the module structure. -## Declaring Modules and Submodules +### Separating Modules into Different Files -- **Crate Root**: The compiler starts by looking in the crate root file (`src/lib.cairo`). -- **Declaring Modules**: In the crate root, modules are declared using `mod ;`. The compiler searches for the module's code in: - - Inline, within curly brackets: - ```cairo,noplayground - // crate root file (src/lib.cairo) - mod garden { - // code defining the garden module goes here - } - ``` - - In the file `src/.cairo`. -- **Declaring Submodules**: In files other than the crate root (e.g., `src/garden.cairo`), submodules are declared similarly (e.g., `mod vegetables;`). The compiler searches within the parent module's directory: - - Inline, within curly brackets: - ```cairo,noplayground - // src/garden.cairo file - mod vegetables { - // code defining the vegetables submodule goes here - } - ``` - - In the file `src//.cairo`. +When modules grow, their definitions can be moved to separate files. -## Paths for Referring to Items +* If `mod front_of_house;` is declared in the crate root (`src/lib.cairo`), the compiler looks for its content in `src/front_of_house.cairo`. +* For a submodule like `pub mod hosting;` declared in `src/front_of_house.cairo`, the compiler looks for its content in a directory named after the parent, i.e., `src/front_of_house/hosting.cairo`. -To reference code within modules, Cairo uses paths, similar to filesystem navigation. Paths consist of identifiers separated by double colons (`::`). +The `mod` keyword declares modules and tells Cairo where to look; it is not an "include" operation. -There are two forms of paths: +## Paths for Referring to Items + +Paths name items in the module tree, similar to filesystem navigation. -- **Absolute Path**: Starts from the crate root, beginning with the crate name. -- **Relative Path**: Starts from the current module. +A path can be: +* **Absolute path:** Starts from the crate root, beginning with the crate name. +* **Relative path:** Starts from the current module. -### Example: Absolute and Relative Paths +Both forms use double colons (`::`) as separators. -Consider a crate with nested modules `front_of_house` and `hosting`, containing the function `add_to_waitlist`. To call this function from `eat_at_restaurant` within the same crate: +### Absolute vs. Relative Paths -Filename: src/lib.cairo +To call a function like `add_to_waitlist` defined deep within nested modules: -```cairo,noplayground +```cairo mod front_of_house { mod hosting { fn add_to_waitlist() {} - fn seat_at_table() {} } mod serving { fn take_order() {} + fn serve_order() {} + fn take_payment() {} } } @@ -4891,20 +4073,11 @@ pub fn eat_at_restaurant() { } ``` -- The **absolute path** starts with `crate::`, referencing the crate root. -- The **relative path** starts from the current module (where `eat_at_restaurant` is defined) and directly accesses `front_of_house`. - -## Starting Relative Paths with `super` - -Relative paths can also start from the parent module using the `super` keyword, analogous to `..` in filesystems. This is useful for referencing items in a parent module, especially when the module structure might change. - -### Example: Using `super` +### Starting Relative Paths with `super` -In the following example, `fix_incorrect_order` within the `back_of_house` module calls `deliver_order` from its parent module: +The `super` keyword constructs a relative path starting from the parent module, analogous to `..` in filesystems. -Filename: src/lib.cairo - -```cairo,noplayground +```cairo fn deliver_order() {} mod back_of_house { @@ -4917,157 +4090,62 @@ mod back_of_house { } ``` -Here, `super::deliver_order()` references the `deliver_order` function defined in the module containing `back_of_house`. +## Privacy and Visibility with `pub` -Visibility and Privacy Rules +By default, code within a module is private to its parent modules. Items in child modules can see items in ancestor modules. -# Visibility and Privacy Rules +To expose items: -Modules allow us to organize code within a crate and control the privacy of items. By default, all items within a module are private, meaning they can only be accessed by the current module and its descendants. +* Mark the module declaration with `pub`. Making a module public only allows access to the module itself, not its contents. +* Mark items within the module with `pub`. -## Modules and Privacy +For structs and enums: +* `pub struct` makes the struct public, but its fields remain private unless explicitly marked `pub`. +* `pub enum` makes the enum and all its variants public. -- **Default Privacy**: Code within a module is private by default. This means it's only accessible within that module and its submodules. -- **Public Modules**: To make a module public, declare it using `pub mod`. This allows parent modules to refer to it. -- **Public Items**: To make items (like functions, structs, etc.) within a module public, use the `pub` keyword before their declarations. Making a module public does not automatically make its contents public. -- **`pub(crate)`**: This keyword restricts visibility to only the current crate. +To allow an item to be visible only within the defining crate, use `pub(crate)`. -For example, consider a crate structure: - -```text -backyard/ -├── Scarb.toml -└── src - ├── garden - │ └── vegetables.cairo - ├── garden.cairo - └── lib.cairo -``` +## Bringing Paths into Scope with the `use` Keyword -The crate root `src/lib.cairo` might contain: +The `use` keyword creates shortcuts to paths within the current scope, reducing path repetition. ```cairo -pub mod garden; -use crate::garden::vegetables::Asparagus; - -#[executable] -fn main() { - let plant = Asparagus {}; - println!("I'm growing {:?}!", plant); -} -``` - -The `garden` module, defined in `src/garden.cairo`, would be declared as: - -```cairo,noplayground -pub mod vegetables; -``` - -And `src/garden/vegetables.cairo` would contain: - -```cairo,noplayground -#[derive(Drop, Debug)] -pub struct Asparagus {} -``` - -The `use crate::garden::vegetables::Asparagus;` line in `lib.cairo` brings the `Asparagus` type into scope. - -## Exposing Paths with the `pub` Keyword - -When items are private, they cannot be accessed from outside their defining module, even if the module itself is public. To allow access, both the module (if necessary for external access) and the specific items within it must be declared `pub`. - -Consider a scenario where `eat_at_restaurant` needs to call `add_to_waitlist` from a nested module: - -```cairo,noplayground mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } +use crate::front_of_house::hosting; pub fn eat_at_restaurant() { - // Absolute path - crate::front_of_house::hosting::add_to_waitlist(); // Compiles - - // Relative path - front_of_house::hosting::add_to_waitlist(); // Compiles -} -``` - -In this example, `mod hosting` is declared `pub` to be accessible from `front_of_house`, and `add_to_waitlist` is declared `pub` to be callable from `eat_at_restaurant`. - -## `use` Keyword for Shortcuts - -The `use` keyword creates shortcuts to items, reducing the need to repeat long paths. For instance, `use crate::garden::vegetables::Asparagus;` allows you to refer to `Asparagus` directly within its scope. - -## Example: Private Struct Fields - -While a struct can be public, its fields remain private by default. - -```cairo -pub mod rectangle { - #[derive(Copy, Drop)] - pub struct Rectangle { - width: u64, // Private field - height: u64 // Private field - } -} - -fn main() { - // This would not compile because width and height are private: - // let r = rectangle::Rectangle { width: 10, height: 20 }; - // println!("{}", r.width); + hosting::add_to_waitlist(); // ✅ Shorter path } ``` -To make the fields accessible, they would also need to be marked with `pub`. +A `use` statement only applies to the scope in which it is declared. -Using the `use` Keyword for Imports and Re-exports +### Idiomatic use of `use` -# Using the `use` Keyword for Imports and Re-exports - -The `use` keyword allows you to bring paths into the current scope, creating shorter and less repetitive ways to refer to items. This is similar to creating a symbolic link. The `use` statement is scoped to the block in which it appears. - -## Bringing Paths into Scope with the `use` Keyword - -To simplify calls to functions that are deep within modules, you can bring their module into scope. For example, `use crate::front_of_house::hosting;` allows you to call `hosting::add_to_waitlist()` instead of the full path. +When bringing in functions, it is idiomatic to bring the parent module into scope, requiring the parent module name to be specified when calling the function, which clarifies the origin of the function. ```cairo -// section "Defining Modules to Control Scope" - mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } -use crate::front_of_house::hosting; +use crate::front_of_house::hosting::add_to_waitlist; // Unidiomatic for functions pub fn eat_at_restaurant() { - hosting::add_to_waitlist(); // ✅ Shorter path + add_to_waitlist(); } ``` -A `use` statement only applies within its scope. If a function using a `use` statement is moved to a different scope (e.g., a child module), the shortcut will no longer apply, leading to a compiler error. - -## Creating Idiomatic `use` Paths - -It is idiomatic in Cairo (following Rust conventions) to bring a function's parent module into scope with `use` and then call the function using the module name (e.g., `hosting::add_to_waitlist()`). This makes it clear that the function is not locally defined while reducing path repetition. - -Conversely, when bringing types like structs, enums, or traits into scope, it's idiomatic to specify the full path to the item itself. - -```cairo -use core::num::traits::BitSize; - -#[executable] -fn main() { - let u8_size: usize = BitSize::::bits(); - println!("A u8 variable has {} bits", u8_size) -} -``` +For structs, enums, and traits, it is idiomatic to specify the full path when importing. ### Providing New Names with the `as` Keyword -If you need to bring two items with the same name into the same scope, or if you simply want to use a shorter name, you can use the `as` keyword to create an alias. +If two items with the same name are brought into scope, the `as` keyword can create an alias: ```cairo use core::array::ArrayTrait as Arr; @@ -5081,7 +4159,7 @@ fn main() { ### Importing Multiple Items from the Same Module -To import several items from the same module cleanly, you can use curly braces `{}` to list them. +Multiple items can be imported using curly braces `{}`: ```cairo // Assuming we have a module called `shapes` with the structures `Square`, `Circle`, and `Triangle`. @@ -5099,1886 +4177,1774 @@ mod shapes { #[derive(Drop)] pub struct Triangle { pub base: u32, - pub height: u32, - } -} - -// We can import the structures `Square`, `Circle`, and `Triangle` from the `shapes` module like -// this: -use shapes::{Circle, Square, Triangle}; - -// Now we can directly use `Square`, `Circle`, and `Triangle` in our code. -#[executable] -fn main() { - let sq = Square { side: 5 }; - let cr = Circle { radius: 3 }; - let tr = Triangle { base: 5, height: 2 }; - // ... -} -``` - -## Re-exporting Names in Module Files - -Re-exporting makes an item that is brought into scope with `use` also available for others to bring into their scope, by using `pub use`. This means the item is available in the current scope and can be re-exported from that scope. - -```cairo -mod front_of_house { - pub mod hosting { - pub fn add_to_waitlist() {} - } -} - -pub use crate::front_of_house::hosting; - -fn eat_at_restaurant() { - hosting::add_to_waitlist(); -} -``` - -Structuring Modules in Separate Files - -# Structuring Modules in Separate Files - -## Organizing Modules - -Modules in Cairo are used to group related functionality, making code easier to manage and navigate. You define a module using the `mod` keyword, followed by the module name and a block of code enclosed in curly braces `{}`. Inside modules, you can define other modules, functions, structs, enums, and more. - -The `src/lib.cairo` (or `src/main.cairo` for binaries) file serves as the crate root, forming a module named after the crate itself at the root of the module tree. - -### Example: Nested Modules - -Consider organizing a restaurant application's logic: - -Filename: src/lib.cairo - -```cairo,noplayground -mod front_of_house { - mod hosting { - fn add_to_waitlist() {} - fn seat_at_table() {} - } - - mod serving { - fn take_order() {} - fn serve_order() {} - fn take_payment() {} - } -} -``` - -Listing 7-1: A `front_of_house` module containing other modules that then contain functions - -This structure creates a module tree where `hosting` and `serving` are children of `front_of_house`, and siblings to each other. - -### Module Tree Representation - -The module tree visually represents the relationships between modules: - -```text -restaurant - └── front_of_house - ├── hosting - │ ├── add_to_waitlist - │ └── seat_at_table - └── serving - ├── take_order - ├── serve_order - └── take_payment -``` - -Listing 7-2: The module tree for the code in Listing 7-1 - -This organization is analogous to a file system's directory structure, allowing for logical grouping and easy navigation of code. - -## Separating Modules into Files + pub height: u32, + } +} -As modules grow, it's beneficial to move their definitions into separate files to maintain code clarity and organization. +// We can import the structures `Square`, `Circle`, and `Triangle` from the `shapes` module like +// this: +use shapes::{Circle, Square, Triangle}; -### Extracting a Module to a New File +// Now we can directly use `Square`, `Circle`, and `Triangle` in our code. +#[executable] +fn main() { + let sq = Square { side: 5 }; + let cr = Circle { radius: 3 }; + let tr = Triangle { base: 5, height: 2 }; + // ... +} +``` -1. **Declare the module in the parent file:** In the parent file (e.g., `src/lib.cairo`), replace the module's body with a `mod module_name;` declaration. -2. **Create the module's file:** Create a new file named `src/module_name.cairo` and place the original module's content within it. +### Re-exporting Names with `pub use` -**Example:** Moving `front_of_house` from `src/lib.cairo`: +Using `pub use` brings a name into scope and makes it available for others to import into their scope, effectively changing the public API structure without altering the internal structure. -Filename: src/lib.cairo +```cairo +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} + } +} -```cairo,noplayground -mod front_of_house; -use crate::front_of_house::hosting; +pub use crate::front_of_house::hosting; fn eat_at_restaurant() { hosting::add_to_waitlist(); } ``` +This allows external code to use `crate::hosting::add_to_waitlist()` instead of the longer internal path. -Listing 7-14: Declaring the `front_of_house` module whose body will be in `src/front_of_house.cairo` +--- -Filename: src/front_of_house.cairo +Sources: -```cairo,noplayground -pub mod hosting { - pub fn add_to_waitlist() {} -} -``` +- https://www.starknet.io/cairo-book/ch08-01-generic-data-types.html +- https://www.starknet.io/cairo-book/ch08-00-generic-types-and-traits.html +- https://www.starknet.io/cairo-book/appendix-02-operators-and-symbols.html +- https://www.starknet.io/cairo-book/ch08-02-traits-in-cairo.html +- https://www.starknet.io/cairo-book/ch11-01-closures.html +- https://www.starknet.io/cairo-book/ch12-01-custom-data-structures.html +- https://www.starknet.io/cairo-book/ch12-02-smart-pointers.html -Listing 7-15: Definitions inside the `front_of_house` module in `src/front_of_house.cairo` +--- -### Extracting a Child Module +## Generics and Constrained Types -To extract a child module (e.g., `hosting` within `front_of_house`): +Generics in Cairo provide tools to handle the duplication of concepts by using abstract stand-ins for concrete types or other properties. This allows writing reusable code that works with many types, enhancing maintainability, although code duplication still exists at compile level, potentially increasing Starknet contract size. -1. **Update the parent module file:** In `src/front_of_house.cairo`, replace the child module's body with `pub mod child_module_name;`. -2. **Create a directory for the parent:** Create a directory named after the parent module (e.g., `src/front_of_house/`). -3. **Create the child module file:** Inside this directory, create a file named after the child module (e.g., `src/front_of_house/hosting.cairo`) and place its definitions there. +### Syntax for Generics -**Example:** Moving `hosting` into its own file: +Generics are defined using angle brackets `<...>` in various declarations: -Filename: src/front_of_house.cairo +* **Definition**: `fn ident<...> ...`, `struct ident<...> ...`, `enum ident<...> ...`, `impl<...> ...` +* **Specification**: `path::<...>` or `method::<...>` (turbofish) is used to specify parameters in an expression. -```cairo,noplayground -pub mod hosting; -``` +### Generic Functions -Filename: src/front_of_house/hosting.cairo +Functions can operate on generic types, avoiding multiple type-specific implementations. When defining a generic function, generics are placed in the signature. -```cairo,noplayground -pub fn add_to_waitlist() {} -``` +For instance, a generic function signature: -The compiler uses the file and directory structure to determine the module tree. The `mod` declaration acts as a pointer to where the module's code resides, not as an include directive. This allows for a clean separation of concerns, with the module tree structure remaining consistent regardless of whether module code is in a single file or spread across multiple files. +```cairo +// Specify generic type T between the angulars +fn largest_list(l1: Array, l2: Array) -> Array { + if l1.len() > l2.len() { + l1 + } else { + l2 + } +} +``` -Advanced Topics: Traits and Component Integration +If operations like dropping an array of a generic type are required, the compiler needs assurance that the generic type `T` implements the necessary trait (like `Drop`). This is achieved using trait bounds: -# Advanced Topics: Traits and Component Integration +```cairo +fn largest_list>(l1: Array, l2: Array) -> Array { + if l1.len() > l2.len() { + l1 + } else { + l2 + } +} +``` -## Module Organization and Trait Implementation +### Constraints for Generic Types (Trait Bounds) -When organizing code into modules, if a trait's implementation resides in a different module than the trait itself, explicit imports are necessary. +Trait bounds constrain generic types to only accept types that implement a particular behavior. We saw this with `Drop`. -```cairo,noplayground -// Here T is an alias type which will be provided during implementation -pub trait ShapeGeometry { - fn boundary(self: T) -> u64; - fn area(self: T) -> u64; -} +#### Anonymous Generic Implementation Parameter (`+` Operator) -mod rectangle { - // Importing ShapeGeometry is required to implement this trait for Rectangle - use super::ShapeGeometry; +If the implementation of a required trait is not used in the function body, we can use the `+` operator to specify the constraint without naming the implementation (e.g., `+PartialOrd` is equivalent to `impl TPartialOrd: PartialOrd`). - #[derive(Copy, Drop)] - pub struct Rectangle { - pub height: u64, - pub width: u64, - } +An example using multiple constraints: - // Implementation RectangleGeometry passes in - // to implement the trait for that type - impl RectangleGeometry of ShapeGeometry { - fn boundary(self: Rectangle) -> u64 { - 2 * (self.height + self.width) - } - fn area(self: Rectangle) -> u64 { - self.height * self.width +```cairo +fn smallest_element, +Copy, +Drop>(list: @Array) -> T { + let mut smallest = *list[0]; + for element in list { + if *element < smallest { + smallest = *element; } } + smallest } +``` -mod circle { - // Importing ShapeGeometry is required to implement this trait for Circle - use super::ShapeGeometry; +### Generic Data Types - #[derive(Copy, Drop)] - pub struct Circle { - pub radius: u64, - } +Structs and enums can be defined using generic type parameters. - // Implementation CircleGeometry passes in - // to implement the imported trait for that type - impl CircleGeometry of ShapeGeometry { - fn boundary(self: Circle) -> u64 { - (2 * 314 * self.radius) / 100 - } - fn area(self: Circle) -> u64 { - (314 * self.radius * self.radius) / 100 - } - } -} -use circle::Circle; -use rectangle::Rectangle; +#### Generic Structs -#[executable] -fn main() { - let rect = Rectangle { height: 5, width: 7 }; - println!("Rectangle area: {}", ShapeGeometry::area(rect)); //35 - println!("Rectangle boundary: {}", ShapeGeometry::boundary(rect)); //24 +Structs use `<>` syntax after the name to declare type parameters, which are then used as field types. - let circ = Circle { radius: 5 }; - println!("Circle area: {}", ShapeGeometry::area(circ)); //78 - println!("Circle boundary: {}", ShapeGeometry::boundary(circ)); //31 +```cairo +#[derive(Drop)] +struct Wallet { + balance: T, } ``` -## Contract Integration - -Contracts integrate components using **impl aliases** to instantiate a component's generic impl with the contract's concrete state. +If implementing a trait for a generic struct, the implementation block must also declare generics and their constraints: -```cairo,noplayground - #[abi(embed_v0)] - impl OwnableImpl = OwnableComponent::OwnableImpl; +```cairo +struct Wallet { + balance: T, +} - impl OwnableInternalImpl = OwnableComponent::InternalImpl; +impl WalletDrop> of Drop>; ``` -This mechanism embeds the component's logic into the contract's ABI. The compiler automatically generates a `HasComponent` trait implementation, bridging the contract's and component's states. - -## Specifying Component Dependencies - -Components can specify dependencies on other components using trait bounds. This restricts an `impl` block to be available only for contracts that already contain the required component. - -For example, a dependency on an `Ownable` component is specified with `impl Owner: ownable_component::HasComponent`, ensuring the `TContractState` has access to the `Ownable` component. - -While this mechanism can be verbose, it leverages Cairo's trait system to manage component interactions. When a component is embedded in a contract, other components within the same contract can access it. +Structs can have multiple generic types: -Macros, Attributes, and Compiler Internals - -Cairo Attributes +```cairo +#[derive(Drop)] +struct Wallet { + balance: T, + address: U, +} +``` -# Cairo Attributes +#### Generic Enums -This section details various attributes available in Cairo, used to modify the behavior or provide hints to the compiler for items like functions, structs, and enums. +Enums can hold generic data types in their variants, such as `Option`: -## Core Attributes +```cairo +enum Option { + Some: T, + None, +} +``` -- **`#[derive(...)]`**: Automatically implements a specified trait for a type. -- **`#[inline]`**: A hint to the compiler to perform an inline expansion of the annotated function. - - **`#[inline(always)]`**: A stronger hint to systematically inline the function. - - **`#[inline(never)]`**: A hint to never inline the function. - Note: These attributes are hints and may be ignored by the compiler. Inlining can reduce execution steps by avoiding function call overhead but may increase code size. It is particularly beneficial for small, frequently called functions. -- **`#[must_use]`**: Indicates that the return value of a function or a specific returned type must be used by the caller. -- **`#[generate_trait]`**: Instructs the compiler to automatically generate a trait definition for the associated implementation block. This is useful for reducing boilerplate, especially for private implementation blocks, and can be used in conjunction with `#[abi(per_item)]`. -- **`#[available_gas(...)]`**: Sets the maximum amount of gas allowed for the execution of the annotated function. -- **`#[panic_with('...', wrapper_name)]`**: Creates a wrapper for the annotated function. This wrapper will cause a panic with the specified error data if the original function returns `None` or `Err`. -- **`#[test]`**: Marks a function as a test function, intended to be run by the testing framework. -- **`#[cfg(...)]`**: A configuration attribute used for conditional compilation, commonly employed to include test modules (e.g., `#[cfg(test)]`). -- **`#[should_panic]`**: Specifies that a test function is expected to panic. The test will only pass if the annotated function indeed panics. +Or multiple generic types, like `Result`: -## Contract ABI Attributes +```cairo +enum Result { + Ok: T, + Err: E, +} +``` -- **`#[abi(embed_v0)]`**: Used within an `impl` block to define that the functions within this block should be exposed as contract entrypoints, implementing a trait. -- **`#[abi(per_item)]`**: Allows for the individual definition of the entrypoint type for functions within an `impl` block. This attribute is often used with `#[generate_trait]`. When `#[abi(per_item)]` is used, public functions must be annotated with `#[external(v0)]` to be exposed; otherwise, they are treated as private. +### Generic Methods - ```cairo - #[starknet::contract] - mod ContractExample { - #[storage] - struct Storage {} +Methods can be implemented on generic types, using the type's generic parameters in the trait and impl definitions. Constraints can be specified on these generic types. - #[abi(per_item)] - #[generate_trait] - impl SomeImpl of SomeTrait { - #[constructor] - // this is a constructor function - fn constructor(ref self: ContractState) {} +If a method involves combining two potentially different generic types, constraints must be applied to all involved types: - #[external(v0)] - // this is a public function - fn external_function(ref self: ContractState, arg1: felt252) {} +```cairo +trait WalletMixTrait { + fn mixup, U2, +Drop>( + self: Wallet, other: Wallet, + ) -> Wallet; +} - #[l1_handler] - // this is a l1_handler function - fn handle_message(ref self: ContractState, from_address: felt252, arg: felt252) {} +impl WalletMixImpl, U1, +Drop> of WalletMixTrait { + fn mixup, U2, +Drop>( + self: Wallet, other: Wallet, + ) -> Wallet { + Wallet { balance: self.balance, address: other.address } + } +} +``` - // this is an internal function - fn internal_function(self: @ContractState) {} - } - } - ``` +#### Associated Types and Type Equality -- **`#[external(v0)]`**: Used to explicitly define a function as external when the `#[abi(per_item)]` attribute is applied to the implementation block. +Generics can constrain types based on associated types using traits like `TypeEqual`. This ensures that different generic implementations share the same associated type. -## Event Attributes +```cairo +trait StateMachine { + type State; + fn transition(ref state: Self::State); +} -- **`#[flat]`**: Applied to an enum variant of the `Event` enum. It signifies that the variant should not be nested during serialization and its name should be ignored, which is useful for composability with Starknet components. -- **`#[key]`**: Designates an `Event` enum field as indexed. This allows for more efficient querying and filtering of events based on this field. +// ... TA and TB implementations use StateCounter as State ... -Cairo Macros +fn combine< + impl A: StateMachine, + impl B: StateMachine, + +core::metaprogramming::TypeEqual, +>( + ref self: A::State, +) { + A::transition(ref self); + B::transition(ref self); +} +``` -# Cairo Macros +#### Generics in Closures -M মনোMacros in Cairo are a powerful metaprogramming feature that allows you to write code which generates other code. This is distinct from functions, which are executed at runtime. Macros are expanded by the compiler before the code is interpreted, enabling capabilities like implementing traits at compile time or accepting a variable number of arguments. +When using generics with closures (e.g., in array extension methods like `map`), the output type of the closure (an associated type of `Fn`) can differ from the input type `T`. -## The Difference Between Macros and Functions +```cairo +#[generate_trait] +impl ArrayExt of ArrayExtTrait { + // Needed in Cairo 2.11.4 because of a bug in inlining analysis. + #[inline(never)] + fn map, F, +Drop, impl func: core::ops::Fn, +Drop>( + self: Array, f: F, + ) -> Array { + let mut output: Array = array![]; + for elem in self { + output.append(f(elem)); + } + output + } +} +``` -While both macros and functions help reduce code duplication, macros offer additional flexibility: +--- -- **Variable Arguments**: Macros can accept any number of arguments, unlike functions which require a fixed signature. -- **Compile-Time Execution**: Macros are expanded during compilation, allowing them to generate code that affects the program's structure, such as implementing traits. +Sources: -However, macro definitions are generally more complex than function definitions due to the indirection involved in writing code that writes code. Additionally, macros must be defined or brought into scope _before_ they are called. +- https://www.starknet.io/cairo-book/ch08-02-traits-in-cairo.html +- https://www.starknet.io/cairo-book/ch05-03-method-syntax.html +- https://www.starknet.io/cairo-book/ch12-10-associated-items.html +- https://www.starknet.io/cairo-book/appendix-03-derivable-traits.html +- https://www.starknet.io/cairo-book/ch11-01-closures.html +- https://www.starknet.io/cairo-book/ch12-04-hash.html -## Declarative Inline Macros for General Metaprogramming +--- -Declarative macros, often referred to as plain "macros," allow you to write code that resembles a `match` expression. They compare patterns against the structure of the source code passed to the macro and replace it with associated code during compilation. +## Traits: Defining and Implementing Behavior -### Defining and Using Declarative Macros +### Defining a Trait -Macros are defined using the `macro` keyword. The syntax for patterns in macro definitions differs from value matching, as it operates on Cairo source code structure. +A trait defines a set of methods that can be implemented by a type, grouping method signatures to define necessary behaviors. Traits are most useful when used with generic types to define shared behavior abstractly, allowing trait bounds to specify required functionality for generic types. -A common example is an array-building macro: +To declare a trait, use the `trait` keyword followed by the name. If it needs to be accessible by other crates, declare it as `pub`. Inside, declare method signatures ending with a semicolon. If the trait is generic, it includes a generic type parameter, like `pub trait Summary { ... }`. +**Example of a non-generic trait definition:** ```cairo -macro make_array { - ($($x:expr), *) => { - { - let mut arr = $defsite::ArrayTrait::new(); - $(arr.append($x);)* - arr - } - }; +#[derive(Drop, Clone)] +struct NewsArticle { + headline: ByteArray, + location: ByteArray, + author: ByteArray, + content: ByteArray, } -# -# #[cfg(test)] -# #[test] -# fn test_make_array() { -# let a = make_array![1, 2, 3]; -# let expected = array![1, 2, 3]; -# assert_eq!(a, expected); -# } -# -# mod hygiene_demo { -# // A helper available at the macro definition site -# fn def_bonus() -> u8 { -# 10 -# } -# -# // Adds the defsite bonus, regardless of what exists at the callsite -# pub macro add_defsite_bonus { -# ($x: expr) => { $x + $defsite::def_bonus() }; -# } -# -# // Adds the callsite bonus, resolved where the macro is invoked -# pub macro add_callsite_bonus { -# ($x: expr) => { $x + $callsite::bonus() }; -# } -# -# // Exposes a variable to the callsite using `expose!`. -# pub macro apply_and_expose_total { -# ($base: expr) => { -# let total = $base + 1; -# expose!(let exposed_total = total;); -# }; -# } -# -# // A helper macro that reads a callsite-exposed variable -# pub macro read_exposed_total { -# () => { $callsite::exposed_total }; -# } -# -# // Wraps apply_and_expose_total and then uses another inline macro -# // that accesses the exposed variable via `$callsite::...`. -# pub macro wrapper_uses_exposed { -# ($x: expr) => { -# { -# $defsite::apply_and_expose_total!($x); -# $defsite::read_exposed_total!() -# } -# }; -# } -# } -# -# use hygiene_demo::{ -# add_callsite_bonus, add_defsite_bonus, apply_and_expose_total, wrapper_uses_exposed, -# }; -# #[cfg(test)] -# #[test] -# fn test_hygiene_e2e() { -# -# // Callsite defines its own `bonus` — used only by callsite-resolving macro -# let bonus = | | -> u8 { -# 20 -# }; -# let price: u8 = 5; -# assert_eq!(add_defsite_bonus!(price), 15); // uses defsite::def_bonus() = 10 -# assert_eq!(add_callsite_bonus!(price), 25); // uses callsite::bonus() = 20 -# -# // Call in statement position; it exposes `exposed_total` at the callsite -# apply_and_expose_total!(3); -# assert_eq!(exposed_total, 4); -# -# // A macro invoked by another macro can access exposed values via `$callsite::...` -# let w = wrapper_uses_exposed!(7); -# assert_eq!(w, 8); -# } -# -# -``` -When calling `make_array![1, 2, 3]`, the pattern `$($x:expr), *` matches `1`, `2`, and `3`. The expansion `$(arr.append($x);)*` then generates `arr.append(1); arr.append(2); arr.append(3);`. - -### Hygiene, `$defsite`/`$callsite`, and `expose!` +pub trait Summary { + fn summarize(self: @NewsArticle) -> ByteArray; +} -Cairo's inline macros are hygienic, meaning names introduced within the macro don't leak into the call site unless explicitly exposed. Name resolution can target either the macro definition site (`$defsite::`) or the call site (`$callsite::`). +impl NewsArticleSummary of Summary { + fn summarize(self: @NewsArticle) -> ByteArray { + format!("{:?} by {:?} ({:?})", self.headline, self.author, self.location) + } +} +``` -Macros are expected to expand to a single expression. If a macro defines multiple statements, they should be wrapped in a `{}` block. +### Implementing a Trait on a Type -An end-to-end example demonstrating these concepts: +Implementing a trait on a type involves using `impl ImplementationName of TraitName` followed by the type parameter in angle brackets (if generic). Within the block, method signatures are provided with their concrete implementation bodies. +**Example of implementing a generic trait:** ```cairo -# macro make_array { -# ($($x:expr), *) => { -# { -# let mut arr = $defsite::ArrayTrait::new(); -# $(arr.append($x);)* -# arr -# } -# }; -# } -# -# #[cfg(test)] -# #[test] -# fn test_make_array() { -# let a = make_array![1, 2, 3]; -# let expected = array![1, 2, 3]; -# assert_eq!(a, expected); -# } -# -mod hygiene_demo { - // A helper available at the macro definition site - fn def_bonus() -> u8 { - 10 - } - - // Adds the defsite bonus, regardless of what exists at the callsite - pub macro add_defsite_bonus { - ($x: expr) => { $x + $defsite::def_bonus() }; +mod aggregator { + pub trait Summary { + fn summarize(self: @T) -> ByteArray; } - // Adds the callsite bonus, resolved where the macro is invoked - pub macro add_callsite_bonus { - ($x: expr) => { $x + $callsite::bonus() }; + #[derive(Drop)] + pub struct NewsArticle { + pub headline: ByteArray, + pub location: ByteArray, + pub author: ByteArray, + pub content: ByteArray, } - // Exposes a variable to the callsite using `expose!`. - pub macro apply_and_expose_total { - ($base: expr) => { - let total = $base + 1; - expose!(let exposed_total = total;); - }; + impl NewsArticleSummary of Summary { + fn summarize(self: @NewsArticle) -> ByteArray { + format!("{} by {} ({})", self.headline, self.author, self.location) + } } - // A helper macro that reads a callsite-exposed variable - pub macro read_exposed_total { - () => { $callsite::exposed_total }; + #[derive(Drop)] + pub struct Tweet { + pub username: ByteArray, + pub content: ByteArray, + pub reply: bool, + pub retweet: bool, } - // Wraps apply_and_expose_total and then uses another inline macro - // that accesses the exposed variable via `$callsite::...`. - pub macro wrapper_uses_exposed { - ($x: expr) => { - { - $defsite::apply_and_expose_total!($x); - $defsite::read_exposed_total!() - } - }; + impl TweetSummary of Summary { + fn summarize(self: @Tweet) -> ByteArray { + format!("{}: {}", self.username, self.content) + } } } -# -# use hygiene_demo::{ -# add_callsite_bonus, add_defsite_bonus, apply_and_expose_total, wrapper_uses_exposed, -# }; -# #[cfg(test)] -# #[test] -# fn test_hygiene_e2e() { -# -# // Callsite defines its own `bonus` — used only by callsite-resolving macro -# let bonus = | | -> u8 { -# 20 -# }; -# let price: u8 = 5; -# assert_eq!(add_defsite_bonus!(price), 15); // uses defsite::def_bonus() = 10 -# assert_eq!(add_callsite_bonus!(price), 25); // uses callsite::bonus() = 20 -# -# // Call in statement position; it exposes `exposed_total` at the callsite -# apply_and_expose_total!(3); -# assert_eq!(exposed_total, 4); -# -# // A macro invoked by another macro can access exposed values via `$callsite::...` -# let w = wrapper_uses_exposed!(7); -# assert_eq!(w, 8); -# } -# -# -``` -### Enabling Inline Macros +use aggregator::{NewsArticle, Summary, Tweet}; + +#[executable] +fn main() { + let news = NewsArticle { + headline: "Cairo has become the most popular language for developers", + location: "Worldwide", + author: "Cairo Digger", + content: "Cairo is a new programming language for zero-knowledge proofs", + }; -To use user-defined inline macros, enable the experimental feature in your `Scarb.toml`: + let tweet = Tweet { + username: "EliBenSasson", + content: "Crypto is full of short-term maximizing projects. \n @Starknet and @StarkWareLtd are about long-term vision maximization.", + reply: false, + retweet: false, + }; // Tweet instantiation -```toml -# [package] -# name = "listing_inline_macros" -# version = "0.1.0" -# edition = "2024_07" -# -experimental-features = ["user_defined_inline_macros"] -# -# [cairo] -# -# [dependencies] -# cairo_execute = "2.12.0" -# -# [dev-dependencies] -# snforge_std = "0.48.0" -# assert_macros = "2.12.0" -# -# [scripts] -# test = "snforge test" -# -# [tool.scarb] -# allow-prebuilt-plugins = ["snforge_std"] + println!("New article available! {}", news.summarize()); + println!("New tweet! {}", tweet.summarize()); +} ``` -## Procedural Macros +### Methods and `self` Parameter -Procedural macros are Rust functions that transform Cairo code. They operate on `TokenStream` and return `ProcMacroResult`. They are defined using attributes like `#[inline_macro]`, `#[attribute_macro]`, and `#[derive_macro]`. +Methods must have a parameter named `self` as their first parameter. The type of `self` defines what instance the method is called on. `self` can be passed by ownership, snapshot (`@`), or reference (mutable reference `ref self`). -To use procedural macros, you need a Rust toolchain and a project structure including `Cargo.toml`, `Scarb.toml`, and `src/lib.rs`. The `Cargo.toml` specifies Rust dependencies and `crate-type = ["cdylib"]`, while `Scarb.toml` defines the package as a `[cairo-plugin]`. +If a method does not modify the instance, `self` is typically declared as a snapshot (`self: @Type`). If modification is needed, a mutable reference is used (`ref self: Type`). -### Expression Macros +Methods are defined within `impl` blocks, which organize capabilities associated with a type. While methods can be defined directly on types via traits, this is verbose. -Expression macros transform Cairo expressions. An example is a compile-time power function (`pow!`) implemented using Rust crates like `cairo-lang-macro` and `bigdecimal`. +### Avoiding Trait Definition with `#[generate_trait]` -### Derive Macros +To avoid manually defining a trait when it's only intended for implementation on a specific type, Cairo provides the `#[generate_trait]` attribute above the implementation block. This tells the compiler to generate the corresponding trait definition automatically. -Derive macros automate trait implementations for types. For instance, a `#[derive(HelloMacro)]` can automatically implement a `Hello` trait for a struct, generating a `hello()` function. +**Example using `#[generate_trait]`:** +```cairo +#[derive(Copy, Drop)] +struct Rectangle { + width: u64, + height: u64, +} + +#[generate_trait] +impl RectangleImpl of RectangleTrait { + fn area(self: @Rectangle) -> u64 { + (*self.width) * (*self.height) + } +} + +#[executable] +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + println!("Area is {}", rect1.area()); +} +``` + +### Default Implementations + +Traits can provide default behavior for methods. Implementors can either use the default by providing an empty `impl` block or override it by providing a custom implementation for that method signature. + +**Example with default implementation:** +```cairo +mod aggregator { + pub trait Summary { + fn summarize(self: @T) -> ByteArray { + "(Read more...)" + } + fn summarize_author(self: @T) -> ByteArray; // Required implementation + } -### Attribute Macros + #[derive(Drop)] + pub struct Tweet { + pub username: ByteArray, + pub content: ByteArray, + pub reply: bool, + pub retweet: bool, + } -Attribute macros are versatile and can be applied to various code items. They take an additional `attr` argument for attribute arguments, enabling actions like renaming structs or modifying function signatures. + impl TweetSummary of Summary { + fn summarize_author(self: @Tweet) -> ByteArray { + format!("@{}", self.username) + } + } +} -## Common Macros +use aggregator::{Summary, Tweet}; -Several built-in macros are available for common tasks: +#[executable] +fn main() { + let tweet = Tweet { + username: "EliBenSasson", + content: "Crypto is full of short-term maximizing projects. \n @Starknet and @StarkWareLtd are about long-term vision maximization.", + reply: false, + retweet: false, + }; -| Macro Name | Description | -| ------------------------ | ------------------------------------------------------ | -| `assert!` | Evaluates a Boolean and panics if `false`. | -| `assert_eq!` | Evaluates an equality and panics if not equal. | -| `assert_ne!` | Evaluates an equality and panics if equal. | -| `format!` | Formats a string and returns a `ByteArray`. | -| `write!` | Writes formatted strings to a formatter. | -| `writeln!` | Writes formatted strings to a formatter on a new line. | -| `get_dep_component!` | Retrieves component state from a snapshot. | -| `get_dep_component_mut!` | Retrieves mutable component state from a reference. | -| `component!` | Embeds a component inside a Starknet contract. | + println!("1 new tweet: {}", tweet.summarize()); +} +``` +In this case, `summarize` uses its default implementation because only `summarize_author` was implemented. -Comments are also supported using `//` for line comments. +### Derivable Traits -Compiler Internals and Optimizations +The `derive` attribute generates code to implement a default trait on a type. Traits compatible with `derive` in the standard library include `Clone` (for deep copying) and `Drop` (for automatic resource cleanup like squashing Dictionaries via the `Destruct` trait). -# Inlining in Cairo +**Example deriving `Clone` and `Drop`:** +```cairo +#[derive(Clone, Drop)] +struct A { + item: felt252, +} -Inlining is a common code optimization technique that involves replacing a function call at the call site with the actual code of the called function. This eliminates the overhead associated with function calls, potentially improving performance by reducing the number of instructions executed, though it may increase the total program size. Considerations for inlining include function size, parameters, call frequency, and impact on compiled code size. +#[executable] +fn main() { + let first_struct = A { item: 2 }; + let second_struct = first_struct.clone(); + assert!(second_struct.item == 2, "Not equal"); +} +``` -## The `inline` Attribute +--- -In Cairo, the `#[inline]` attribute suggests whether the Sierra code of an attributed function should be directly injected into the caller's context instead of using a `function_call` libfunc. This feature is experimental, and its syntax and capabilities may evolve. Item-producing macros (structs, enums, functions, etc.) are not yet supported; procedural macros are preferred for attributes, derives, and crate-wide transformations. +Sources: -> Inlining is often a tradeoff between the number of steps and code length. Use the `inline` attribute cautiously where it is appropriate. +- https://www.starknet.io/cairo-book/ch12-10-associated-items.html +- https://www.starknet.io/cairo-book/ch12-03-operator-overloading.html +- https://www.starknet.io/cairo-book/ch08-02-traits-in-cairo.html -## Inlining Decision Process +--- -For functions without explicit inline directives, the Cairo compiler uses a heuristic approach. The decision to inline is based on the function's complexity, primarily relying on the `DEFAULT_INLINE_SMALL_FUNCTIONS_THRESHOLD`. The compiler estimates a function's "weight" using `ApproxCasmInlineWeight` to gauge the complexity of the generated Cairo Assembly (CASM) statements. Functions with a weight below the threshold are typically inlined. +## Associated Items and Operator Overloading -The compiler also considers the raw statement count; functions with fewer statements than the threshold are usually inlined to optimize small, frequently called functions. Very simple functions (e.g., those that only call another function or return a constant) are always inlined. Conversely, functions with complex control flow (like `Match`) or those ending with a `Panic` are generally not inlined. +### Operator Overloading -## Inlining Example +Operator overloading allows redefining standard operators (like `+`, `-`, `*`, `/`) for user-defined types by implementing the corresponding trait for that operator. This enhances code readability if used judiciously. -Consider the following program demonstrating inlining: +For example, combining two `Potion` structs (with `health` and `mana` fields) can be achieved by implementing the `Add` trait: -```cairo -#[executable] -fn main() -> felt252 { - inlined() + not_inlined() +``` +trait Extend { + fn extend[Item: A], +Destruct>(ref self: T, iterator: I); } -#[inline(always)] -fn inlined() -> felt252 { - 1 +impl ArrayExtend> of Extend, T> { + fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { + for item in iterator { + self.append(item); + } + } } +``` -#[inline(never)] -fn not_inlined() -> felt252 { - 2 +``` +struct Potion { + health: felt252, + mana: felt252, +} + +impl PotionAdd of Add { + fn add(lhs: Potion, rhs: Potion) -> Potion { + Potion { health: lhs.health + rhs.health, mana: lhs.mana + rhs.mana } + } +} + +#[executable] +fn main() { + let health_potion: Potion = Potion { health: 100, mana: 0 }; + let mana_potion: Potion = Potion { health: 0, mana: 100 }; + let super_potion: Potion = health_potion + mana_potion; + // Both potions were combined with the `+` operator. + assert!(super_potion.health == 100); + assert!(super_potion.mana == 100); } ``` -Listing 12-5: A small Cairo program that adds the return value of 2 functions, with one of them being inlined +### Associated Items -In this example, the `inlined` function, annotated with `#[inline(always)]`, has its code directly injected into `main`. However, because its return value is not used, the compiler optimizes `main` by skipping the `inlined` function's code altogether, reducing code length and execution steps. +Associated items are items declared in traits or definitions in implementations, including associated functions (methods), associated types, associated constants, and associated implementations. They are useful when logically related to the implementation. -The `not_inlined` function, annotated with `#[inline(never)]`, is called normally using the `function_call` libfunc. The Sierra code execution for `main` would involve calling `not_inlined`, storing its result, and then dropping it as it's unused. Finally, a unit type `()` is returned as `main` doesn't explicitly return a value. +#### Associated Types -## Summary +Associated types are type aliases within traits, allowing trait implementers to choose the actual types. This keeps trait definitions flexible and clean. -Inlining is a valuable compiler optimization that can eliminate the overhead of function calls by injecting Sierra code directly into the caller's context. When used effectively, it can reduce the number of steps and potentially the code length, as demonstrated in the example. +Consider the `Pack` trait, where `Result` is an associated type placeholder: + +``` +trait Pack { + type Result; -Testing and Debugging + fn pack(self: T, other: T) -> Self::Result; +} -Introduction to Cairo Testing and Debugging +impl PackU32Impl of Pack { + type Result = u64; -# Introduction to Cairo Testing and Debugging + fn pack(self: u32, other: u32) -> Self::Result { + let shift: u64 = 0x100000000; // 2^32 + self.into() * shift + other.into() + } +} -## Executing the Program +fn bar>(self: T, b: T) -> PackImpl::Result { + PackImpl::pack(self, b) +} -To test a Cairo program, you can use the `scarb execute` command. This command runs the program and displays its output. +trait PackGeneric { + fn pack_generic(self: T, other: T) -> U; +} -**Example:** Executing a primality test program with the input `17`. +impl PackGenericU32 of PackGeneric { + fn pack_generic(self: u32, other: u32) -> u64 { + let shift: u64 = 0x100000000; // 2^32 + self.into() * shift + other.into() + } +} -```bash -scarb execute -p prime_prover --print-program-output --arguments 17 -``` +fn foo>(self: T, other: T) -> U { + self.pack_generic(other) +} -- `-p prime_prover`: Specifies the package name. -- `--print-program-output`: Displays the program's result. -- `--arguments 17`: Passes `17` as input to the program. +#[executable] +fn main() { + let a: u32 = 1; + let b: u32 = 1; -The output indicates success (0 for no panic) and the program's result (1 for true, meaning 17 is prime). + let x = foo(a, b); + let y = bar(a, b); -```bash -# Example output for 17 (prime) -$ scarb execute -p prime_prover --print-program-output --arguments 17 - Compiling prime_prover v0.1.0 (listings/ch01-getting-started/prime_prover/Scarb.toml) - Finished `dev` profile target(s) in 1 second - Executing prime_prover -Program output: -1 + // result is 2^32 + 1 + println!("x: {}", x); + println!("y: {}", y); +} ``` -Try with other numbers: +Using associated types (as in `bar`) leads to cleaner function signatures compared to explicitly listing the result type as a generic parameter (as in `foo`). -```bash -$ scarb execute -p prime_prover --print-program-output --arguments 4 -[0, 0] # 4 is not prime +#### Associated Constants + +Associated constants are constants tied to a type, declared with `const` in a trait and defined in its implementation. For example, the `Shape` trait defines an associated constant `SIDES`: -$ scarb execute -p prime_prover --print-program-output --arguments 23 -[0, 1] # 23 is prime ``` +trait Shape { + const SIDES: u32; + fn describe() -> ByteArray; +} + +struct Triangle {} -Execution artifacts, such as `air_public_input.json`, `air_private_input.json`, `trace.bin`, and `memory.bin`, are generated in the `./target/execute/prime_prover/execution1/` directory. +impl TriangleShape of Shape { + const SIDES: u32 = 3; + fn describe() -> ByteArray { + "I am a triangle." + } +} -## Generating a Zero-Knowledge Proof +struct Square {} -Cairo 2.10 integrates the Stwo prover via Scarb, enabling direct generation of zero-knowledge proofs. +impl SquareShape of Shape { + const SIDES: u32 = 4; + fn describe() -> ByteArray { + "I am a square." + } +} -To generate a proof for a specific execution (e.g., `execution1`), use the `scarb prove` command: +fn print_shape_info>() { + println!("I have {} sides. {}", ShapeImpl::SIDES, ShapeImpl::describe()); +} -```bash -$ scarb prove --execution-id 1 - Proving prime_prover -warn: soundness of proof is not yet guaranteed by Stwo, use at your own risk -Saving proof to: target/execute/prime_prover/execution1/proof/proof.json +#[executable] +fn main() { + print_shape_info::(); + print_shape_info::(); +} ``` -This command generates a proof that the primality check was computed correctly without revealing the input. +This allows generic functions like `print_shape_info` to access type-specific constants. -Understanding and Resolving Cairo Errors +#### Constraint Traits on Associated Items -# Understanding and Resolving Cairo Errors +Associated items can be constrained based on the generic parameter type using the `[AssociatedItem: ConstrainedValue]` syntax after a trait bound. This is experimental and requires `experimental-features = ["associated_item_constraints"]` in `Scarb.toml`. -This section details common error messages encountered in Cairo development and provides guidance on how to resolve them. +An example is constraining an iterator's `Item` associated type within an `extend` method implementation to match the collection's element type: -## Common Cairo Errors +``` +trait Extend { + fn extend[Item: A], +Destruct>(ref self: T, iterator: I); +} -### `Variable not dropped.` +impl ArrayExtend> of Extend, T> { + fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { + for item in iterator { + self.append(item); + } + } +} +``` -This error occurs when a variable of a type that does not implement the `Drop` trait goes out of scope without being explicitly destroyed. Ensure that variables requiring destruction implement either the `Drop` trait or the `Destruct` trait. Refer to the "Ownership" section for more details. +--- -### `Variable was previously moved.` +Sources: -This message indicates that you are attempting to use a variable whose ownership has already been transferred to another function. For types that do not implement the `Copy` trait, they are passed by value, transferring ownership. Such variables cannot be reused in the original context after ownership transfer. Consider using the `clone` method in these scenarios. +- https://www.starknet.io/cairo-book/ch12-05-macros.html +- https://www.starknet.io/cairo-book/ch12-10-procedural-macros.html +- https://www.starknet.io/cairo-book/ch08-02-traits-in-cairo.html -### `error: Trait has no implementation in context: core::fmt::Display::` +--- -This error arises when trying to print an instance of a custom data type using `{}` placeholders with `print!` or `println!`. To fix this, either manually implement the `Display` trait for your type or derive the `Debug` trait (using `#[derive(Debug)]`) and use `:?` placeholders for printing. +## Metaprogramming: Macros and Code Transformation -### `Got an exception while executing a hint: Hint Error: Failed to deserialize param #x.` +Macros are a family of features in Cairo that allow writing code that writes other code, known as metaprogramming. They expand to produce more code than written manually, useful for reducing boilerplate. -This error signifies that an entrypoint was called without the expected arguments. Verify that the arguments provided to the entrypoint are correct. A common pitfall involves `u256` variables, which are composed of two `u128` values; when calling a function expecting a `u256`, you must provide two arguments. +### Macros vs. Functions -### `Item path::item is not visible in this context.` +Functions require declared parameter counts and types, and are called at runtime. Macros, however, can take a variable number of parameters (e.g., `println!`) and are expanded before the compiler interprets the code, allowing them to perform compile-time actions like trait implementation. A downside is that macro definitions must be in scope before they are called, and their definitions are generally more complex than function definitions. -This error means that while the path to an item is correct, there's a visibility issue. By default, all items in Cairo are private to their parent modules. To resolve this, declare the necessary modules and items along the path using `pub(crate)` or `pub` to grant access. +### Declarative Inline Macros -### `Identifier not found.` +The simplest form is the declarative macro, defined using the `macro` construct. It operates similarly to a `match` expression, comparing the input source code structure against patterns. -This is a general error that can indicate: +#### Defining Declarative Macros -- A variable is being used before its declaration. Ensure variables are declared using `let`. -- The path used to bring an item into scope is incorrect. Verify that you are using valid paths. +Declarative macros allow matching code structure to associated code blocks that replace the macro invocation. -## Starknet Components Related Error Messages +The following example shows a simplified definition of an array-building macro: -### `Trait not found. Not a trait.` +```c +macro make_array { + ($($x:expr), *) => { + { + let mut arr = $defsite::ArrayTrait::new(); + $(arr.append($x);)* + arr + } + }; +} +``` -This error can occur when an implementation block for a component is not imported correctly into your contract. Ensure you follow the correct syntax for importing: +When called as `make_array![1, 2, 3]`, the `$x` pattern matches `1`, `2`, and `3`. The expansion `$(arr.append($x);)*` generates an `append` call for each matched expression, expanding to: -```cairo,noplayground -#[abi(embed_v0)] -impl IMPL_NAME = PATH_TO_COMPONENT::EMBEDDED_NAME +```c +{ + let mut arr = ArrayTrait::new(); + arr.append(1); + arr.append(2); + arr.append(3); + arr +} ``` -Testing Fundamentals: Writing and Running Tests +#### Macro Pattern Syntax -# Testing Fundamentals: Writing and Running Tests +Macro patterns are matched against Cairo source code structure. Key components include: +* Parentheses enclose the whole matcher pattern. +* `$dollar_sign` introduces a macro variable that captures matching code. `$x:expr` matches any Cairo expression and names it `$x`. +* A comma following `$()` requires literal commas between matched expressions. +* The `*` quantifier specifies the subpattern can repeat zero or more times. -Correctness in Cairo programs is crucial, and while the type system helps, it cannot catch everything. Cairo provides built-in support for writing tests to verify program behavior. +#### Hygiene: `$defsite`, `$callsite`, and `expose!` -## The Purpose of Tests +Cairo’s inline macros are hygienic, meaning names introduced in the definition do not leak into the call site unless explicitly exposed. Name resolution references the definition site or call site using `$defsite::` and `$callsite::`. + +To use user-defined inline macros, you must enable the experimental feature in `Scarb.toml`: +```toml +# [package] +# name = "listing_inline_macros" +# version = "0.1.0" +# edition = "2024_07" +# +experimental-features = ["user_defined_inline_macros"] +# +# [cairo] +# +# [dependencies] +# cairo_execute = "2.12.0" +# +# [dev-dependencies] +# snforge_std = "0.48.0" +# assert_macros = "2.12.0" +# +# [scripts] +# test = "snforge test" +# +# [tool.scarb] +# allow-prebuilt-plugins = ["snforge_std"] +``` +Macros are expected to expand to a single expression; wrapping multiple statements in `{}` achieves this. -Tests are Cairo functions designed to verify that other code functions as intended. A typical test function involves three steps: +The following example demonstrates hygiene and name resolution: -1. **Set up**: Prepare any necessary data or state. -2. **Run**: Execute the code being tested. -3. **Assert**: Verify that the results match expectations. +```c +macro make_array { + ($($x:expr), *) => { + { + let mut arr = $defsite::ArrayTrait::new(); + $(arr.append($x);)* + arr + } + }; +} -## Anatomy of a Test Function +#[cfg(test)] +#[test] +fn test_make_array() { + let a = make_array![1, 2, 3]; + let expected = array![1, 2, 3]; + assert_eq!(a, expected); +} -Cairo offers several features for writing tests: +mod hygiene_demo { + // A helper available at the macro definition site + fn def_bonus() -> u8 { + 10 + } -- `#[test]` attribute: Marks a function as a test. -- `assert!` macro: Checks if a condition is true. -- `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!`, `assert_ge!` macros: For comparing values. These require `assert_macros` as a dev dependency. -- `#[should_panic]` attribute: Asserts that a test function panics. + // Adds the defsite bonus, regardless of what exists at the callsite + pub macro add_defsite_bonus { + ($x: expr) => { $x + $defsite::def_bonus() }; + } -### The `#[test]` Attribute + // Adds the callsite bonus, resolved where the macro is invoked + pub macro add_callsite_bonus { + ($x: expr) => { $x + $callsite::bonus() }; + } -A function annotated with `#[test]` is recognized by the test runner. The `scarb test` command executes these functions. + // Exposes a variable to the callsite using `expose!`. + pub macro apply_and_expose_total { + ($base: expr) => { + let total = $base + 1; + expose!(let exposed_total = total;); + }; + } -```cairo,noplayground -#[cfg(test)] -mod tests { - use super::*; + // A helper macro that reads a callsite-exposed variable + pub macro read_exposed_total { + () => { $callsite::exposed_total }; + } - #[test] - fn it_works() { - let result = add(2, 2); // Assuming 'add' is a function in the outer scope - assert_eq!(result, 4); + // Wraps apply_and_expose_total and then uses another inline macro + // that accesses the exposed variable via `$callsite::...`. + pub macro wrapper_uses_exposed { + ($x: expr) => { + { + $defsite::apply_and_expose_total!($x); + $defsite::read_exposed_total!() + } + }; } } -``` -### The `#[cfg(test)]` Attribute +use hygiene_demo::{ + add_callsite_bonus, add_defsite_bonus, apply_and_expose_total, wrapper_uses_exposed, +}; +#[cfg(test)] +#[test] +fn test_hygiene_e2e() { -The `#[cfg(test)]` attribute ensures that the code within the annotated module (typically `mod tests`) is compiled only when running tests. This is necessary for test code that might not be valid in other compilation contexts. + // Callsite defines its own `bonus` — used only by callsite-resolving macro + let bonus = | | -> u8 { + 20 + }; + let price: u8 = 5; + assert_eq!(add_defsite_bonus!(price), 15); // uses defsite::def_bonus() = 10 + assert_eq!(add_callsite_bonus!(price), 25); // uses callsite::bonus() = 20 -```cairo,noplayground -#[cfg(test)] -mod tests { - use super::*; + // Call in statement position; it exposes `exposed_total` at the callsite + apply_and_expose_total!(3); + assert_eq!(exposed_total, 4); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } + // A macro invoked by another macro can access exposed values via `$callsite::...` + let w = wrapper_uses_exposed!(7); + assert_eq!(w, 8); } ``` +This demonstrates: +* `$defsite::...` resolves to items next to the macro definition. +* `$callsite::...` resolves to items visible where the macro is invoked. +* `expose!` deliberately introduces new items into the call site, accessible via `$callsite::name`. -### Testing Private Functions +### Procedural Macros -Cairo allows testing private functions. Items within child modules (like the `tests` module) can access items in their ancestor modules. +Procedural macros are Rust functions that transform Cairo code. They are preferred over declarative macros when needing attributes/derives or advanced transformations requiring Rust logic. They operate on `TokenStream` (source code units) and return a `ProcMacroResult`. -```cairo,noplayground -// Filename: src/lib.cairo -pub fn add(a: u32, b: u32) -> u32 { - internal_adder(a, 2) -} +#### Procedural Macro Types and Signatures -fn internal_adder(a: u32, b: u32) -> u32 { - a + b -} +Macros are defined using one of three attributes: +* `#[inline_macro]`: For function-like calls (e.g., `println!()`). + ```c + #[inline_macro] + pub fn inline(code: TokenStream) -> ProcMacroResult {} + ``` +* `#[attribute_macro]`: For custom attributes. + ```c + #[attribute_macro] + pub fn attribute(attr: TokenStream, code: TokenStream) -> ProcMacroResult {} + ``` +* `#[derive_macro]`: For automatic trait implementations. + ```c + #[derive_macro] + pub fn derive(code: TokenStream) -> ProcMacroResult {} + ``` -#[cfg(test)] -mod tests { - use super::*; // Brings internal_adder into scope +#### Project Setup and Dependencies - #[test] - fn test_private_function() { - // The test can call the private function directly - assert_eq!(4, internal_adder(2, 2)); - } -} -``` +Creating a procedural macro requires a Rust project structure with `Cargo.toml` (defining `crate-type = ["cdylib"]` and `cairo-lang-macro` dependency) and a Cairo project structure with `Scarb.toml` marking the package as a plugin: -## Running Tests +`Cargo.toml` example: +```toml +[package] +name = "pow" +version = "0.1.0" +edition = "2021" +publish = false -The `scarb test` command compiles and runs all functions annotated with `#[test]` in the project. The output provides details on passed, failed, ignored, and filtered tests. +[lib] +crate-type = ["cdylib"] -Example output: +[dependencies] +bigdecimal = "0.4.5" +cairo-lang-macro = "0.1.1" +cairo-lang-parser = "2.12.0" +cairo-lang-syntax = "2.12.0" -```shell -$ scarb test -... -Collected 2 test(s) from listing_10_01 package -Running 2 test(s) from src/ -[PASS] listing_10_01::other_tests::exploration (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~40000) -[PASS] listing_10_01::tests::it_works (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~40000) -Tests: 2 passed, 0 failed, 0 ignored, 0 filtered out +[workspace] ``` -## Examples - -### Testing Conversions - -This example tests a `parse_u8` function that converts `felt252` to `u8`. +`Scarb.toml` example for the macro package: +```toml +[package] +name = "pow" +version = "0.1.0" -```cairo,noplayground -fn parse_u8(s: felt252) -> Result { - match s.try_into() { - Some(value) => Ok(value), - None => Err('Invalid integer'), - } -} +[cairo-plugin] +``` -#[cfg(test)] -mod tests { - use super::*; +To use the macro, the consuming project adds the macro package to its `[dependencies]` in `Scarb.toml`, e.g., `pow = { path = "../path/to/pow" }`. - #[test] - fn test_felt252_to_u8() { - let number: felt252 = 5; - // should not panic - let res = parse_u8(number).unwrap(); - } +#### Expression Macros - #[test] - #[should_panic] - fn test_felt252_to_u8_panic() { - let number: felt252 = 256; - // should panic - let res = parse_u8(number).unwrap(); - } -} -``` +If the goal is expression transformation, declarative inline macros are simpler. Procedural expression macros are used when Rust-powered parsing is needed. -### Testing Struct Methods +#### Derive Macros -This tests a `can_hold` method on a `Rectangle` struct. +Derive macros generate custom trait implementations automatically. The macro receives the type's structure, generates the implementation logic, and outputs the code. -```cairo,noplayground -#[derive(Drop)] -struct Rectangle { - width: u64, - height: u64, -} +To implement the `Hello` trait using `HelloMacro`: -trait RectangleTrait { - fn can_hold(self: @Rectangle, other: @Rectangle) -> bool; -} +```c +#[derive_macro] +pub fn hello_macro(token_stream: TokenStream) -> ProcMacroResult { + let db = SimpleParserDatabase::default(); + let (parsed, _diag) = db.parse_virtual_with_diagnostics(token_stream); + let mut nodes = parsed.descendants(&db); -impl RectangleImpl of RectangleTrait { - fn can_hold(self: @Rectangle, other: @Rectangle) -> bool { - *self.width > *other.width && *self.height > *other.height + let mut struct_name = String::new(); + for node in nodes.by_ref() { + if node.kind(&db) == TerminalStruct { + struct_name = nodes + .find(|node| node.kind(&db) == TokenIdentifier) + .unwrap() + .get_text(&db); + break; + } } -} -#[cfg(test)] -mod tests { - use super::*; + ProcMacroResult::new(TokenStream::new(indoc::formatdoc! {r#" + impl SomeHelloImpl of Hello<{0}> { + fn hello(self: @{0}) { + println!("Hello {0}!"); + } + } + "#, struct_name})) +} +``` +The trait `Hello` must be defined or imported in the consuming code. - #[test] - fn larger_can_hold_smaller() { - let larger = Rectangle { height: 7, width: 8 }; - let smaller = Rectangle { height: 1, width: 5 }; +#### Attribute Macros - assert!(larger.can_hold(@smaller), "rectangle cannot hold"); - } -} +Attribute macros can apply to any item (like functions) and use a second argument (`attr: TokenStream`) to receive the attribute arguments (e.g., `#[macro(arguments)]`). -#[cfg(test)] -mod tests2 { - use super::*; +An example creating a macro to rename a struct: - #[test] - fn smaller_cannot_hold_larger() { - let larger = Rectangle { height: 7, width: 8 }; - let smaller = Rectangle { height: 1, width: 5 }; +```c +use cairo_lang_macro::attribute_macro; +use cairo_lang_macro::{ProcMacroResult, TokenStream}; - assert!(!smaller.can_hold(@larger), "rectangle cannot hold"); - } +#[attribute_macro] +pub fn rename(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { + ProcMacroResult::new(TokenStream::new( + token_stream + .to_string() + .replace("struct OldType", "#[derive(Drop)]\n struct RenamedType"), + )) } ``` +Usage example in a Cairo file: +```c +#[executable] +fn main() { + let a = SomeType {}; + a.hello(); -### Testing Starknet Contracts + let res = pow!(10, 2); + println!("res : {}", res); -Tests for Starknet contracts can be written using `snforge_std` and can either deploy the contract or interact with its internal state for testing. + let _a = RenamedType {}; +} -```cairo,noplayground -use snforge_std::{ - ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, load, spy_events, - start_cheat_caller_address, stop_cheat_caller_address, -}; -use starknet::storage::StoragePointerReadAccess; -use starknet::{ContractAddress, contract_address_const}; -use crate::pizza::PizzaFactory::{Event as PizzaEvents, PizzaEmission}; -use crate::pizza::PizzaFactory::{InternalTrait}; -use crate::pizza::{IPizzaFactoryDispatcher, IPizzaFactoryDispatcherTrait, PizzaFactory}; +#[derive(HelloMacro, Drop, Destruct)] +struct SomeType {} -fn owner() -> ContractAddress { - contract_address_const::<'owner'>() -} +#[rename] +struct OldType {} -fn deploy_pizza_factory() -> (IPizzaFactoryDispatcher, ContractAddress) { - let contract = declare("PizzaFactory").unwrap().contract_class(); - let owner: ContractAddress = contract_address_const::<'owner'>(); - let constructor_calldata = array![owner.into()]; - let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); - let dispatcher = IPizzaFactoryDispatcher { contract_address }; - (dispatcher, contract_address) +trait Hello { + fn hello(self: @T); } +``` -#[test] -fn test_constructor() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); +### Advanced Constraints: TypeEqual Trait - let pepperoni_count = load(pizza_factory_address, selector!("pepperoni"), 1); - let pineapple_count = load(pizza_factory_address, selector!("pineapple"), 1); - assert_eq!(pepperoni_count, array![10]); - assert_eq!(pineapple_count, array![10]); - assert_eq!(pizza_factory.get_owner(), owner()); -} +The `core::metaprogramming::TypeEqual` trait allows creating constraints based on type equality, useful in advanced scenarios like excluding specific types from a trait implementation. -#[test] -fn test_change_owner_should_change_owner() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); - let new_owner: ContractAddress = contract_address_const::<'new_owner'>(); - assert_eq!(pizza_factory.get_owner(), owner()); - start_cheat_caller_address(pizza_factory_address, owner()); - pizza_factory.change_owner(new_owner); - assert_eq!(pizza_factory.get_owner(), new_owner); -} +To implement a trait for all types that implement `Default`, except for `SensitiveData`: -#[test] -#[should_panic(expected: "Only the owner can set ownership")] -fn test_change_owner_should_panic_when_not_owner() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); - let not_owner = contract_address_const::<'not_owner'>(); - start_cheat_caller_address(pizza_factory_address, not_owner); - pizza_factory.change_owner(not_owner); - stop_cheat_caller_address(pizza_factory_address); +```c +trait SafeDefault { + fn safe_default() -> T; } -#[test] -#[should_panic(expected: "Only the owner can make pizza")] -fn test_make_pizza_should_panic_when_not_owner() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); - let not_owner = contract_address_const::<'not_owner'>(); - start_cheat_caller_address(pizza_factory_address, not_owner); - pizza_factory.make_pizza(); +#[derive(Drop, Default)] +struct SensitiveData { + secret: felt252, } -#[test] -fn test_make_pizza_should_increment_pizza_counter() { - // Setup - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); - start_cheat_caller_address(pizza_factory_address, owner()); - let mut spy = spy_events(); - - // When - pizza_factory.make_pizza(); - - // Then - let expected_event = PizzaEvents::PizzaEmission(PizzaEmission { counter: 1 }); - assert_eq!(pizza_factory.count_pizza(), 1); - spy.assert_emitted(@array![(pizza_factory_address, expected_event)]); +// Implement SafeDefault for all types EXCEPT SensitiveData +impl SafeDefaultImpl< + T, +Default, -core::metaprogramming::TypeEqual, +> of SafeDefault { + fn safe_default() -> T { + Default::default() + } } -#[test] -fn test_set_as_new_owner_direct() { - let mut state = PizzaFactory::contract_state_for_testing(); - let owner: ContractAddress = contract_address_const::<'owner'>(); - state.set_owner(owner); - assert_eq!(state.owner.read(), owner); +#[executable] +fn main() { + let _safe: u8 = SafeDefault::safe_default(); + let _unsafe: SensitiveData = Default::default(); // Allowed + // This would cause a compile error: +// let _dangerous: SensitiveData = SafeDefault::safe_default(); } ``` -Assertions and Test Verification +--- -# Assertions and Test Verification +Sources: -The `assert!` macro is used to verify that a condition in a test evaluates to `true`. If the condition is `false`, the macro calls `panic()` with a provided message, causing the test to fail. +- https://www.starknet.io/cairo-book/ch12-09-deref-coercion.html +- https://www.starknet.io/cairo-book/ch11-01-closures.html +- https://www.starknet.io/cairo-book/ch12-10-arithmetic-circuits.html +- https://www.starknet.io/cairo-book/ch12-00-advanced-features.html -## `assert_eq!` and `assert_ne!` Macros +--- -These macros provide a convenient way to test for equality (`assert_eq!`) or inequality (`assert_ne!`) between two values. They are preferred over using `assert!(left == right)` because they print the differing values upon failure, aiding in debugging. +# Advanced Features: Deref Coercion, Closures, and Circuits -To use these macros with custom types like structs or enums, they must implement the `PartialEq` and `Debug` traits. These traits can often be derived using `#[derive(Debug, PartialEq)]`. +This section covers advanced language features including implicit type coercion via traits, the mechanics of closures, and the use of arithmetic circuits. -**Example using `assert_eq!` and `assert_ne!`:** +### Deref Coercion -```cairo, noplayground -pub fn add_two(a: u32) -> u32 { - a + 2 -} +Deref coercion simplifies interacting with nested or wrapped data structures by allowing an instance of one type (`T`) to implicitly behave like an instance of another type (`K`) if `T` implements the `Deref` or `DerefMut` traits to `K`. -#[cfg(test)] -mod tests { - use super::*; +The traits are defined as: - #[test] - fn it_adds_two() { - assert_eq!(4, add_two(2)); - } +```cairo +pub trait Deref { + type Target; + fn deref(self: T) -> Self::Target; +} - #[test] - fn wrong_check() { - assert_ne!(0, add_two(2)); - } +pub trait DerefMut { + type Target; + fn deref_mut(ref self: T) -> Self::Target; } ``` -When an `assert_eq!` fails, it provides a detailed message showing the expected and actual values. For instance, if `add_two` incorrectly returned `5` for an input of `2`, the failure message would indicate `4: 4` and `add_two(2): 5`. - -## `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!` Macros - -These macros are used for comparison tests: - -- `assert_lt!`: Checks if the first value is strictly less than the second. -- `assert_le!`: Checks if the first value is less than or equal to the second. -- `assert_gt!`: Checks if the first value is strictly greater than the second. -- `assert_ge!`: Checks if the first value is greater than or equal to the second. - -To use these macros with custom types, the `PartialOrd` trait must be implemented. The `Copy` trait may also be necessary if instances are used multiple times. +Implementing `Deref` for a wrapper type allows direct access to the wrapped type's members: -**Example using comparison macros:** - -```cairo, noplayground -#[derive(Drop, Copy, Debug, PartialEq)] -struct Dice { - number: u8, +```cairo +#[derive(Drop, Copy)] +struct UserProfile { + username: felt252, + email: felt252, + age: u16, } -impl DicePartialOrd of PartialOrd { - fn lt(lhs: Dice, rhs: Dice) -> bool { - lhs.number < rhs.number - } - - fn le(lhs: Dice, rhs: Dice) -> bool { - lhs.number <= rhs.number - } - - fn gt(lhs: Dice, rhs: Dice) -> bool { - lhs.number > rhs.number - } +#[derive(Drop, Copy)] +struct Wrapper { + value: T, +} - fn ge(lhs: Dice, rhs: Dice) -> bool { - lhs.number >= rhs.number +impl DerefWrapper of Deref> { + type Target = T; + fn deref(self: Wrapper) -> T { + self.value } } -#[cfg(test)] -#[test] -fn test_struct_equality() { - let first_throw = Dice { number: 5 }; - let second_throw = Dice { number: 2 }; - let third_throw = Dice { number: 6 }; - let fourth_throw = Dice { number: 5 }; - - assert_gt!(first_throw, second_throw); - assert_ge!(first_throw, fourth_throw); - assert_lt!(second_throw, third_throw); - assert_le!( - first_throw, fourth_throw, "{:?},{:?} should be lower or equal", first_throw, fourth_throw, - ); +#[executable] +fn main() { + let wrapped_profile = Wrapper { + value: UserProfile { username: 'john_doe', email: '[email protected]', age: 30 }, + }; + // Access fields directly via deref coercion + println!("Username: {}", wrapped_profile.username); + println!("Current age: {}", wrapped_profile.age); } ``` -## Adding Custom Failure Messages +`DerefMut` only applies to mutable variables. Deref coercion also permits calling methods defined on the target type directly on the source instance: -Optional arguments can be provided to assertion macros (`assert!`, `assert_eq!`, `assert_ne!`) to include custom failure messages. These arguments are formatted using the `format!` macro syntax, allowing for detailed explanations when a test fails. +```cairo +struct MySource { + pub data: u8, +} -**Example with a custom message:** +struct MyTarget { + pub data: u8, +} -```cairo, noplayground - #[test] - fn it_adds_two() { - assert_eq!(4, add_two(2), "Expected {}, got add_two(2)={}", 4, add_two(2)); +#[generate_trait] +impl TargetImpl of TargetTrait { + fn foo(self: MyTarget) -> u8 { + self.data } -``` - -This results in a more informative error message upon failure, such as "Expected 4, got add_two(2)=5". - -Advanced Testing Techniques: Panics and Ignoring Tests - -# Advanced Testing Techniques: Panics and Ignoring Tests - -## Handling Panics in Tests - -Cairo provides mechanisms to handle and test for panics, which are runtime errors that halt program execution. - -### `panic!` Macro - -The `panic!` macro is a convenient way to halt execution and signal an error. It can accept a string literal as an argument, allowing for longer error messages than the 31-character limit of `panic_with_felt252`. +} -```cairo -#[executable] -fn main() { - if true { - panic!("2"); +impl SourceDeref of Deref { + type Target = MyTarget; + fn deref(self: MySource) -> MyTarget { + MyTarget { data: self.data } } - println!("This line isn't reached"); } -``` - -### `panic_with_felt252` Function - -For a more idiomatic approach, `panic_with_felt252` can be used. It takes a `felt252` value as an argument, making it a concise way to express intent, especially when a simple error code is sufficient. - -```cairo -use core::panic_with_felt252; #[executable] fn main() { - panic_with_felt252(2); + let source = MySource { data: 5 }; + // Thanks to the Deref impl, we can call foo directly on MySource + let res = source.foo(); + assert!(res == 5); } ``` -## Testing for Panics with `should_panic` - -The `#[should_panic]` attribute can be added to a test function to assert that the test is expected to panic. The test passes if a panic occurs and fails if no panic occurs. +### Closures -Consider a `Guess` struct where the `new` method panics if the input value is out of range: +The way a closure handles captured values determines which `Fn` traits it implements: -```cairo -#[derive(Drop)] -struct Guess { - value: u64, -} +1. **`FnOnce`**: Implemented by all closures. Applies if the closure moves captured values out of its body (callable only once). +2. **`Fn`**: Applies if the closure does not move captured values out and does not mutate them (callable multiple times). -pub trait GuessTrait { - fn new(value: u64) -> Guess; -} +The `unwrap_or_else` method on `OptionTrait` uses `FnOnce` to constrain the provided closure `f`, as it is called at most once: -impl GuessImpl of GuessTrait { - fn new(value: u64) -> Guess { - if value < 1 || value > 100 { - panic!("Guess must be >= 1 and <= 100"); +```cairo +pub impl OptionTraitImpl of OptionTrait { + #[inline] + fn unwrap_or_else, impl func: core::ops::FnOnce[Output: T], +Drop>( + self: Option, f: F, + ) -> T { + match self { + Some(x) => x, + None => f(), } - - Guess { value } } } ``` -A test to verify this panic behavior would look like: +Closures are extensively used for functional programming patterns like `map` and `filter` on arrays: ```cairo -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic] - fn greater_than_100() { - GuessTrait::new(200); +#[generate_trait] +impl ArrayExt of ArrayExtTrait { + // Needed in Cairo 2.11.4 because of a bug in inlining analysis. + #[inline(never)] + fn map, F, +Drop, impl func: core::ops::Fn, +Drop>( + self: Array, f: F, + ) -> Array { + let mut output: Array = array![]; + for elem in self { + output.append(f(elem)); + } + output } } -``` - -If the code within a `#[should_panic]` test does not panic, the test will fail with a message like "Expected to panic, but no panic occurred". -### Precise `should_panic` Tests - -To make `#[should_panic]` tests more robust, you can specify an `expected` string. The test will only pass if the panic message contains the specified text. +#[generate_trait] +impl ArrayFilterExt of ArrayFilterExtTrait { + // Needed in Cairo 2.11.4 because of a bug in inlining analysis. + #[inline(never)] + fn filter< + T, + +Copy, + +Drop, + F, + +Drop, + impl func: core::ops::Fn[Output: bool], + +Drop, + >( + self: Array, f: F, + ) -> Array { + let mut output: Array = array![]; + for elem in self { + if f(elem) { + output.append(elem); + } + } + output + } +} -```cairo -#[cfg(test)] -mod tests { - use super::*; +#[executable] +fn main() { + let double = |value| value * 2; + println!("Double of 2 is {}", double(2_u8)); + println!("Double of 4 is {}", double(4_u8)); - #[test] - #[should_panic(expected: "Guess must be <= 100")] - fn greater_than_100() { - GuessTrait::new(200); - } -} -``` + // This won't work because `value` type has been inferred as `u8`. + //println!("Double of 6 is {}", double(6_u16)); -If the panic message does not match the `expected` string, the test will fail, indicating the mismatch. + let sum = |x: u32, y: u32, z: u16| { + x + y + z.into() + }; + println!("Result: {}", sum(1, 2, 3)); -## Ignoring Tests + let x = 8; + let my_closure = |value| { + x * (value + 3) + }; -Tests that are time-consuming or not relevant for regular runs can be ignored using the `#[ignore]` attribute. + println!("my_closure(1) = {}", my_closure(1)); -```cairo -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - // ... - } + let double = array![1, 2, 3].map(|item: u32| item * 2); + let another = array![1, 2, 3].map(|item: u32| { + let x: u64 = item.into(); + x * x + }); - #[test] - #[ignore] - fn expensive_test() { // code that takes an hour to run - } + println!("double: {:?}", double); + println!("another: {:?}", another); + + let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); + println!("even: {:?}", even); } ``` -When tests are ignored, they are marked as `[IGNORE]` in the output. To run all tests, including ignored ones, use the `scarb test --include-ignored` command. - -## Running Single Tests +### Arithmetic Circuits -To run only specific tests, you can pass the test function's name as an argument to `scarb test`. Partial names can also be used to run multiple tests matching the pattern. +Arithmetic circuits are mathematical models representing polynomial computations over a field, consisting of input signals and arithmetic operations (addition/multiplication gates). Cairo supports emulated arithmetic circuits up to 384 bits, useful for implementing verification for proof systems or cryptographic primitives. -```shell -$ scarb test add_two_and_two -``` +Cairo's circuit constructs are found in `core::circuit`. Basic operations include `circuit_add` and `circuit_mul`. The following example computes $a \cdot (a + b)$ over the BN254 prime field, where inputs $a=10$ and $b=20$: -This command will execute only the `add_two_and_two` test. The output will indicate tests that were filtered out. +```cairo +use core::circuit::{ + AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, + CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, +}; -Test Organization: Unit vs. Integration Tests +// Circuit: a * (a + b) +// witness: a = 10, b = 20 +// expected output: 10 * (10 + 20) = 300 +fn eval_circuit() -> (u384, u384) { + let a = CircuitElement::> {}; + let b = CircuitElement::> {}; -# Test Organization: Unit vs. Integration Tests + let add = circuit_add(a, b); + let mul = circuit_mul(a, add); -Tests in Cairo can be broadly categorized into two main types: unit tests and integration tests. Both are crucial for ensuring code correctness, both in isolation and when components interact. + let output = (mul,); -## Unit Tests + let mut inputs = output.new_inputs(); + inputs = inputs.next([10, 0, 0, 0]); + inputs = inputs.next([20, 0, 0, 0]); -Unit tests focus on verifying individual units of code in isolation. This allows for quick pinpointing of issues within a specific module or function. + let instance = inputs.done(); -### Location and Structure + let bn254_modulus = TryInto::< + _, CircuitModulus, + >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) + .unwrap(); -Unit tests are typically placed within the `src` directory, in the same file as the code they are testing. The convention is to create a module named `tests` within the file and annotate it with `#[cfg(test]]`. + let res = instance.eval(bn254_modulus).unwrap(); -The `#[cfg(test]]` attribute instructs the compiler to only compile and run this code when `scarb test` is invoked, not during a regular build (`scarb build`). This prevents test code from being included in the final compiled artifact, saving space and compile time. + let add_output = res.get_output(add); + let circuit_output = res.get_output(mul); -**Example Unit Test:** + assert!(add_output == u384 { limb0: 30, limb1: 0, limb2: 0, limb3: 0 }, "add_output"); + assert!(circuit_output == u384 { limb0: 300, limb1: 0, limb2: 0, limb3: 0 }, "circuit_output"); -```cairo -pub fn add(left: usize, right: usize) -> usize { - left + right + (add_output, circuit_output) } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +#[executable] +fn main() { + eval_circuit(); } ``` -## Integration Tests +--- -Integration tests validate how different parts of your library work together. They simulate how an external user would interact with your code, using only the public interface. +Sources: -### The `tests` Directory +- https://www.starknet.io/cairo-book/ch12-06-inlining-in-cairo.html +- https://www.starknet.io/cairo-book/ch12-10-arithmetic-circuits.html +- https://www.starknet.io/cairo-book/ch103-02-01-under-the-hood.html +- https://www.starknet.io/cairo-book/ch12-10-procedural-macros.html -Integration tests reside in a top-level directory named `tests`, parallel to the `src` directory. `scarb` automatically recognizes this directory and compiles each file within it as a separate crate. +--- -**Directory Structure Example:** +--- -```shell -adder -├── Scarb.lock -├── Scarb.toml -├── src -│ └── lib.cairo -└── tests - └── integration_test.cairo -``` - -**Example Integration Test:** - -To test a function `add_two` from the `adder` crate: - -```cairo -// tests/integration_tests.cairo -use adder::add_two; - -#[test] -fn it_adds_two() { - assert_eq!(4, add_two(2)); -} -``` - -Note the `use adder::add_two;` statement, which is necessary to bring the library's functionality into the scope of the separate integration test crate. Unlike unit tests, files in the `tests` directory do not require the `#[cfg(test]]` attribute, as `scarb` handles their compilation context. +Sources: -### Running and Filtering Tests +- https://www.starknet.io/cairo-book/ch103-02-01-under-the-hood.html -When you run `scarb test`, the output is divided into sections, first for integration tests (one section per file in `tests/`) and then for unit tests. If any test fails, subsequent sections may not run. +--- -You can filter tests using the `-f` flag with `scarb test`. For example, `scarb test -f integration_tests::internal` runs a specific integration test function, while `scarb test -f integration_tests` runs all tests within files containing "integration_tests" in their path. +## Compiler Internals and Component Structure -### Organizing Integration Tests with Submodules +### Components: Under the Hood -As integration tests grow, you can organize them into multiple files within the `tests` directory. Each file is compiled as a separate crate. +Components provide powerful modularity to Starknet contracts. This section dives deep into the compiler internals to explain the mechanisms that enable component composability. -**Example with a common helper:** +### A Primer on Embeddable Impls -If you create `tests/common.cairo` with a `setup` function: +An impl of a Starknet interface trait (marked with `#[starknet::interface]`) can be made embeddable. Embeddable impls can be injected into any contract, adding new entry points and modifying the ABI of the contract. -```cairo -// tests/common.cairo -pub fn setup() { - println!("Setting up tests..."); +Example demonstrating embeddable impls: +```rust +#[starknet::interface] +trait SimpleTrait { + fn ret_4(self: @TContractState) -> u8; } -``` -And `tests/integration_tests.cairo` calls it: +#[starknet::embeddable] +impl SimpleImpl of SimpleTrait { + fn ret_4(self: @TContractState) -> u8 { + 4 + } +} -```cairo -// tests/integration_tests.cairo -use tests::common::setup; -use adder::it_adds_two; +#[starknet::contract] +mod simple_contract { + #[storage] + struct Storage {} -#[test] -fn internal() { - setup(); // Call helper function - assert!(it_adds_two(2, 2) == 4, "internal_adder failed"); + #[abi(embed_v0)] + impl MySimpleImpl = super::SimpleImpl; } ``` +By embedding `SimpleImpl`, we externally expose `ret4` in the contract's ABI. -Running `scarb test` would produce a section for `common.cairo` even if it contains no tests. +### Inside Components: Generic Impls -To treat the entire `tests` directory as a single crate, you can add a `tests/lib.cairo` file: +Components build upon this embedding mechanism using generic impls, as seen in the component impl block syntax: -```cairo -// tests/lib.cairo -mod common; -mod integration_tests; +```rust + #[embeddable_as(OwnableImpl)] + impl Ownable< + TContractState, +HasComponent, + > of super::IOwnable> { ``` -This structure consolidates the tests into a single crate, allowing helper functions like `setup` to be imported and used without generating a separate output section for the helper file itself. The `scarb test` output will then reflect a single `adder_tests` crate. +The key points are: +* The generic impl `Ownable` requires the implementation of the `HasComponent` trait by the underlying contract. +* This trait is automatically generated with the `component!()` macro when using a component inside a contract. +* The compiler will generate an embeddable impl that wraps any function in `Ownable`. -## Summary +--- -Cairo provides robust testing capabilities through unit and integration tests. Unit tests ensure the correctness of isolated code units, often accessing private details. Integration tests validate the interaction of multiple components using the public API, mimicking external usage. Both are essential for reliable software development, complementing Cairo's type system in preventing bugs. +Sources: -Testing Cairo Components +- https://www.starknet.io/cairo-book/ch12-06-inlining-in-cairo.html -# Testing Cairo Components +--- -Testing components differs from testing contracts. Contracts are tested against a specific state, either by deployment or by direct manipulation of `ContractState`. Components, being generic and not deployable on their own, require different testing approaches. +# Function Inlining Mechanics and Performance Tuning -## Testing the Component by Deploying a Mock Contract +Inlining replaces a function call with the body of the called function at the call site, eliminating function call overhead, which can improve performance by reducing executed instructions, though it may increase total program size. -The most straightforward method to test a component is by integrating it into a mock contract solely for testing. This allows testing the component within a contract's context and using a Dispatcher to call its entry points. +### The `inline` Attribute Hints -### Counter Component Example +In Cairo, the `inline` attribute suggests whether the Sierra code should be injected directly into the caller's context instead of using a `function_call` libfunc. -Consider a simple `CounterComponent` that allows incrementing a counter: +The variants are: +* `#[inline]`: Suggests performing an inline expansion. +* `#[inline(always)]`: Suggests an inline expansion should always be performed. +* `#[inline(never)]`: Suggests that an inline expansion should never be performed. -```cairo, noplayground -#[starknet::component] -pub mod CounterComponent { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; +Note that the attribute is only a hint and may be ignored by the compiler, although `#[inline(always)]` is rarely ignored. Annotating functions with `#[inline(always)]` reduces the total number of steps required by avoiding the step-cost associated with function calls and argument handling. - #[storage] - pub struct Storage { - value: u32, - } +### Compiler Heuristics for Default Inlining - #[embeddable_as(CounterImpl)] - pub impl Counter> of super::ICounter> { - fn get_counter(self: @ComponentState) -> u32 { - self.value.read() - } +For functions without explicit inline directives, the compiler uses a heuristic approach. The decision relies mostly on the threshold `DEFAULT_INLINE_SMALL_FUNCTIONS_THRESHOLD`. - fn increment(ref self: ComponentState) { - self.value.write(self.value.read() + 1); - } - } -} -``` +The compiler calculates a function's complexity using the `ApproxCasmInlineWeight` struct, which estimates the generated Cairo Assembly (CASM) statements. If this weight falls below the threshold, the function is inlined. The raw statement count is also considered. -### Mock Contract Definition +Special cases are handled: +* Very simple functions (e.g., returning a constant or calling another function) are always inlined. +* Functions with complex control flow (like `Match`) or those ending with `Panic` are generally not inlined. -A mock contract for testing the `CounterComponent` can be defined as follows: +### Performance Tradeoffs and Code Size -```cairo, noplayground -#[starknet::contract] -mod MockContract { - use super::counter::CounterComponent; +Inlining presents a trade-off between the number of execution steps and code length. +* **Benefit:** More frequent calls benefit more from inlining as the step count decreases. +* **Drawback:** Inlining large functions or using `#[inline]` or `#[inline(always)]` indiscriminately increases compile time and code length due to duplication of Sierra code at every call site. - component!(path: CounterComponent, storage: counter, event: CounterEvent); +Inlining is particularly useful for small functions, ideally those with many arguments. In cases where the return value of an inlined function is unused, the compiler may further optimize by omitting the inlined code entirely, reducing both code length and steps, as demonstrated in Listing 12-6. - #[storage] - struct Storage { - #[substorage(v0)] - counter: CounterComponent::Storage, - } +### Inlining Mechanics Illustrated (Sierra and Casm) - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - CounterEvent: CounterComponent::Event, - } +When a function is *not* inlined (e.g., `not_inlined`), the Sierra code uses the `function_call` libfunc. When it *is* inlined (e.g., `inlined`), its Sierra statements are injected directly into the caller's sequence of statements, using different variable IDs to avoid conflicts. - #[abi(embed_v0)] - impl CounterImpl = CounterComponent::CounterImpl; +In the Casm example (Listing 12-5), the non-inlined function involves `call rel X` and `ret` instructions to manage the function stack. The inlined function body, however, appears directly in the sequence without any `call` instruction, leading to fewer total instructions executed for that part of the logic. + +**Example Code (Listing 12-5):** +```rust +#[executable] +fn main() -> felt252 { + inlined() + not_inlined() } -``` -This mock contract embeds the component and exposes its entry points. An interface is also defined for external interaction: +#[inline(always)] +fn inlined() -> felt252 { + 1 +} -```cairo, noplayground -#[starknet::interface] -pub trait ICounter { - fn get_counter(self: @TContractState) -> u32; - fn increment(ref self: TContractState); +#[inline(never)] +fn not_inlined() -> felt252 { + 2 } ``` -Tests can then be written by deploying this mock contract and calling its entry points: - -```cairo, noplayground -use starknet::SyscallResultTrait; -use starknet::syscalls::deploy_syscall; -use super::MockContract; -use super::counter::{ICounterDispatcher, ICounterDispatcherTrait}; - -fn setup_counter() -> ICounterDispatcher { - let (address, _) = deploy_syscall( - MockContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false, - ) - .unwrap_syscall(); - ICounterDispatcher { contract_address: address } +**Example Code (Listing 12-6):** +```rust +#[executable] +fn main() { + inlined(); + not_inlined(); } -#[test] -fn test_constructor() { - let counter = setup_counter(); - assert_eq!(counter.get_counter(), 0); +#[inline(always)] +fn inlined() -> felt252 { + 'inlined' } -#[test] -fn test_increment() { - let counter = setup_counter(); - counter.increment(); - assert_eq!(counter.get_counter(), 1); +#[inline(never)] +fn not_inlined() -> felt252 { + 'not inlined' } ``` -## Testing Components Without Deploying a Contract +Use inlining cautiously where appropriate, as the compiler handles default inlining, and manual application is usually only for fine-tuning. -Components utilize genericity for reusable storage and logic. When a contract embeds a component, a `HasComponent` trait is generated, making component methods accessible. By providing a concrete `TContractState` that implements `HasComponent` to `ComponentState`, component methods can be invoked directly without deploying a mock contract. +--- -### Using Type Aliases for Testing +Sources: -Define a type alias for `ComponentState` using a concrete `ContractState` (e.g., `MockContract::ContractState`): +- https://www.starknet.io/cairo-book/ch12-10-arithmetic-circuits.html -```cairo, noplayground -type TestingState = CounterComponent::ComponentState; +--- -impl TestingStateDefault of Default { - fn default() -> TestingState { - CounterComponent::component_state_for_testing() - } -} +### Arithmetic Circuit Definition and Evaluation -#[test] -fn test_increment() { - let mut counter: TestingState = Default::default(); +Arithmetic circuits in Cairo, particularly when emulating zk-SNARKs features, involve defining elements, combining them with gates, assigning inputs, specifying a modulus, and finally evaluating the circuit. - counter.increment(); - counter.increment(); +#### Combining Circuit Elements and Gates - assert_eq!(counter.get_counter(), 2); -} -``` +Circuit elements can be combined using arithmetic operations. For instance, combining `CircuitElement` and `CircuitElement` with an addition gate results in `CircuitElement>`. Direct combination functions are available: -This `TestingState` type alias allows direct invocation of component methods. The `component_state_for_testing` function creates an instance of `TestingState` for testing. +* `circuit_add` +* `circuit_sub` +* `circuit_mul` +* `circuit_inverse` -This approach is more lightweight and allows testing internal component functions not trivially exposed externally. +Note that `CircuitElement` where $T$ is an input or gate type, represents the circuit description, while `CircuitElement<{}>` (empty struct) represents a signal or intermediate element. -## Testing Contract Internals +#### Example Circuit Definition: $a \cdot (a + b)$ -### Accessing Internal Functions with `contract_state_for_testing` +The following example demonstrates defining inputs, intermediate calculations, and the final output structure: -The `contract_state_for_testing` function allows direct interaction with a contract's `ContractState` without deployment. This is useful for testing internal functions and storage variables. +```rust +use core::circuit::{ + AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, + CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, +}; -```cairo,noplayground -use crate::pizza::PizzaFactory::{InternalTrait}; -use crate::pizza::{IPizzaFactoryDispatcher, IPizzaFactoryDispatcherTrait, PizzaFactory}; +// Circuit: a * (a + b) +// witness: a = 10, b = 20 +// expected output: 10 * (10 + 20) = 300 +fn eval_circuit() -> (u384, u384) { + let a = CircuitElement::> {}; + let b = CircuitElement::> {}; -#[test] -fn test_set_as_new_owner_direct() { - let mut state = PizzaFactory::contract_state_for_testing(); - let owner: ContractAddress = contract_address_const::<'owner'>(); - state.set_owner(owner); - assert_eq!(state.owner.read(), owner); -} -``` + let add = circuit_add(a, b); + let mul = circuit_mul(a, add); -This function creates a `ContractState` instance, enabling calls to functions that accept `ContractState` as a parameter, and direct access to storage variables after importing necessary traits. + let output = (mul,); -### Mocking Caller Address with `start_cheat_caller_address` + let mut inputs = output.new_inputs(); + inputs = inputs.next([10, 0, 0, 0]); + inputs = inputs.next([20, 0, 0, 0]); -The `start_cheat_caller_address` function allows mocking the caller's address to test access control logic. + let instance = inputs.done(); -```cairo,noplayground -# use snforge_std::{ -# ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, load, spy_events, -# start_cheat_caller_address, stop_cheat_caller_address, -# }; -# use starknet::storage::StoragePointerReadAccess; -# use starknet::{ContractAddress, contract_address_const}; -# use crate::pizza::PizzaFactory::{Event as PizzaEvents, PizzaEmission}; -# use crate::pizza::PizzaFactory::{InternalTrait}; -# use crate::pizza::{IPizzaFactoryDispatcher, IPizzaFactoryDispatcherTrait, PizzaFactory}; -# -# fn owner() -> ContractAddress { -# contract_address_const::<'owner'>() -# } -# -# fn deploy_pizza_factory() -> (IPizzaFactoryDispatcher, ContractAddress) { -# let contract = declare("PizzaFactory").unwrap().contract_class(); -# -# let owner: ContractAddress = contract_address_const::<'owner'>(); -# let constructor_calldata = array![owner.into()]; -# -# let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); -# -# let dispatcher = IPizzaFactoryDispatcher { contract_address }; -# -# (dispatcher, contract_address) -# } -# -#[test] -fn test_change_owner_should_change_owner() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); + let bn254_modulus = TryInto::<\n _, CircuitModulus,\n >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0])\n .unwrap(); - let new_owner: ContractAddress = contract_address_const::<'new_owner'>(); - assert_eq!(pizza_factory.get_owner(), owner()); + let res = instance.eval(bn254_modulus).unwrap(); - start_cheat_caller_address(pizza_factory_address, owner()); + let add_output = res.get_output(add); + let circuit_output = res.get_output(mul); - pizza_factory.change_owner(new_owner); + assert!(add_output == u384 { limb0: 30, limb1: 0, limb2: 0, limb3: 0 }, "add_output"); + assert!(circuit_output == u384 { limb0: 300, limb1: 0, limb2: 0, limb3: 0 }, "circuit_output"); - assert_eq!(pizza_factory.get_owner(), new_owner); + (add_output, circuit_output) } -#[test] -#[should_panic(expected: "Only the owner can set ownership")] -fn test_change_owner_should_panic_when_not_owner() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); - let not_owner = contract_address_const::<'not_owner'>(); - start_cheat_caller_address(pizza_factory_address, not_owner); - pizza_factory.change_owner(not_owner); - stop_cheat_caller_address(pizza_factory_address); -} -``` - -### Capturing Events with `spy_events` - -The `spy_events` function captures emitted events, allowing assertions on their parameters and verifying contract behavior, such as incrementing counters or restricting actions to the owner. - -```cairo,noplayground -# use snforge_std::{ -# ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, load, spy_events, -# start_cheat_caller_address, stop_cheat_caller_address, -# }; -# use starknet::storage::StoragePointerReadAccess; -# use starknet::{ContractAddress, contract_address_const}; -# use crate::pizza::PizzaFactory::{Event as PizzaEvents, PizzaEmission}; -# use crate::pizza::PizzaFactory::{InternalTrait}; -# use crate::pizza::{IPizzaFactoryDispatcher, IPizzaFactoryDispatcherTrait, PizzaFactory}; -# -# fn owner() -> ContractAddress { -# contract_address_const::<'owner'>() -# } -# -# fn deploy_pizza_factory() -> (IPizzaFactoryDispatcher, ContractAddress) { -# let contract = declare("PizzaFactory").unwrap().contract_class(); -# -# let owner: ContractAddress = contract_address_const::<'owner'>(); -# let constructor_calldata = array![owner.into()]; -# -# let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); -# -# let dispatcher = IPizzaFactoryDispatcher { contract_address }; -# -# (dispatcher, contract_address) -# } -# -#[test] -#[should_panic(expected: "Only the owner can make pizza")] -fn test_make_pizza_should_panic_when_not_owner() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); - let not_owner = contract_address_const::<'not_owner'>(); - start_cheat_caller_address(pizza_factory_address, not_owner); - - pizza_factory.make_pizza(); +#[executable] +fn main() { + eval_circuit(); } +``` -#[test] -fn test_make_pizza_should_increment_pizza_counter() { - // Setup - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); - start_cheat_caller_address(pizza_factory_address, owner()); - let mut spy = spy_events(); +#### Input Assignment - // When - pizza_factory.make_pizza(); +Inputs are assigned sequentially using an accumulator pattern. The `new_inputs()` call initiates this process, returning an `AddInputResult` enum: - // Then - let expected_event = PizzaEvents::PizzaEmission(PizzaEmission { counter: 1 }); - assert_eq!(pizza_factory.count_pizza(), 1); - spy.assert_emitted(@array![(pizza_factory_address, expected_event)]); +```rust +pub enum AddInputResult { + /// All inputs have been filled. + Done: CircuitData, + /// More inputs are needed to fill the circuit instance's data. + More: CircuitInputAccumulator, } ``` -Testing Smart Contracts with Frameworks - -## Testing Smart Contracts with Frameworks +Values for inputs (which are 384-bit, represented by four `u96` limbs) are assigned by calling `next()` on the accumulator until `done()` is called to obtain the full `CircuitData`. -To test smart contracts using Starknet Foundry, you first need to configure your Scarb project. This involves adding `snforge_std` as a dev dependency in your `Scarb.toml` file and setting up a script for testing. +#### Modulus Definition -### Configuring Scarb Project with Starknet Foundry +Circuits operate over a finite field modulus (up to 384-bit). A `CircuitModulus` must be defined for evaluation. The BN254 prime field modulus is specified as: -Modify your `Scarb.toml` file to include Starknet Foundry: +```rust + let bn254_modulus = TryInto::<\n _, CircuitModulus,\n >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0])\n .unwrap(); +``` -```toml,noplayground -[dev-dependencies] -snforge_std = "0.48.0" # Use the latest version +#### Circuit Evaluation -[scripts] -test = "snforge test" +The evaluation process passes the input signals through the defined gates according to the modulus specified. This is done via the `eval` method on the circuit instance: -[tool.scarb] -allow-pre-built-plugins = ["snforge_std"] +```rust + let res = instance.eval(bn254_modulus).unwrap(); ``` -This configuration ensures that running `scarb test` will execute `snforge test`. After configuring `Scarb.toml`, install Starknet Foundry following the official documentation. - -### Testing Flow with Starknet Foundry +After evaluation, the results of output gates (or any intermediate gates) can be retrieved using `get_output` on the result object, passing the corresponding `CircuitElement`: -The typical workflow for testing a contract with Starknet Foundry involves these steps: - -1. **Declare the contract class**: Identify the contract by its name. -2. **Serialize constructor calldata**: Prepare the constructor arguments. -3. **Deploy the contract**: Obtain the contract's address. -4. **Interact with the contract**: Call its entrypoints to test various scenarios. +```rust + let add_output = res.get_output(add); + let circuit_output = res.get_output(mul); +``` -The command to run these tests is `snforge test`, which is executed via `scarb test` due to the project configuration. Test outputs typically include success status and estimated gas consumption. +#### Context on Arithmetic Circuits in Cairo -Profiling and Performance Analysis +zk-SNARKs use arithmetic circuits over a finite field $F_p$, defined by constraints of the form: +\[ +(a_1 \cdot s_1 + ... + a_n \cdot s_n) \cdot (b_1 \cdot s_1 + ... + b_n \cdot s_n) + (c_1 \cdot s_1 + ... + c_n \cdot s_n) = 0 \mod p +\] +Where $s_i$ are signals and $a_i, b_i, c_i$ are coefficients. A witness is an assignment satisfying these constraints. Cairo implements these structures to allow for zk-SNARKs proof verification inside STARK proofs, although STARKs primarily use Algebraic Intermediate Representation (AIR) polynomial constraints. -### Profiling and Performance Analysis +### Circuit Verification and Proof System Comparison -To profile your Cairo code, you can use the `snforge test --build-profile` command. This command generates trace files for passing tests in the `_snfoundry_trace_` directory and corresponding output files in the `_profile_` directory. +--- -To analyze a profile, run the `go tool pprof -http=":8000" path/to/profile/output.pb.gz` command. This starts a web server at the specified port for analysis. +Sources: -Consider the following `sum_n` function and its test: +- https://www.starknet.io/cairo-book/ch12-10-procedural-macros.html -```cairo -fn sum_n(n: usize) -> usize { - let mut i = 0; - let mut sum = 0; - while i <= n { - sum += i; - i += 1; - } - sum -} +--- -#[cfg(test)] -mod tests { - use super::*; +### Macro Implementation Details - #[test] - #[available_gas(2000000)] - fn test_sum_n() { - let result = sum_n(10); - assert!(result == 55, "result is not 55"); - } -} -``` +The core code for macro implementation is written in Rust and relies on three primary Rust crates: +* `cairo_lang_macro`: Specific to macro implementation. +* `cairo_lang_parser`: Used for compiler parser functions. +* `cairo_lang_syntax`: Related to the compiler syntax. -After generating the trace and profile output, the `go tool pprof` web server provides valuable information: +Since macro functions operate at the Cairo syntax level, logic from the syntax functions created for the Cairo compiler can be reused directly. For deeper understanding of concepts like the Cairo parser or syntax, consulting the Cairo compiler workshop is recommended. -- **Function Calls:** `snforge` simulates contract calls, so a test function with multiple calls to a contract function will still register as one call in the profiler. -- **Cairo Steps:** The execution of `sum_n` uses 256 Cairo steps. +#### Example: `pow` Macro Implementation -
- pprof number of steps -
+The `pow` function example demonstrates processing input to extract the base and exponent arguments to calculate $base^{exponent}$. -The Cairo Profiler also offers insights into memory holes and builtins usage, with ongoing development for additional features. +```rust +use bigdecimal::{num_traits::pow, BigDecimal}; +use cairo_lang_macro::{inline_macro, Diagnostic, ProcMacroResult, TokenStream}; +use cairo_lang_parser::utils::SimpleParserDatabase; + +#[inline_macro] +pub fn pow(token_stream: TokenStream) -> ProcMacroResult { + let db = SimpleParserDatabase::default(); + let (parsed, _diag) = db.parse_virtual_with_diagnostics(token_stream); + + // extracting the args from the parsed input + let macro_args: Vec = parsed + .descendants(&db) + .next() + .unwrap() + .get_text(&db) + .trim_matches(|c| c == '(' || c == ')') + .split(',') + .map(|s| s.trim().to_string()) + .collect(); + + if macro_args.len() != 2 { + return ProcMacroResult::new(TokenStream::empty()).with_diagnostics( + Diagnostic::error(format!("Expected two arguments, got {:?}", macro_args)).into(), + ); + } + + // getting the value from the base arg + let base: BigDecimal = match macro_args[0].parse() { + Ok(val) => val, + Err(_) => { + return ProcMacroResult::new(TokenStream::empty()) + .with_diagnostics(Diagnostic::error("Invalid base value").into()); + } + }; -[Profiling]: https://foundry-rs.github.io/starknet-foundry/snforge-advanced-features/profiling.html -[Cairo Profiler]: https://github.com/software-mansion/cairo-profiler -[go]: https://go.dev/doc/install -[Graphviz]: https://www.graphviz.org/download/ + // getting the value from the exponent arg + let exp: usize = match macro_args[1].parse() { + Ok(val) => val, + Err(_) => { + return ProcMacroResult::new(TokenStream::empty()) + .with_diagnostics(Diagnostic::error("Invalid exponent value").into()); + } + }; -Quizzes and Summaries + // base^exp + let result: BigDecimal = pow(base, exp); -# Quizzes and Summaries + ProcMacroResult::new(TokenStream::new(result.to_string())) +} +``` -### How to Write Tests +--- -1. **What is the annotation you add to a function to indicate that it's a test?** - `#[test]` +Sources: -2. **Let's say you have a function with the type signature:** +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/ch100-01-contracts-classes-and-instances.html +- https://www.starknet.io/cairo-book/ch101-01-starknet-types.html +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html +- https://www.starknet.io/cairo-book/ch102-01-contract-class-abi.html +- https://www.starknet.io/cairo-book/appendix-02-operators-and-symbols.html +- https://www.starknet.io/cairo-book/ch101-00-building-starknet-smart-contracts.html +- https://www.starknet.io/cairo-book/ch103-00-building-advanced-starknet-smart-contracts.html +- https://www.starknet.io/cairo-book/ch103-02-02-component-dependencies.html +- https://www.starknet.io/cairo-book/ch103-03-upgradeability.html +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html - ```cairo - fn f(x: usize) -> Result; - ``` +--- - **And you want to test that `f(0)` should return `Err(_)`. Which of the following is _NOT_ a valid way to test that?** +--- - ```cairo - #[test] - #[should_err] - fn test() -> Result { - f(0) - } - ``` +Sources: - _Note: `should_err` does not exist in Cairo — tests that return `Result` will pass even if the result is an `Err`._ +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/ch100-01-contracts-classes-and-instances.html +- https://www.starknet.io/cairo-book/ch101-01-starknet-types.html +- https://www.starknet.io/cairo-book/ch103-00-building-advanced-starknet-smart-contracts.html -3. **Does the test pass?** +--- - ```cairo - fn division_operation(number1: u16, number2: u16) -> u16 { - if number2 == 0 { - panic!("ZeroDivisionError not allowed!"); - } - let result = number1 / number2; - result - } +### Introduction to Cairo and Starknet Architecture - #[cfg(test)] - mod tests { - use super::{division_operation}; +#### Smart Contract Fundamentals +Smart contracts are characterized as **permissionless** and **transparent**, allowing for **composability** where contracts interact with one another. They are restricted to accessing data only from their deployment blockchain, requiring external software called *oracles* to fetch external data (e.g., token prices). Common development standards include `ERC20` (for tokens like USDC) and `ERC721` (for NFTs like CryptoPunks). Primary use cases currently center around DeFi (decentralized finance applications like lending/DEXs) and Tokenization. - #[test] - #[should_panic(expected: ("Zerodivisionerror not allowed!",))] - fn test_division_operation() { - division_operation(10, 0); - } - } - ``` +#### Cairo and Starknet Architecture +Cairo is a language developed specifically for STARKs, enabling the writing of **provable code** to verify computation correctness between states. Starknet utilizes its own Virtual Machine (VM) instead of the EVM, which offers benefits like decreased transaction costs, native account abstraction ("Smart Accounts"), and support for emerging use cases such as **transparent AI** and fully **on-chain blockchain games**. - **No**. The expected string `"Zerodivisionerror not allowed!"` should be exactly the same as the panic string `"ZeroDivisionError not allowed!"`. +#### Cairo Programs vs. Starknet Smart Contracts +Starknet contracts are a superset of Cairo programs. A standard Cairo program requires a `main` function as its entry point. In contrast, contracts deployed on Starknet lack a `main` function but possess one or multiple entry points. For the compiler to recognize a module as a Starknet contract, it must be annotated with the `#[starknet::contract]` attribute. -4. **What is the output when these tests are run with the command `scarb cairo-test -f test_`?** +#### Contract Address and State +A contract instance maintains a **nonce**, representing the number of transactions originating from that address plus one. The **Contract Address** is a unique identifier calculated as a chain hash (using `pedersen`) of several components: +* `prefix`: ASCII encoding of the constant string `STARKNET_CONTRACT_ADDRESS`. +* `deployer_address`: Determined by the deployment transaction type (e.g., 0 for `DEPLOY_ACCOUNT`). +* `salt`: A value ensuring unique addresses and thwarting replay attacks. +* `class_hash`. +* `constructor_calldata_hash`: Array hash of constructor inputs. - ```cairo - #[cfg(test)] - mod tests { - #[test] - #[ignore] - fn test_addition() { - assert_ne!((5 + 4), 5); - } +The computation follows: +``` +contract_address = pedersen( + “STARKNET_CONTRACT_ADDRESS”, + deployer_address, + salt, + class_hash, + constructor_calldata_hash) +``` - #[test] - fn division_function() { - assert_eq!((10_u8 / 5), 2); - } +#### Starknet Types: ContractAddress +The `ContractAddress` type represents the unique address of a deployed contract on Starknet, essential for cross-contract calls and access control checks. - #[test] - fn test_multiplication() { - assert_ne!((3 * 2), 8); - assert_eq!((5 * 5), 25); - } +```rust +use starknet::{ContractAddress, get_caller_address}; - #[test] - fn test_subtraction() { - assert!((12 - 11) == 1, "The first argument was false"); - } - } - ``` +#[starknet::interface] +pub trait IAddressExample { + fn get_owner(self: @TContractState) -> ContractAddress; + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); +} - `test result: ok. 2 passed; 0 failed; 1 ignored; 1 filtered out;` - _Explanation: `test_addition` is ignored. `division_function` is filtered out because its name doesn't match the filter `test_`. `test*multiplication`and`test_subtraction` pass.* +#[starknet::contract] +mod AddressExample { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use super::{ContractAddress, get_caller_address}; -Starknet Smart Contracts + #[storage] + struct Storage { + owner: ContractAddress, + } -Introduction to Starknet and Smart Contracts + #[constructor] + fn constructor(ref self: ContractState, initial_owner: ContractAddress) { + self.owner.write(initial_owner); + } -# Introduction to Starknet and Smart Contracts + #[abi(embed_v0)] + impl AddressExampleImpl of super::IAddressExample { + fn get_owner(self: @ContractState) -> ContractAddress { + self.owner.read() + } -## What are Smart Contracts? + fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { + let caller = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can transfer"); + self.owner.write(new_owner); + } + } +} +``` -Smart contracts are programs deployed on a blockchain, consisting of storage and functions. They execute based on specific inputs and can modify or read the blockchain's storage. Each smart contract has a unique address and can hold tokens. While the term "smart contract" is a misnomer, they are fundamental to blockchain applications. +--- -### Programming Languages and Compilation +Sources: -Different blockchains use different languages for smart contracts. Ethereum primarily uses Solidity, compiled into bytecode. Starknet uses Cairo, which is compiled into Sierra and then Cairo Assembly (CASM). +- https://www.starknet.io/cairo-book/ch100-01-contracts-classes-and-instances.html +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/ch101-01-starknet-types.html +- https://www.starknet.io/cairo-book/ch103-02-02-component-dependencies.html +- https://www.starknet.io/cairo-book/ch103-03-upgradeability.html -### Characteristics of Smart Contracts +--- -- **Permissionless:** Anyone can deploy a smart contract. -- **Transparent:** Data stored by the contract is publicly accessible. -- **Composable:** Developers can write contracts that interact with other contracts. +### Contract Class Definition and Lifecycle -Smart contracts can only access on-chain data. For external data, they require oracles. Standards like `ERC20` (for tokens) and `ERC721` (for NFTs) facilitate interoperability. +Starknet distinguishes between a **contract class**, which is the definition of the contract (the code), and a **contract instance**, which is a deployed contract having its own storage and callable by transactions. -## Use Cases for Smart Contracts +#### Contract Classes and Instances -Smart contracts enable a wide range of applications: +Contract classes are identified uniquely by their **class hash**. To introduce new classes to Starknet's state, the `DECLARE` transaction is used. Once declared, instances can be deployed using the `deploy` system call, or their functionality can be used without deployment via the `library_call` system call (analogous to `delegatecall`). -### Decentralized Finance (DeFi) +#### Components of a Cairo Class Definition -Enables financial applications like lending/borrowing, decentralized exchanges (DEXs), stablecoins, and more, without traditional intermediaries. +A contract class is defined by several components whose hashes contribute to the final class hash: -### Tokenization +| Name | Notes | +| :--- | :--- | +| Contract class version | Currently supported version is 0.1.0. | +| Array of external functions entry points | Defined by `(_selector_, _function_idx_)`. Selector is `starknet_keccak` hash of the function name. | +| Array of L1 handlers entry points | - | +| Array of constructors entry points | The compiler currently allows only one constructor. | +| ABI | A string supplied by the user declaring the class. The ABI hash is `starknet_keccak(bytes(ABI, "UTF-8"))`. | +| Sierra program | An array of field elements representing the Sierra instructions. | -Facilitates the creation and trading of digital tokens representing real-world assets (e.g., real estate, art), enabling fractional ownership and increased liquidity. +The **class hash** is computed using the Poseidon hash function ($h$): +$$ +\text{class\_hash} = h(\text{contract\_class\_version}, \text{external\_entry\_points}, \text{l1\_handler\_entry\_points}, \text{constructor\_entry\_points}, \text{abi\_hash}, \text{sierra\_program\_hash}) +$$ +The hash of an entry point array is $h(\text{selector}_1,\text{index}_1,...,\text{selector}_n,\text{index}_n)$. The version hash uses the ASCII encoding of the string `CONTRACT_V0.1.0`. -### Voting +#### Contract Interfaces -Creates secure, transparent, and immutable voting systems where votes are tallied automatically on the blockchain. +Interfaces define the blueprint of a contract, specifying exposed functions without bodies. They are defined by annotating a trait with `#[starknet::interface]`. All functions in the trait are public. -### Royalties +The `self` parameter in function signatures determines state access: +* `ref self: TContractState`: Allows modification of the contract's storage variables. +* `self: @TContractState`: Takes a snapshot, preventing state modification (the compiler enforces this). -Automates the distribution of royalties to content creators based on consumption or sales. +The implementation must conform to the interface, or a compilation error results. -### Decentralized Identities (DIDs) +#### Constructors and Public Functions -Allows individuals to manage their digital identities securely and control the sharing of personal information. +1. **Constructors**: Run once upon deployment to initialize state. + * Must be named `constructor` and annotated with `#[constructor]`. + * Must take `self` as the first argument, usually with the `ref` keyword to allow state modification. + * A contract can have only one constructor. -## The Rise of Starknet and Cairo +2. **Public Functions**: Accessible from outside the contract. + * Defined inside an implementation block annotated with `#[abi(embed_v0)]`, which embeds the functions as implementations of the Starknet interface and potential entry points. + * Alternatively, they can be defined independently using the `#[external(v0)]` attribute. -Ethereum's success led to scalability issues (high transaction costs). Layer 2 (L2) solutions aim to address this. Starknet is a validity rollup L2 that uses STARKs for cryptographic proofs of computation correctness, offering significant scalability potential. +#### Upgradeability -### Cairo and Starknet's VM +Starknet supports native upgradeability by replacing the contract's class hash via the `replace_class_syscall`. This syscall updates the contract source code. -Cairo is a language designed for STARKs, enabling "provable code" for Starknet. Starknet utilizes its own Virtual Machine (VM), distinct from the EVM, offering greater flexibility. This, combined with native account abstraction, enables advanced features like "Smart Accounts" and new use cases such as transparent AI and fully on-chain blockchain games. +To implement this, an entry point must be exposed that executes `replace_class_syscall` with the new class hash. The `ClassHash` type represents the hash of a contract class. -## Cairo Programs vs. Starknet Smart Contracts +```rust +use core::num::traits::Zero; +use starknet::{ClassHash, syscalls}; -Starknet contracts are a superset of Cairo programs. While Cairo programs have a `main` function as an entry point, Starknet contracts have one or more functions that serve as entry points and have access to Starknet's state. +fn upgrade(new_class_hash: ClassHash) { + assert!(!new_class_hash.is_zero(), "Class hash cannot be zero"); + syscalls::replace_class_syscall(new_class_hash).unwrap(); +} +``` -### Smart Wallets and Account Abstraction +--- -Interacting with Starknet often requires tools like Starkli. Account Abstraction allows for more complex account logic, referred to as "Smart Wallets", which are essential for functionalities like voting contracts. Preparing and funding these accounts is a prerequisite for interaction. +Sources: -Starknet Contract Fundamentals +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/ch101-00-building-starknet-smart-contracts.html +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html -# Starknet Contract Fundamentals +--- -## Defining a Starknet Contract +### Core Implementation Patterns (Traits, Functions, and State) -Starknet contracts are defined within modules annotated with the `#[starknet::contract]` attribute. Other related attributes include `#[starknet::interface]`, `#[starknet::component]`, `#[starknet::embeddable]`, `#[embeddable_as(...)]`, `#[storage]`, `#[event]`, and `#[constructor]`. +Starknet contracts encapsulate state and logic within a module annotated with `#[starknet::contract]`. The contract state is defined within a struct annotated with `#[storage]`, which is always initialized empty. Logic is defined by functions interacting with this state. -## Anatomy of a Simple Contract +#### Simple Contract Structure Example -A contract encapsulates state and logic within a module marked with `#[starknet::contract]`. The state is defined in a `Storage` struct, and logic is implemented in functions that interact with this state. +A basic contract defines storage and implements functions via an interface: -```cairo,noplayground +```rust #[starknet::interface] trait ISimpleStorage { fn set(ref self: TContractState, x: u128); @@ -7007,81 +5973,127 @@ mod SimpleStorage { } ``` -### The Interface: The Contract's Blueprint +#### Interfaces and Implementation Blocks + +Contracts use interfaces, defined with `#[starknet::interface]`, as blueprints for their public functions. Logic is provided in implementation blocks (`impl`). + +#### Function Visibility and Self Parameter + +Functions can be classified based on their visibility and how they handle the contract state (`ContractState`): + +##### External Functions (State Modifying) +External functions are public functions exposed to the outside world that can mutate state. They must take `self` as the first argument, passed by reference using the `ref` keyword, granting read and write access to storage variables. + +##### View Functions (Read-Only by Convention) +View functions are public, typically read-only functions. They must take `self` as the first argument, passed as a snapshot using the `@` modifier. This restricts storage write access via `self` at compile time. + +##### State Mutability Warning +The read-only property of view functions is enforced only by the compiler. Starknet does not enforce this limitation; a transaction targeting a view function *can* change the state via direct system calls or calls to other contracts. Developers must not assume view functions are side-effect free. -Interfaces, defined using `#[starknet::interface]` on a trait, specify the functions a contract exposes. They use a generic `TContractState` for the contract's state. Functions with `ref self` can modify state (external), while those with `@self` are read-only (view). +##### Other Function Types -```cairo,noplayground +* **Standalone Public Functions**: Public functions defined outside an implementation block using the `#[external(v0)]` attribute. They must take `self` as the first parameter. +* **Private (Internal) Functions**: Functions not exposed externally. They can only be called from within the contract. They can be grouped using `#[generate_trait]` or defined as free functions within the module. Private functions do not require `self` as the first argument. + +The `NameRegistry` contract demonstrates these patterns, including using `Map` storage types, constructor logic, public functions within an `impl` block, a standalone public function (`get_contract_name`), and internal functions grouped via `#[generate_trait]`. + +```rust #[starknet::interface] -trait ISimpleStorage { - fn set(ref self: TContractState, x: u128); - fn get(self: @TContractState) -> u128; +pub trait INameRegistry { + fn store_name(ref self: TContractState, name: felt252); + fn get_name(self: @TContractState, address: ContractAddress) -> felt252; } -``` -## Public Functions +#[starknet::contract] +mod NameRegistry { + use starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + }; + use starknet::{ContractAddress, get_caller_address}; + + #[storage] + struct Storage { + names: Map, + total_names: u128, + } -Public functions are exposed externally. They are defined within an `impl` block annotated with `#[abi(embed_v0)]` or as standalone functions with `#[external(v0)]`. + #[derive(Drop, Serde, starknet::Store)] + pub struct Person { + address: ContractAddress, + name: felt252, + } -- **External Functions**: Use `ref self: ContractState`, allowing state modification. -- **View Functions**: Use `@self: ContractState`, restricting state modification through `self` at compile time. + #[constructor] + fn constructor(ref self: ContractState, owner: Person) { + self.names.entry(owner.address).write(owner.name); + self.total_names.write(1); + } -```cairo,noplayground + // Public functions inside an impl block #[abi(embed_v0)] - impl SimpleStorage of super::ISimpleStorage { - fn set(ref self: ContractState, x: u128) { - self.stored_data.write(x); + impl NameRegistry of super::INameRegistry { + fn store_name(ref self: ContractState, name: felt252) { + let caller = get_caller_address(); + self._store_name(caller, name); } - fn get(self: @ContractState) -> u128 { - self.stored_data.read() + fn get_name(self: @ContractState, address: ContractAddress) -> felt252 { + self.names.entry(address).read() } } -``` - -**Note:** While the compiler enforces some restrictions for view functions, Starknet itself does not guarantee immutability for view functions called within a transaction. Always be cautious. - -## Private Functions -Functions not marked as `#[external(v0)]` or within an `#[abi(embed_v0)]` block are private (internal). They can only be called from within the contract. They can be grouped in a `#[generate_trait]` impl block or defined as free functions. + // Standalone public function + #[external(v0)] + fn get_contract_name(self: @ContractState) -> felt252 { + 'Name Registry' + } -```cairo,noplayground + // Could be a group of functions about a same topic #[generate_trait] impl InternalFunctions of InternalFunctionsTrait { fn _store_name(ref self: ContractState, user: ContractAddress, name: felt252) { - // ... implementation ... + let total_names = self.total_names.read(); + + self.names.entry(user).write(name); + + self.total_names.write(total_names + 1); } } + + // Free function (Private/Internal) + fn get_total_names_storage_address(self: @ContractState) -> felt252 { + self.total_names.__base_address__ + } +} ``` -## Constructors +--- -Constructors, marked with `#[constructor]`, run only once during contract deployment to initialize the contract's state. A contract can have only one constructor. +Sources: -```cairo,noplayground - #[constructor] - fn constructor(ref self: ContractState, owner: Person) { - self.names.entry(owner.address).write(owner.name); - self.total_names.write(1); - } -``` +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html +- https://www.starknet.io/cairo-book/ch102-01-contract-class-abi.html +- https://www.starknet.io/cairo-book/appendix-02-operators-and-symbols.html +- https://www.starknet.io/cairo-book/ch101-01-starknet-types.html +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html -## Contract Storage +--- -Contract storage is a persistent map of `2^251` slots, each holding a `felt252`. Storage addresses are computed based on variable names and types. Common storage types include `Map` and `Vec`. +# Advanced Contract Elements (Events, ABI, and System Interaction) -- **Accessing State**: Use `.read()` to retrieve a value and `.write()` to store or update a value. +### Contract Events -```cairo,noplayground - self.stored_data.read() - self.stored_data.write(x); -``` +Events inform the outside world of changes during execution and are stored in transaction receipts. -## Contract Events +#### Defining Events +Events are defined in an enum annotated with `#[event]`, which must be named `Event`. Each variant represents an event, and its data structure must derive `starknet::Event`. -Events inform the outside world about contract changes. They are defined in an enum annotated with `#[event]` and emitted using `self.emit()`. +```rust +#[starknet::contract] +mod EventExample { + #[storage] + struct Storage {} -```cairo,noplayground #[event] #[derive(Drop, starknet::Event)] pub enum Event { @@ -7090,1169 +6102,1318 @@ Events inform the outside world about contract changes. They are defined in an e FieldUpdated: FieldUpdated, BookRemoved: BookRemoved, } + // ... struct/enum definitions for BookAdded, FieldUpdated, etc. + + #[abi(embed_v0)] + impl EventExampleImpl of super::IEventExample { + fn add_book(ref self: ContractState, id: u32, title: felt252, author: felt252) { + // ... logic to add a book in the contract storage ... + self.emit(BookAdded { id, title, author }); + } + // ... other functions + } +} ``` -## Starknet Types +#### Event Data Fields and Keys +Each event data field can be annotated with `#[key]`. Key fields are stored separately for efficient filtering by external tools. The variant name is used internally as the first event key. + +#### Flattening Nested Events with `#[flat]` +The `#[flat]` attribute can be used on an enum variant to flatten serialization. If `FieldUpdated` is annotated with `#[flat]`, emitting `FieldUpdated::Title(...)` results in the event name being `Title` instead of `FieldUpdated`. + +```rust +// Example of #[flat] usage +#[derive(Drop, starknet::Event)] +pub enum FieldUpdated { + Title: UpdatedTitleData, + Author: UpdatedAuthorData, +} +``` -Starknet provides specialized types for blockchain interactions: +#### Emitting Events +Events are emitted by calling `self.emit()` with an event data structure. -- `ContractAddress`: Represents a deployed contract's address. -- `StorageAddress`: Represents a location within a contract's storage. -- `EthAddress`: Represents a 20-byte Ethereum address for cross-chain applications. +```rust +self.emit(BookAdded { id, title, author }); +``` -## Contract Classes and Instances +#### Contract Attributes Related to Events +| Attribute | Defines | +| :--- | :--- | +| `#[event]` | Defines an event in a smart contract | +| `#[key]` | Defines an indexed `Event` enum field | +| `#[flat]` | Defines an enum variant of the `Event` enum that is not nested, ignoring the variant name in serialization | -- **Contract Class**: The definition of a contract's code. -- **Contract Instance**: A deployed contract with its own storage. +### Contract ABI and Entrypoints -## Contract Address Computation +The Contract Class ABI is the high-level specification describing callable functions, parameters, and return values, used for external interaction (via JSON representation) or contract-to-contract calls (via dispatcher pattern). -A contract address is computed using a hash of prefix, deployer address, salt, class hash, and constructor calldata hash. +#### Entrypoints +Entrypoints are functions callable from outside the contract class: +1. **Public functions**: Most common, exposed as `view` or `external`. +2. **Constructor**: Called only once during deployment. +3. **L1-Handlers**: Triggered by the sequencer after receiving a message from L1. -## Class Hash Computation +#### Function Selector +Entrypoints are identified by a *selector*, computed as `sn_keccak(function_name)`. -A class hash is the chain hash of its components: version, entry points, ABI hash, and Sierra program hash. +#### Encoding +All data must be serialized into `felt252` before execution at the CASM level, as specified by the ABI. -Starknet System Calls +#### ABI Definition Attributes +| Attribute | Explanation | +| :--- | :--- | +| `#[abi(embed_v0)]` | Defines an implementation of a trait, exposing its functions as contract entrypoints. | +| `#[abi(per_item)]` | Allows individual definition of the entrypoint type of functions inside an impl. | +| `#[external(v0)]` | Defines an external function when `#[abi(per_item)]` is used. | +| `#[generate_trait]` | Generates a trait definition for the implementation block, often used for private impls or with `#[abi(per_item)]`. | -# Starknet System Calls +When using `#[abi(per_item)]`, public functions must be annotated with `#[external(v0)]` to be exposed; otherwise, they are considered private. -System calls enable a contract to request services from the Starknet OS, providing access to broader Starknet state beyond local variables. The following system calls are available in Cairo 1.0: +```rust +#[starknet::contract] +mod ContractExample { + #[storage] + struct Storage {} -- `get_block_hash` -- `get_execution_info` -- `call_contract` -- `deploy` -- `emit_event` -- `library_call` -- `send_message_to_L1` -- `get_class_hash_at` -- `replace_class` -- `storage_read` -- `storage_write` -- `keccak` -- `sha256_process_block` + #[abi(per_item)] + #[generate_trait] + impl SomeImpl of SomeTrait { + #[constructor] + // this is a constructor function + fn constructor(ref self: ContractState) {} -## `get_block_hash` + #[external(v0)] + // this is a public function + fn external_function(ref self: ContractState, arg1: felt252) {} -### Syntax + #[l1_handler] + // this is a l1_handler function + fn handle_message(ref self: ContractState, from_address: felt252, arg: felt252) {} -```cairo,noplayground -pub extern fn get_block_hash_syscall( - block_number: u64, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; + // this is an internal function + fn internal_function(self: @ContractState) {} + } +} ``` -### Description +### System Interaction and Context Access + +#### Class Hashes +Class hashes have the same value range as addresses `[0, 2^251)`. They uniquely identify a specific version of contract code and are used in deployment, proxy patterns, and upgrades. -Retrieves the hash of a specific Starknet block within the range of `[first_v0_12_0_block, current_block - 10]`. +#### Working with Block and Transaction Information +Starknet provides functions to access the current execution context: -### Return Values +```rust +#[starknet::interface] +pub trait IBlockInfo { + fn get_block_info(self: @TContractState) -> (u64, u64); + fn get_tx_info(self: @TContractState) -> (ContractAddress, felt252); +} -Returns the hash of the specified block. +#[starknet::contract] +mod BlockInfoExample { + use starknet::{get_block_info, get_tx_info}; + use super::ContractAddress; -### Error Messages + #[storage] + struct Storage {} -- `Block number out of range`: `block_number` is greater than `current_block - 10`. -- `0`: `block_number` is less than the first block number of v0.12.0. + #[abi(embed_v0)] + impl BlockInfoImpl of super::IBlockInfo { + fn get_block_info(self: @ContractState) -> (u64, u64) { + let block_info = get_block_info(); + (block_info.block_number, block_info.block_timestamp) + } -## `get_execution_info` + fn get_tx_info(self: @ContractState) -> (ContractAddress, felt252) { + let tx_info = get_tx_info(); -### Syntax + // Access transaction details + let sender = tx_info.account_contract_address; + let tx_hash = tx_info.transaction_hash; -```cairo,noplayground -pub extern fn get_execution_info_syscall() -> SyscallResult< - Box, -> implicits(GasBuiltin, System) nopanic; + (sender, tx_hash) + } + } +} ``` +`get_block_info()` returns `BlockInfo` (block number, timestamp), and `get_tx_info()` returns `TxInfo` (sender address, transaction hash, fee details). + +--- -### Description +Sources: -Fetches information about the original transaction. In Cairo 1.0, all block, transaction, and execution context getters are consolidated into this single system call. +- https://www.starknet.io/cairo-book/ch101-01-00-contract-storage.html +- https://www.starknet.io/cairo-book/ch101-01-02-storage-vecs.html +- https://www.starknet.io/cairo-book/ch101-01-01-storage-mappings.html +- https://www.starknet.io/cairo-book/ch103-01-optimizing-storage-costs.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/appendix-03-derivable-traits.html +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html -### Arguments +--- -None. +--- -### Return Values +Sources: -Returns a struct containing the execution info. +- https://www.starknet.io/cairo-book/ch101-01-00-contract-storage.html +- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html -## `call_contract` +--- -### Syntax +## Defining and Interacting with Contract Storage -```cairo,noplayground -pub extern fn call_contract_syscall( - address: ContractAddress, entry_point_selector: felt252, calldata: Span, -) -> SyscallResult> implicits(GasBuiltin, System) nopanic; +Contract storage is a persistent space on the blockchain, structured as a map with $2^{251}$ slots, where each slot is a `felt252` initialized to 0. Each slot is identified by a storage address, which is a `felt252` computed from the variable's name and type parameters. + +### Declaring Storage Variables + +Storage variables must be declared within a special struct named `Storage`, which requires the `#[storage]` attribute. This attribute instructs the compiler to generate the necessary code for state interaction. This struct can hold types implementing the `Store` trait, including structs, enums, Mappings, and Vectors. + +```rust +#[storage] +struct Storage { + owner: Person, + expiration: Expiration, +} ``` -### Description +### Accessing and Modifying State + +Interaction with contract storage is primarily done through high-level storage variables using two methods: -Calls a specified contract with the given address, entry point selector, and call arguments. +1. **`read()`**: Returns the value of a storage variable. It takes no arguments and is called on the variable itself. + ```rust + self.stored_data.read() + ``` +2. **`write(value)`**: Writes a new value to the storage slot. For simple variables, it takes one argument (the value). For mappings, it may take more arguments (key and value). + ```rust + self.stored_data.write(x); + ``` -_Note: Internal calls cannot return `Err(_)`as this is not handled by the sequencer or Starknet OS. Failure of`call*contract_syscall` results in the entire transaction being reverted.* +When accessing fields within a storage struct (e.g., `self.owner.name.read()`), the compiler transparently translates these accesses into underlying `StoragePointer` manipulations. -### Arguments +### Interacting via Contract Interface Implementation -- `address`: The address of the contract to call. -- `entry_point_selector`: The selector for a function within the contract, computable with the `selector!` macro. -- `calldata`: The calldata array. +When implementing a contract interface defined by a trait, the public functions must be defined in an implementation block. This block must use the `#[abi(embed_v0)]` attribute to expose its functions externally; otherwise, they will not be callable. -### Return Values +The `self` parameter in the trait methods **must** be of type `ContractState`, which is generated by the compiler and grants access to storage variables and allows event emission. -The call response, of type `SyscallResult>`. +* If `self` is passed by reference (`ref self: ContractState`), state modification is allowed. +* If `self` is passed as a snapshot (`self: @ContractState`), only read access is permitted, and attempts to modify state will result in a compilation error. -## `deploy` +For example, in the implementation of `ISimpleStorage`: -### Syntax +```rust +#[abi(embed_v0)] +impl SimpleCounterImpl of super::ISimpleStorage { + fn get_owner(self: @ContractState) -> Person { + self.owner.read() + } -```cairo,noplayground -pub extern fn deploy_syscall( - class_hash: ClassHash, - contract_address_salt: felt252, - calldata: Span, - deploy_from_zero: bool, -) -> SyscallResult<(ContractAddress, Span)> implicits(GasBuiltin, System) nopanic; + fn change_expiration(ref self: ContractState, expiration: Expiration) { + if get_caller_address() != self.owner.address.read() { + panic!("Only the owner can change the expiration"); + } + self.expiration.write(expiration); + } +} ``` -### Description +### View Functions -Deploys a new instance of a previously declared class. +View functions are public functions where `self: ContractState` is passed as a snapshot (`@ContractState`). This configuration restricts state modification through `self` and marks the function's `state_mutability` as `view`. -### Arguments +--- -- `class_hash`: The class hash of the contract to deploy. -- `contract_address_salt`: An arbitrary value used in the computation of the contract's address. -- `calldata`: The constructor's calldata. -- `deploy_from_zero`: A flag for contract address computation. If not set, the caller's address is used; otherwise, 0 is used. +Sources: -### Return Values +- https://www.starknet.io/cairo-book/ch101-01-00-contract-storage.html +- https://www.starknet.io/cairo-book/appendix-03-derivable-traits.html +- https://www.starknet.io/cairo-book/ch103-01-optimizing-storage-costs.html -A tuple containing the deployed contract's address and the constructor's response array. +--- -## `emit_event` +## Storing Custom Types with the `Store` Trait -### Syntax +The `Store` trait, defined in the `starknet::storage_access` module, specifies how a type should be stored in storage. For a type to be stored in storage, it **must** implement the `Store` trait. -```cairo,noplayground -pub extern fn emit_event_syscall( - keys: Span, data: Span, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` +Most core library types implement `Store`. However, custom structs or enums require explicit implementation. This can be achieved by adding `#[derive(starknet::Store)]` on top of the definition, provided all members/variants also implement `Store`. -### Description +### Structs Storage Layout + +On Starknet, structs are stored in storage as a sequence of primitive types, stored contiguously in the order they are defined. The first element is stored at the struct's base address (accessible via `var.__base_address__`). -Emits an event with specified keys and data. Keys are analogous to Ethereum event topics, and data contains the event payload. +For example, for a `Person` struct with `name` and `address`: -### Arguments +| Fields | Address | +| --- | --- | +| name | `owner.__base_address__` | +| address | `owner.__base_address__ + 1` | -- `keys`: The event's keys. -- `data`: The event's data. +### Enums Storage Layout -### Return Values +When storing an enum variant, you store the variant's index (starting at 0) and any associated values. The index is stored at the base address, and associated values follow contiguously. -None. +For an enum `Expiration` with `Finite: u64` (index 0) and `Infinite` (index 1): -### Example +If `Finite` is stored: -```cairo,noplayground -let keys = ArrayTrait::new(); -keys.append('key'); -keys.append('deposit'); -let values = ArrayTrait::new(); -values.append(1); -values.append(2); -values.append(3); -emit_event_syscall(keys, values).unwrap_syscall(); -``` +| Element | Address | +| --- | --- | +| Variant index (0 for Finite) | `expiration.__base_address__` | +| Associated limit date | `expiration.__base_address__ + 1` | -## `library_call` +If `Infinite` is stored: -### Syntax +| Element | Address | +| --- | --- | +| Variant index (1 for Infinite) | `expiration.__base_address__` | + +Enums used in contract storage **must** define a default variant (using `#[default]`), which is returned when reading an uninitialized storage slot, preventing runtime errors. + +Both `Drop` and `Serde` derivations are required for properly serializing arguments passed to entrypoints and deserializing their outputs. + +### Accessing Members of Stored Custom Types + +When working with compound types stored in storage, you can call `read` and `write` on specific members instead of the struct variable itself, minimizing storage operations. For instance, for a stored struct `owner`, you can access its member `name` via `self.owner.name.read()`. + +### Starknet Storage with `starknet::Store` + +The `starknet::Store` trait is relevant when building on Starknet. It automatically implements the necessary read and write functions for a type to be used in smart contract storage. + +### Storage Nodes + +A storage node is a special kind of struct that can contain storage-specific types like `Map` or `Vec`. They can only exist within contract storage and are defined with the `#[starknet::storage_node]` attribute. They help logically group related data and allow for sophisticated storage layouts. + +When accessing a storage node, you cannot `read` or `write` it directly; you must access its individual members. + +Example of defining and using a storage node: -```cairo,noplayground -pub extern fn library_call_syscall( - class_hash: ClassHash, function_selector: felt252, calldata: Span, -) -> SyscallResult> implicits(GasBuiltin, System) nopanic; ``` +#[starknet::contract] +mod VotingSystem { + use starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + }; + use starknet::{ContractAddress, get_caller_address}; + + #[storage] + struct Storage { + proposals: Map, + proposal_count: u32, + } + + #[starknet::storage_node] + struct ProposalNode { + title: felt252, + description: felt252, + yes_votes: u32, + no_votes: u32, + voters: Map, + } -### Description + #[external(v0)] + fn create_proposal(ref self: ContractState, title: felt252, description: felt252) -> u32 { + let mut proposal_count = self.proposal_count.read(); + let new_proposal_id = proposal_count + 1; -Executes the logic of another class within the context of the caller. Requires the class hash, function selector, and serialized calldata. + let mut proposal = self.proposals.entry(new_proposal_id); + proposal.title.write(title); + proposal.description.write(description); + proposal.yes_votes.write(0); + proposal.no_votes.write(0); -## `get_class_hash_at` + self.proposal_count.write(new_proposal_id); -### Syntax + new_proposal_id + } -```cairo,noplayground -pub extern fn get_class_hash_at_syscall( - contract_address: ContractAddress, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; + #[external(v0)] + fn vote(ref self: ContractState, proposal_id: u32, vote: bool) { + let mut proposal = self.proposals.entry(proposal_id); + let caller = get_caller_address(); + let has_voted = proposal.voters.entry(caller).read(); + if has_voted { + return; + } + proposal.voters.entry(caller).write(true); + } +} ``` -### Description +--- -Retrieves the class hash of the contract at the given address. +Sources: -### Arguments +- https://www.starknet.io/cairo-book/ch101-01-00-contract-storage.html -- `contract_address`: The address of the deployed contract. +--- -### Return Values +### Storage Layout and Address Computation -The class hash of the contract's originating code. +The calculation mechanism for storage addresses is modeled using `StoragePointers` and `StoragePaths`. -## `replace_class` +#### Modeling of the Contract Storage in the Core Library -### Syntax +Storage variables in Cairo are not stored contiguously. To manage retrieval, the core library models the contract storage using a system of `StoragePointers` and `StoragePaths`. -```cairo,noplayground -pub extern fn replace_class_syscall( - class_hash: ClassHash, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` +Each storage variable can be converted into a `StoragePointer`. This pointer comprises two primary fields: +* The base address of the storage variable in the contract's storage. +* The offset, relative to the base address, of the specific storage slot being pointed to. + +#### Example of Storage Access + +The following example demonstrates a contract structure and how storage addresses can be accessed programmatically: + +```rust +#[starknet::interface] +pub trait ISimpleStorage { + fn get_owner(self: @TContractState) -> SimpleStorage::Person; + fn get_owner_name(self: @TContractState) -> felt252; + fn get_expiration(self: @TContractState) -> SimpleStorage::Expiration; + fn change_expiration(ref self: TContractState, expiration: SimpleStorage::Expiration); +} + +#[starknet::contract] +mod SimpleStorage { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + + #[storage] + struct Storage { + owner: Person, + expiration: Expiration, + } + + #[derive(Drop, Serde, starknet::Store)] + pub struct Person { + address: ContractAddress, + name: felt252, + } -### Description + #[derive(Copy, Drop, Serde, starknet::Store)] + pub enum Expiration { + Finite: u64, + #[default] + Infinite, + } + + #[constructor] + fn constructor(ref self: ContractState, owner: Person) { + self.owner.write(owner); + } -Replaces the class of the calling contract with the class specified by `class_hash`. The replacement takes effect from the next transaction onwards or subsequent calls within the same transaction after the replacement. + #[abi(embed_v0)] + impl SimpleCounterImpl of super::ISimpleStorage { + fn get_owner(self: @ContractState) -> Person { + self.owner.read() + } -### Arguments + fn get_owner_name(self: @ContractState) -> felt252 { + self.owner.name.read() + } -- `class_hash`: The hash of the class to use as a replacement. + fn get_expiration(self: @ContractState) -> Expiration { + self.expiration.read() + } -### Return Values + fn change_expiration(ref self: ContractState, expiration: Expiration) { + if get_caller_address() != self.owner.address.read() { + panic!("Only the owner can change the expiration"); + } + self.expiration.write(expiration); + } + } -None. + fn get_owner_storage_address(self: @ContractState) -> felt252 { + self.owner.__base_address__ + } -## `storage_read` + fn get_owner_name_storage_address(self: @ContractState) -> felt252 { + self.owner.name.__storage_pointer_address__.into() + } -### Syntax +} -```cairo,noplayground -pub extern fn storage_read_syscall( - address_domain: u32, address: StorageAddress, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; +#[cfg(test)] +mod tests; ``` -### Description +--- -Reads a value from the contract's storage at the specified address domain and storage address. +Sources: -### Return Values +- https://www.starknet.io/cairo-book/ch101-01-01-storage-mappings.html +- https://www.starknet.io/cairo-book/ch101-01-00-contract-storage.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html -The value read from storage as a `felt252`. +--- -Contract Storage Management +### Persistent Key-Value Storage: Mappings -# Contract Storage Management +#### Storage Mappings Fundamentals -Starknet contracts manage their state through contract storage, which can be accessed in two primary ways: +Storage mappings provide a way to associate keys with values persistently in contract storage. They do not store the key data itself; instead, they use the hash of the key to compute the storage slot address for the corresponding value. Consequently, it is impossible to iterate over the keys of a storage mapping. -1. **High-level storage variables**: Declared in a `Storage` struct annotated with `#[storage]`. This is the recommended approach for structured data. -2. **Low-level system calls**: Using `storage_read_syscall` and `storage_write_syscall` for direct access to any storage key. +Mappings have no concept of length, and all values default to the zero value for their type (e.g., `0` for `u64`). The only way to remove an entry is to set its value to this default. -## Declaring and Using Storage Variables +The `Map` type from `core::starknet::storage` must be used for persistent storage. The `Felt252Dict` type is a **memory** type and cannot be stored directly in contract storage. To manipulate `Map` contents in memory, elements must be copied to/from a `Felt252Dict` or similar structure. `Map` can only be used as a storage variable within a contract's storage struct. -Storage variables are declared within a `struct` annotated with `#[storage]`. This attribute enables the compiler to generate code for interacting with blockchain state. Any type implementing the `Store` trait can be used. +#### Declaring and Using Storage Mappings -```cairo +Mappings are declared in the `#[storage]` struct: + +```rust #[storage] struct Storage { - owner: Person, - expiration: Expiration, + user_values: Map, +} +``` + +To read a value, retrieve the storage pointer using `entry(key)` and then call `read()`: + +```rust +fn get(self: @TState, address: ContractAddress) -> u64 { + self.user_values.entry(address).read() +} +``` + +To write a value, retrieve the storage pointer and call `write(value)`: + +```rust +fn set(ref self: ContractState, amount: u64) { + let caller = get_caller_address(); + self.user_values.entry(caller).write(amount); +} +``` + +Mappings can also be members of storage nodes, such as tracking voters in a proposal structure: + +```rust +#[starknet::storage_node] +struct ProposalNode { + // ... other fields + voters: Map, +} +``` + +#### Nested Mappings + +Mappings can contain other mappings, allowing for complex key structures, such as mapping a user address to their warehouse inventory: `Map>`. + +Accessing nested mappings requires traversing the keys sequentially using chained `entry()` calls: + +```rust +#[storage] +struct Storage { + user_warehouse: Map>, +} + +// Writing to a nested map +fn set_quantity(ref self: ContractState, item_id: u64, quantity: u64) { + let caller = get_caller_address(); + self.user_warehouse.entry(caller).entry(item_id).write(quantity); +} + +// Reading from a nested map +fn get_item_quantity(self: @ContractState, address: ContractAddress, item_id: u64) -> u64 { + self.user_warehouse.entry(address).entry(item_id).read() } ``` -### Accessing Storage Variables - -Automatically generated `read` and `write` functions are available for each storage variable. For compound types like structs, you can access individual members directly. +#### Storage Address Computation -To read a variable: +The storage address for a value associated with key(s) in a mapping is computed based on the mapping variable's name and the key(s): -```cairo -let owner_data = self.owner.read(); -``` +* **Single Key ($k$):** The address is calculated as $h(\text{sn\_keccak}(\text{variable\_name}), k)$, where $h$ is the Pedersen hash. The result is taken modulo $2^{251} - 256$. +* **Multiple Keys ($k_1, k_2, \dots, k_n$):** The address is computed iteratively: $h(\dots h(h(\text{sn\_keccak}(\text{variable\_name}), k_1), k_2), \dots, k_n)$. +* If a key is a struct, each element of the struct is treated as a sequential key, provided the struct implements the `Hash` trait. -To write a variable: +The base address of the storage variable can be accessed via the `__base_address__` attribute. -```cairo -self.owner.write(new_owner_data); -``` +--- -When working with struct members, you can read/write them directly: +Sources: -```cairo -// Reading a specific member of a struct -fn get_owner_name(self: @ContractState) -> felt252 { - self.owner.name.read() -} -``` +- https://www.starknet.io/cairo-book/ch101-01-02-storage-vecs.html -## Storing Custom Types with the `Store` Trait +--- -To store custom types (structs, enums) in storage, they must implement the `Store` trait. This can be achieved by deriving it: +# Dynamic Collections: Storage Vectors -```cairo -#[derive(Drop, Serde, starknet::Store)] -pub struct Person { - address: ContractAddress, - name: felt252, -} -``` +## Storing Collections with Vectors -Enums used in storage must also implement `Store` and define a default variant using `#[default]`: +The `Vec` type, provided by `starknet::storage`, allows storing collections of values in contract storage. It is a phantom type designed for storage and cannot be instantiated as a regular variable, used as a function parameter, or included in regular structs. To work with its contents, elements must be copied to and from a memory `Array`. -```cairo -#[derive(Copy, Drop, Serde, starknet::Store)] -pub enum Expiration { - Finite: u64, - #[default] - Infinite, -} -``` +To use `Vec` operations, import `Vec`, `VecTrait`, `MutableVecTrait`, `StoragePointerReadAccess`, and `StoragePointerWriteAccess` from `starknet::storage`. -Both `Drop` and `Serde` are required for proper serialization/deserialization. +### Declaring and Using Storage Vectors -### Structs Storage Layout +Storage Vectors are declared using the `Vec` type with angle brackets specifying the element type, e.g., `addresses: Vec` within the `Storage` struct. -Struct members are stored contiguously in storage, in the order they are defined. The first member is at the struct's base address, and subsequent members follow. +**Adding Elements:** Use the `push` method to add an element to the end. -| Fields | Address | -| --------- | --------------------------- | -| `owner` | `owner.__base_address__` | -| `address` | `owner.__base_address__ +1` | +```rust +// Example of adding an element +self.addresses.push(caller); +``` -### Enums Storage Layout +**Retrieving Elements:** +* Use indexing (`vec[index]`) or `get(index)` to obtain a storage pointer to the element. Call `.read()` on the pointer to get the value. +* `get(index)` returns `Option`, returning `None` if the index is out of bounds. Indexing/`at` panics if out of bounds. -Enums store their variant's index (starting from 0) and any associated values. The index is stored at the enum's base address. If a variant has associated data, it's stored at the next address. +```rust +// Example of getting the n-th element safely +self.addresses.get(index).map(|ptr| ptr.read()) +``` -For `Expiration`: +**Retrieving All Elements:** Iterate over the indices of the storage `Vec`, read each element, and append it to a memory `Array`. -- `Finite` (index 0) with a `u64` value: - | Element | Address | - | ---------------------------- | --------------------------------- | - | Variant index (0 for Finite) | `expiration.__base_address__` | - | Associated `u64` limit date | `expiration.__base_address__ + 1` | -- `Infinite` (index 1): - | Element | Address | - | ------------------------------ | ----------------------------- | - | Variant index (1 for Infinite) | `expiration.__base_address__` | +**Modifying Elements:** Get a mutable pointer to the storage pointer at the desired index and use the `write` method. -## Storage Nodes +```rust +// Example of modifying the element at index +self.addresses[index].write(new_address); +``` -Storage nodes are special structs annotated with `#[starknet::storage_node]` that can contain storage-specific types like `Map` or `Vec`. They act as intermediate nodes in storage address calculations and can only exist within contract storage. +**Removing Elements:** The `pop` method removes and returns the last element as `Some(value)`, or `None` if empty, updating the stored length. -```cairo -#[starknet::storage_node] -struct ProposalNode { - title: felt252, - description: felt252, - yes_votes: u32, - no_votes: u32, - voters: Map, -} +```rust +// Example of popping the last element +self.addresses.pop() ``` -Accessing members of a storage node involves navigating through its fields: +### Storage Address Computation for Vecs -```cairo -let mut proposal = self.proposals.entry(proposal_id); -proposal.title.write(title); -``` +The storage address for elements in a `Vec` is computed as follows: -## Low-Level Storage Access (Syscalls) +1. The length of the `Vec` is stored at the base address, calculated as `sn_keccak(variable_name)`. +2. The elements are stored at addresses computed as `h(base_address, i)`, where `i` is the element's index and `h` is the Pedersen hash function. -Direct access to storage can be done via system calls: +### Summary of Storage Vec Operations -### `storage_read_syscall` +* Use the `Vec` type for collections in storage. +* Use `push` to add, `pop` to remove the last element, and `get`/indexing to read elements. +* Use `[index].write(value)` to modify an element in place. +* Address calculation involves `sn_keccak` for the base length address and Pedersen hashing for element addresses. -Reads a value from a specified storage address. +--- -- **Arguments**: `address_domain` (currently `0` for onchain mode), `address` (the storage address). -- **Return**: `SyscallResult`. +Sources: -```cairo -use starknet::storage_access::storage_base_address_from_felt252; +- https://www.starknet.io/cairo-book/ch103-01-optimizing-storage-costs.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html -let storage_address = storage_base_address_from_felt252(3534535754756246375475423547453); -storage_read_syscall(0, storage_address).unwrap_syscall(); -``` +--- -### `storage_write_syscall` +### Advanced Storage Structures and Optimization -Writes a value to a specified storage address. +#### Felt252Dict Implementation and Dictionary Squashing -- **Arguments**: `address_domain` (currently `0`), `address` (storage address), `value` (`felt252`). -- **Return**: `SyscallResult<()>`. +Implementing `Felt252Dict` involves scanning the entire entry list during read/write operations to find the last entry with the same key to extract its `previous_value`. This results in a worst-case time complexity of $O(n)$, where $n$ is the number of entries. -```cairo -pub extern fn storage_write_syscall( - address_domain: u32, address: StorageAddress, value: felt252, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` +This structure is necessary because Cairo proofs require "dictionary squashing" to verify computational integrity within STARK proof boundaries. Squashing verifies coherence by checking that for all entries with a key $k$, the $i$-th entry's `new_value` equals the $(i+1)$-th entry's `previous_value`, based on insertion order. -## Storing Key-Value Pairs with Mappings +Example entry list progression for squashing: -Mappings (`Map`) associate keys with values in storage. They compute storage slot addresses based on the hash of the key, rather than storing keys directly. Iteration over keys is not possible. +| key | previous | new | +| --- | --- | --- | +| Alex | 0 | 150 | +| Maria | 0 | 100 | +| Charles | 0 | 70 | +| Maria | 100 | 250 | +| Alex | 150 | 40 | +| Alex | 40 | 300 | +| Maria | 250 | 190 | +| Alex | 300 | 90 | -To declare a mapping: +After squashing, the entry list would be reduced to: -```cairo -user_values: Map, -``` +#### Storage Optimization via Bit-Packing -To access or modify entries: +Optimizing storage usage is critical in Cairo smart contracts because most transaction costs relate to storage updates, where each slot write incurs gas. Bit-packing minimizes data size by using the fewest bits possible for storage. -```cairo -// Writing to a mapping -self.user_values.entry(caller).write(amount); +The storage in a Starknet smart contract consists of a map with $2^{251}$ slots, each being a `felt252` initialized to 0. Packing multiple values into fewer slots reduces user gas costs. -// Reading from a mapping -let value = self.user_values.entry(address).read(); -``` +##### Bitwise Operations for Packing -Nested mappings are supported: +Packing combines several integers into a single larger integer if the container size accommodates the sum of the required bits (e.g., two `u8` and one `u16` into one `u32`). This requires bitwise operators: -```cairo -user_warehouse: Map>, -``` +* **Shifting:** Multiplying or dividing an integer by a power of 2 shifts the value left or right, respectively. +* **Masking (AND):** Applying an `AND` operator isolates specific bits. +* **Combining (OR):** Adding (using `OR`) two integers combines their values. -Access requires chaining `entry` calls: `self.user_warehouse.entry(caller).entry(item_id).write(quantity);` +Example of combining two `u8` into a `u16` (packing) and reversing it (unpacking): -## Storing Ordered Collections with Vectors +Packing and unpacking integer values -Vectors (`Vec`) store ordered collections in storage. Unlike memory arrays (`Array`), `Vec` is a phantom type for storage. +##### Implementing StorePacking Trait -To declare a vector: +Storage optimization can be achieved by implementing the `StorePacking` trait. The compiler uses the `StoreUsingPacking` implementation of the `Store` trait to automatically pack data before writing to storage and unpack it after reading. -```cairo -addresses: Vec, -``` +In this implementation: +* `TWO_POW_8` and `TWO_POW_40` are used for left shifting in `pack` and right shifting in `unpack`. +* `MASK_8` and `MASK_32` are used to isolate variables during `unpack`. +* Variables are converted to `u128` to enable bitwise operations. -Operations include: +This technique applies to any group of fields whose total bit size fits within the packed storage type (e.g., packing fields summing to 256 bits into a `u256`). A crucial requirement is that the type produced by `StorePacking::pack` must also implement the `Store` trait for `StoreUsingPacking` to function correctly. -- `push(value)`: Appends an element. -- `get(index)`: Returns an optional storage pointer to the element at `index`. -- `len()`: Returns the number of elements. -- `pop()`: Removes and returns the last element. -- Indexing (`vec[index]`): Accesses an element (panics if out of bounds). +--- -```cairo -// Appending to a vector -self.addresses.push(caller); +Sources: -// Getting an element by index -self.addresses.get(index).map(|ptr| ptr.read()); +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html -// Modifying an element -self.addresses[index].write(new_address); -``` +--- -To retrieve all elements, iterate and append to a memory `Array`. +### State Reading and Event Logging Layout -## Addresses of Storage Variables +#### Event Structure When Using `#[flat]` -Storage variable addresses are computed using `sn_keccak` hashes of variable names. +When an `Event` enum variant (like `FieldUpdated`) is annotated with `#[flat]`, the inner variant name (e.g., `Author`) is used as the event name for logging, instead of the outer variant name. -- **Single values**: `sn_keccak(variable_name)`. -- **Structs/Enums**: Base address is `sn_keccak(variable_name)`; layout depends on type structure. -- **Maps/Vecs**: Address computed relative to a base address using keys/indices. -- **Storage Nodes**: Address computed via a chain of hashes reflecting the node structure. +The resulting log structure for such an event is defined as follows: -The base address of a storage variable can be accessed via `__base_address__`. +* **First Key:** Calculated using `selector!("Author")`. +* **Second Key:** The value of the field annotated with `#[key]` (e.g., the `id` field). +* **Data Field:** The serialized data of the remaining fields. For the example provided: `0x5374657068656e204b696e67` (representing 'Stephen King'). -## Optimizing Storage Costs with Bit-packing +--- -Storage operations are costly. Bit-packing combines multiple variables into fewer storage slots to reduce gas costs. Integers can be combined if their total bit size fits within a larger integer type. Bitwise operators (`<<`, `>>`, `&`, `|`) are used for packing and unpacking. +Sources: -For example, packing `u8`, `u32`, and `u64` (total 104 bits) into a single `u128` slot: +- https://www.starknet.io/cairo-book/ch102-02-interacting-with-another-contract.html +- https://www.starknet.io/cairo-book/ch103-04-L1-L2-messaging.html +- https://www.starknet.io/cairo-book/ch102-03-executing-code-from-another-class.html +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html +- https://www.starknet.io/cairo-book/ch101-01-starknet-types.html +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html +- https://www.starknet.io/cairo-book/ch102-00-starknet-contract-interactions.html +- https://www.starknet.io/cairo-book/ch102-04-serialization-of-cairo-types.html -```cairo -struct Sizes { - tiny: u8, - small: u32, - medium: u64, -} -``` +--- -The storage slot would store these packed values, requiring bitwise operations for access. +--- -Contract Interaction and Communication +Sources: -# Contract Interaction and Communication +- https://www.starknet.io/cairo-book/ch101-01-starknet-types.html +- https://www.starknet.io/cairo-book/ch102-04-serialization-of-cairo-types.html -Smart contracts require external triggers, such as user actions or calls from other contracts, to execute. This inter-contract communication enables the development of complex applications. Understanding the Application Binary Interface (ABI), calling conventions, and inter-contract communication mechanisms is crucial. +--- -## Contract Class ABI +# Address Types and Data Serialization -The Contract Class ABI defines the interface of a contract, specifying callable functions, their parameters, and return types. It facilitates communication by encoding and decoding data according to the contract's interface. External sources typically use a JSON representation of the ABI. +## Contract and Storage Addresses -Functions are identified by their selectors, computed as `sn_keccak(function_name)`. Unlike some languages, function overloading based on parameters is not supported in Cairo, making the function name's hash a unique identifier. +Contract addresses in Starknet have a value range of $[0, 2^{251})$, enforced by the type system. A `ContractAddress` can be created from a `felt252` using the `TryInto` trait. -### Encoding and Decoding +### Storage Address -At the blockchain's low-level CASM instruction level, data is represented as `felt252`. The ABI dictates how higher-level types are serialized into `felt252` sequences for contract calls and deserialized back upon receiving data. +The `StorageAddress` type denotes the location of a value within a contract's storage. While usually handled internally by storage structures like Map and Vec, it's important for advanced patterns. Storage addresses share the same value range as contract addresses, $[0, 2^{251})$. The related `StorageBaseAddress` has a slightly smaller range of $[0, 2^{251} - 256)$ to accommodate offsets. -## The Dispatcher Pattern +```rust +#[starknet::contract] +mod StorageExample { + use starknet::storage_access::StorageAddress; -The dispatcher pattern simplifies contract interaction by using a generated struct that wraps a contract address and implements a trait derived from the contract's ABI. This provides a type-safe way to call functions on other contracts. + #[storage] + struct Storage { + value: u256, + } -The compiler automatically generates dispatchers for defined interfaces. For example, an `IERC20` interface might yield `IERC20Dispatcher` and `IERC20SafeDispatcher`. + // This is an internal function that demonstrates StorageAddress usage + // In practice, you rarely need to work with StorageAddress directly + fn read_from_storage_address(address: StorageAddress) -> felt252 { + starknet::syscalls::storage_read_syscall(0, address).unwrap() + } +} +``` -- **Contract Dispatchers**: Wrap a contract address for calling functions on deployed contracts. -- **Safe Dispatchers**: Allow callers to handle potential errors (panics) during function execution, returning a `Result` type. +## Ethereum Addresses -Under the hood, dispatchers utilize system calls like `starknet::syscalls::call_contract_syscall`. +The `EthAddress` type represents a 20-byte Ethereum address, primarily used for building cross-chain applications, L1-L2 messaging, and token bridges. -### Contract Dispatcher Example +```rust +use starknet::EthAddress; -A contract can use a dispatcher to interact with another contract, such as an ERC20 token. The dispatcher struct holds the target contract's address and implements the generated trait. Its function implementations serialize arguments, perform the `call_contract_syscall`, and deserialize the results. +#[starknet::interface] +pub trait IEthAddressExample { + fn set_l1_contract(ref self: TContractState, l1_contract: EthAddress); + fn send_message_to_l1(ref self: TContractState, recipient: EthAddress, amount: felt252); +} -```cairo #[starknet::contract] -mod TokenWrapper { - use starknet::{ContractAddress, get_caller_address}; - use super::ITokenWrapper; // Assuming ITokenWrapper is defined elsewhere - use super::{IERC20Dispatcher, IERC20DispatcherTrait}; +mod EthAddressExample { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::syscalls::send_message_to_l1_syscall; + use super::EthAddress; #[storage] - struct Storage {} + struct Storage { + l1_contract: EthAddress, + } #[abi(embed_v0)] - impl TokenWrapper of ITokenWrapper { - fn token_name(self: @ContractState, contract_address: ContractAddress) -> felt252 { - IERC20Dispatcher { contract_address }.name() + impl EthAddressExampleImpl of super::IEthAddressExample { + fn set_l1_contract(ref self: ContractState, l1_contract: EthAddress) { + self.l1_contract.write(l1_contract); } - fn transfer_token( - ref self: ContractState, - address: ContractAddress, - recipient: ContractAddress, - amount: u256, - ) -> bool { - let erc20_dispatcher = IERC20Dispatcher { contract_address: address }; - erc20_dispatcher.transfer_from(get_caller_address(), recipient, amount) + fn send_message_to_l1(ref self: ContractState, recipient: EthAddress, amount: felt252) { + // Send a message to L1 with recipient and amount + let payload = array![recipient.into(), amount]; + send_message_to_l1_syscall(self.l1_contract.read().into(), payload.span()).unwrap(); } } -} -``` - -### Handling Errors with Safe Dispatchers -Safe dispatchers return a `Result>`, where `T` is the expected return type and `Array` contains the panic reason if the call fails. This allows for custom error handling logic. - -```cairo -#[feature("safe_dispatcher")] -fn interact_with_failable_contract() -> u32 { - let contract_address = 0x123.try_into().unwrap(); - // Use the Safe Dispatcher - let faillable_dispatcher = IFailableContractSafeDispatcher { contract_address }; // Assuming IFailableContractSafeDispatcher exists - let response: Result> = faillable_dispatcher.can_fail(); // Assuming can_fail() exists - - // Match the result to handle success or failure - match response { - Result::Ok(x) => x, // Return the value on success - Result::Err(_panic_reason) => { - // Handle the error, e.g., log it or return a default value - 0 // Return 0 in case of failure - }, + #[l1_handler] + fn handle_message_from_l1(ref self: ContractState, from_address: felt252, amount: felt252) { + // Verify the message comes from the expected L1 contract + assert!(from_address == self.l1_contract.read().into(), "Invalid L1 sender"); + // Process the message... } } ``` -Certain critical failures, such as calls to non-existent contracts or class hashes, or failures within `deploy` or `replace_class` syscalls, may still result in immediate transaction reverts and cannot be caught by safe dispatchers. +## Data Serialization Overview -## Calling Contracts using Low-Level Calls +The field element (`felt252`), containing 252 bits, is the sole type in the Cairo VM. Data types that fit within 252 bits are represented by a single `felt`. Types larger than 252 bits are represented by a list of felts. Developers interacting with contracts must serialize arguments larger than 252 bits correctly for calldata formulation. Using Starknet SDKs or `sncast` is highly recommended to simplify this process. -Directly using `starknet::syscalls::call_contract_syscall` offers more control over data serialization and error handling than the dispatcher pattern. - -Arguments must be serialized into a `Span`, typically using the `Serde` trait. The syscall returns a serialized array of values that must be manually deserialized. +### Data types using at most 252 bits -```cairo -#[starknet::contract] -mod TokenWrapper { - use starknet::{ContractAddress, SyscallResultTrait, get_caller_address, syscalls}; - use super::ITokenWrapper; // Assuming ITokenWrapper is defined elsewhere +The following Cairo data types fit within 252 bits and are represented by a single felt: - #[storage] - struct Storage {} +* `ContractAddress` +* `EthAddress` +* `StorageAddress` +* `ClassHash` +* Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`, and `usize` +* `bytes31` +* `felt252` +* Signed integers: `i8`, `i16`, `i32`, `i64`, and `i128` - impl TokenWrapper of ITokenWrapper { - fn transfer_token( - ref self: ContractState, - address: ContractAddress, - recipient: ContractAddress, - amount: u256, - ) -> bool { - let mut call_data: Array = array![]; - Serde::serialize(@get_caller_address(), ref call_data); - Serde::serialize(@recipient, ref call_data); - Serde::serialize(@amount, ref call_data); +Note that a negative value, $(-x)$, is serialized as $(P-x)$, where $P = 2^{251} + 17 \cdot 2^{192} + 1$. - let mut res = syscalls::call_contract_syscall( - address, selector!("transfer_from"), call_data.span(), - ) - .unwrap_syscall(); +--- - Serde::::deserialize(ref res).unwrap() - } - } -} -``` +Sources: -## Executing Code from Another Class (Library Calls) +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html +- https://www.starknet.io/cairo-book/ch102-00-starknet-contract-interactions.html -Library calls allow a contract to execute logic from another class within its own execution context, affecting its own state. This differs from contract calls, where the called logic executes in the context of the called contract. +--- -When Contract A uses a library call to execute logic from Class B: +### Fundamentals of Contract Interaction -- `get_caller_address()` in B's logic returns the caller of A. -- `get_contract_address()` in B's logic returns the address of A. -- Storage updates in B's logic update A's storage. +#### Necessity and Scope of Contract Interaction +A smart contract requires an external trigger from an entity (user or another contract) to execute. The ability for contracts to interact facilitates the creation of complex applications where individual contracts handle specific functionalities. Key aspects of interaction include understanding the Application Binary Interface (ABI), how to call contracts, enabling inter-contract communication, and proper usage of classes as libraries. -### Library Dispatchers +#### Deploying and Interacting via `katana` +Interaction with Starknet contracts begins after deployment. For local development and testing, the `katana` local Starknet node is recommended over using testnets like Goerli. -Similar to contract dispatchers, library dispatchers wrap a `ClassHash` and use `starknet::syscalls::library_call_syscall`. +To start the local node: +```bash +katana +``` +This command starts the node with predeployed accounts, which are listed in the output, providing necessary addresses and private keys for testing interactions: +``` +... +PREFUNDED ACCOUNTS +================== -```cairo -// Assuming an interface IERC20 is defined -trait IERC20DispatcherTrait { - fn name(self: T) -> felt252; - fn transfer(self: T, recipient: ContractAddress, amount: u256); -} +| Account address | 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 +| Private key | 0x0300001800000000300000180000000000030000000000003006001800006600 +| Public key | 0x01b7b37a580d91bc3ad4f9933ed61f3a395e0e51c9dd5553323b8ca3942bb44e -#[derive(Copy, Drop, starknet::Store, Serde)] -struct IERC20LibraryDispatcher { - class_hash: starknet::ClassHash, -} +| Account address | 0x033c627a3e5213790e246a917770ce23d7e562baa5b4d2917c23b1be6d91961c +| Private key | 0x0333803103001800039980190300d206608b0070db0012135bd1fb5f6282170b +| Public key | 0x04486e2308ef3513531042acb8ead377b887af16bd4cdd8149812dfef1ba924d -impl IERC20LibraryDispatcherImpl of IERC20DispatcherTrait { - fn name(self: IERC20LibraryDispatcher) -> felt252 { - // starknet::syscalls::library_call_syscall is called here - // ... implementation details ... - unimplemented!() - } - fn transfer(self: IERC20LibraryDispatcher, recipient: ContractAddress, amount: u256) { - // starknet::syscalls::library_call_syscall is called here - // ... implementation details ... - unimplemented!() - } -} +| Account address | 0x01d98d835e43b032254ffbef0f150c5606fa9c5c9310b1fae370ab956a7919f5 +| Private key | 0x07ca856005bee0329def368d34a6711b2d95b09ef9740ebf2c7c7e3b16c1ca9c +| Public key | 0x07006c42b1cfc8bd45710646a0bb3534b182e83c313c7bc88ecf33b53ba4bcbc +... ``` -### Using the Library Dispatcher +#### Differentiating Call and Invoke Operations +Interacting with external functions is categorized based on state modification: +* **Calling Contracts:** Used for external functions that only read from the state. These operations do not alter the network state, thus requiring no fees or signing. +* **Invoking Contracts:** Used for external functions that write to the state. These operations alter the network state and mandate fees and signing. -A contract can execute logic from another class by instantiating a library dispatcher with the target class's hash and calling its functions. Storage modifications will apply to the calling contract. +#### Reading State with `starkli call` +Read functions are executed using the `starkli call` command. The general structure is: `starkli call [contract_address] [function] [input] --rpc [url]`. -```cairo -#[starknet::interface] -trait IValueStore { - fn set_value(ref self: TContractState, value: u128); - fn get_value(self: @TContractState) -> u128; -} +The function `voter_can_vote` checks eligibility. If the input address corresponds to a registered voter who hasn't voted, the result is 1 (true): +```bash +starkli call 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 voter_can_vote 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 --rpc http://0.0.0.0:5050 +``` +Checking an unregistered account with `is_voter_registered` yields 0 (false): +```bash +starkli call 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 is_voter_registered 0x44444444444444444 --rpc http://0.0.0.0:5050 +``` +Functions that modify state, such as casting a vote via the `vote` function, necessitate the use of the `starknet invoke` command. -#[starknet::contract] -mod ValueStoreExecutor { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ClassHash, ContractAddress}; - use super::{IValueStoreDispatcherTrait, IValueStoreLibraryDispatcher}; +--- - #[storage] - struct Storage { - logic_library: ClassHash, - value: u128, // This contract's storage - } +Sources: - #[constructor] - fn constructor(ref self: ContractState, logic_library: ClassHash) { - self.logic_library.write(logic_library); - } +- https://www.starknet.io/cairo-book/ch102-02-interacting-with-another-contract.html - #[abi(embed_v0)] - impl ValueStoreExecutor of super::IValueStore { - fn set_value(ref self: ContractState, value: u128) { - // Calls set_value in ValueStoreLogic's class context, updating self.value - IValueStoreLibraryDispatcher { class_hash: self.logic_library.read() } - .set_value(value); - } +--- - fn get_value(self: @ContractState) -> u128 { - // Calls get_value in ValueStoreLogic's class context, reading self.value - IValueStoreLibraryDispatcher { class_hash: self.logic_library.read() }.get_value() - } - } +# High-Level Dispatchers and Type-Safe Calling - #[external(v0)] - fn get_value_local(self: @ContractState) -> u128 { - self.value.read() // Reads the local storage directly - } -} -``` +The dispatcher pattern enables calling functions on another contract by utilizing a struct that wraps the contract address and implements the dispatcher trait generated by the compiler from the contract class ABI. This provides a type-safe method for contract interaction, abstracting the low-level `contract_call_syscall`. -### Calling Classes using Low-Level Calls +### Types of Generated Dispatchers -The `starknet::syscalls::library_call_syscall` can be used directly for more granular control. It requires the class hash, function selector, and serialized arguments. +When a contract interface is defined, the compiler automatically generates several dispatchers. Using `IERC20` as an example: -```cairo -#[starknet::contract] -mod ValueStore { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ClassHash, SyscallResultTrait, syscalls}; +* **Contract Dispatchers** (e.g., `IERC20Dispatcher`): Wrap a contract address and are used to call functions on other contracts. +* **Library Dispatchers** (e.g., `IERC20LibraryDispatcher`): Wrap a class hash and are used to call functions on classes. +* **'Safe' Dispatchers** (e.g., `IERC20SafeDispatcher`): Allow the caller to handle potential execution errors. - #[storage] - struct Storage { - logic_library: ClassHash, - value: u128, - } +### The Dispatcher Pattern Implementation Details - #[constructor] - fn constructor(ref self: ContractState, logic_library: ClassHash) { - self.logic_library.write(logic_library); - } +The contract dispatcher struct wraps the contract address and implements the generated trait. For each function, the implementation involves: serializing arguments into `__calldata__`, executing `contract_call_syscall` with the address, selector, and calldata, and deserializing the return value. - #[external(v0)] - fn set_value(ref self: ContractState, value: u128) -> bool { - let mut call_data: Array = array![]; - Serde::serialize(@value, ref call_data); +The following listing shows the generated items for an `IERC20` interface: - let mut res = syscalls::library_call_syscall( - self.logic_library.read(), selector!("set_value"), call_data.span(), - ) - .unwrap_syscall(); +```rust +use starknet::ContractAddress; - Serde::::deserialize(ref res).unwrap() +trait IERC20DispatcherTrait { + fn name(self: T) -> felt252; + fn transfer(self: T, recipient: ContractAddress, amount: u256); +} + +#[derive(Copy, Drop, starknet::Store, Serde)] +struct IERC20Dispatcher { + pub contract_address: starknet::ContractAddress, +} + +impl IERC20DispatcherImpl of IERC20DispatcherTrait { + fn name(self: IERC20Dispatcher) -> felt252 { + let mut __calldata__ = core::traits::Default::default(); + + let mut __dispatcher_return_data__ = starknet::syscalls::call_contract_syscall( + self.contract_address, selector!("name"), core::array::ArrayTrait::span(@__calldata__), + ); + let mut __dispatcher_return_data__ = starknet::SyscallResultTrait::unwrap_syscall( + __dispatcher_return_data__, + ); + core::option::OptionTrait::expect( + core::serde::Serde::::deserialize(ref __dispatcher_return_data__), + 'Returned data too short', + ) } + fn transfer(self: IERC20Dispatcher, recipient: ContractAddress, amount: u256) { + let mut __calldata__ = core::traits::Default::default(); + core::serde::Serde::::serialize(@recipient, ref __calldata__); + core::serde::Serde::::serialize(@amount, ref __calldata__); - #[external(v0)] - fn get_value(self: @ContractState) -> u128 { - self.value.read() + let mut __dispatcher_return_data__ = starknet::syscalls::call_contract_syscall( + self.contract_address, + selector!("transfer"), + core::array::ArrayTrait::span(@__calldata__), + ); + let mut __dispatcher_return_data__ = starknet::SyscallResultTrait::unwrap_syscall( + __dispatcher_return_data__, + ); + () } } ``` -Starknet Components - -# Starknet Components +### Calling Contracts Using the Contract Dispatcher -Components are reusable modules that can incorporate logic, storage, and events, extendable without code duplication. They function like Lego blocks, allowing you to enrich contracts by plugging in pre-written modules. A component's code becomes part of the contract it's embedded in, meaning components cannot be deployed independently. +A wrapper contract can use the imported dispatcher struct to interact with another contract by wrapping the target contract's address. -## What's in a Component? +The following example shows a `TokenWrapper` contract calling `name` and `transfer_from` on an ERC20 contract: -Similar to contracts, components can contain: - -- Storage variables -- Events -- External and internal functions - -## Creating Components +```rust +use starknet::ContractAddress; -To create a component: +#[starknet::interface] +trait IERC20 { + fn name(self: @TContractState) -> felt252; -1. Define it in its own module decorated with `#[starknet::component]`. -2. Declare a `Storage` struct and an `Event` enum within this module. -3. Define the component's interface by declaring a trait with `#[starknet::interface]`. -4. Implement the component's logic in an `impl` block marked with `#[embeddable_as(name)]`. This `impl` block typically implements the interface trait. + fn symbol(self: @TContractState) -> felt252; -```cairo -// Example of an embeddable impl for a component -#[embeddable_as(name)] -impl ComponentName< - TContractState, +HasComponent, -> of super::InterfaceName> { - // Component functions implementation -} -``` + fn decimals(self: @TContractState) -> u8; -Functions within these `impl` blocks expect arguments like `ref self: ComponentState` (for state-modifying functions) or `self: @ComponentState` (for view functions). This makes the `impl` generic over `TContractState`, allowing its use in any contract that implements the `HasComponent` trait. + fn total_supply(self: @TContractState) -> u256; -Internal functions can be defined in a separate `impl` block without the `#[embeddable_as]` attribute; they are not exposed externally but can be used within the embedding contract. + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; -## Migrating a Contract to a Component + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; -To migrate a contract to a component, make the following changes: + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; -- Add the `#[starknet::component]` attribute to the module. -- Add the `#[embeddable_as(name)]` attribute to the `impl` block to be embedded. -- Add generic parameters `TContractState` and `+HasComponent` to the `impl` block. -- Change `self` arguments within the `impl` block from `ContractState` to `ComponentState`. + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; -## Using Components Inside a Contract + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; +} -To integrate a component into a contract: +#[starknet::interface] +trait ITokenWrapper { + fn token_name(self: @TContractState, contract_address: ContractAddress) -> felt252; -1. Declare it using the `component!()` macro, specifying its path, a name for its storage variable in the contract, and a name for its event variant. -2. Add the component's storage and event types to the contract's `Storage` and `Event` structs, respectively. The storage variable must be annotated with `#[substorage(v0)]`. -3. Embed the component's logic by instantiating its generic `impl` with a concrete `ContractState` using an `impl` alias annotated with `#[abi(embed_v0)]`. + fn transfer_token( + ref self: TContractState, + address: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) -> bool; +} -```cairo +//**** Specify interface here ****// #[starknet::contract] -mod MyContract { - // Declare the component - component!(path: MyComponent, storage: my_comp_storage, event: MyComponentEvent); - - // Embed the component's logic - #[abi(embed_v0)] - impl MyComponentImpl = MyComponent::MyComponentImpl; +mod TokenWrapper { + use starknet::{ContractAddress, get_caller_address}; + use super::ITokenWrapper; + use super::{IERC20Dispatcher, IERC20DispatcherTrait}; #[storage] - struct Storage { - // Contract's own storage - my_data: u128, - // Component's storage, annotated with substorage - #[substorage(v0)] - my_comp_storage: MyComponent::Storage, - } + struct Storage {} - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - // Contract's own events - MyDataUpdated: MyDataUpdated, - // Component's events - MyComponentEvent: MyComponent::Event, + #[abi(embed_v0)] + impl TokenWrapper of ITokenWrapper { + fn token_name(self: @ContractState, contract_address: ContractAddress) -> felt252 { + IERC20Dispatcher { contract_address }.name() + } + + fn transfer_token( + ref self: ContractState, + address: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) -> bool { + let erc20_dispatcher = IERC20Dispatcher { contract_address: address }; + erc20_dispatcher.transfer_from(get_caller_address(), recipient, amount) + } } - - // ... rest of the contract } -``` - -The component's functions can then be called externally using a dispatcher instantiated with the contract's address. -## Component Dependencies +#[cfg(test)] +mod tests; +``` -Components can depend on other components. A component cannot be embedded within another component directly, but it can utilize another component's functionality. This is achieved by adding a generic constraint to the `impl` block that requires the `TContractState` to implement the `HasComponent` trait of the dependency component. +### Handling Errors with Safe Dispatchers -Within the `impl` block, the `get_dep_component!` macro (for read access) or `get_dep_component_mut!` macro (for mutable access) is used to access the state of the dependent component. +Safe dispatchers (e.g., `IERC20SafeDispatcher`) return a `Result::Err` containing the panic reason if the called function fails, allowing for graceful error handling. -```cairo -// Example of a component depending on another (Ownable) -#[starknet::component] -pub mod OwnableCounterComponent { - use super::OwnableComponent; // Assuming OwnableComponent is in scope - use OwnableComponent::{HasComponent as OwnerHasComponent}; // Alias for clarity +For a hypothetical `IFailableContract`: - #[storage] - pub struct Storage { - value: u32, - } +```rust +#[starknet::interface] +pub trait IFailableContract { + fn can_fail(self: @TState) -> u32; +} - #[embeddable_as(OwnableCounterImpl)] - impl OwnableCounter< - TContractState, - +HasComponent, - +Drop, - impl Owner: OwnerHasComponent, // Dependency constraint - > of super::IOwnableCounter> { - fn get_counter(self: @ComponentState) -> u32 { - self.value.read() - } +#[feature("safe_dispatcher")] +fn interact_with_failable_contract() -> u32 { + let contract_address = 0x123.try_into().unwrap(); + // Use the Safe Dispatcher + let faillable_dispatcher = IFailableContractSafeDispatcher { contract_address }; + let response: Result> = faillable_dispatcher.can_fail(); - fn increment(ref self: ComponentState) { - // Accessing the dependent component's state - let ownable_comp = get_dep_component!(@self, Owner); - ownable_comp.assert_only_owner(); // Using a function from the dependency - self.value.write(self.value.read() + 1); - } + // Match the result to handle success or failure + match response { + Result::Ok(x) => x, // Return the value on success + Result::Err(_panic_reason) => { + // Handle the error, e.g., log it or return a default value + // The panic_reason is an array of felts detailing the error + 0 // Return 0 in case of failure + }, } } ``` -## Example: An Ownable Component +--- -This example demonstrates an `Ownable` component that manages contract ownership. +Sources: -### Interface (`IOwnable`) +- https://www.starknet.io/cairo-book/ch102-02-interacting-with-another-contract.html -```cairo -#[starknet::interface] -trait IOwnable { - fn owner(self: @TContractState) -> ContractAddress; - fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); - fn renounce_ownership(ref self: TContractState); -} -``` +--- -### Component Implementation (`OwnableComponent`) +### Low-Level Contract Invocation via Syscalls -```cairo -#[starknet::component] -pub mod OwnableComponent { - use core::num::traits::Zero; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, get_caller_address}; - // Assuming Errors enum is defined elsewhere or in scope - // use super::Errors; +While safe dispatchers return a `Result>` allowing for error handling (as detailed in Chapter 9: Error Handling), some scenarios still cause an immediate transaction revert, meaning the error cannot be caught by the caller using a safe dispatcher. - #[storage] - pub struct Storage { - owner: ContractAddress, - } +#### Scenarios Leading to Immediate Revert - #[event] - #[derive(Drop, starknet::Event)] - pub enum Event { - OwnershipTransferred: OwnershipTransferred, - } +The following cases are expected to be handled in future Starknet versions: - #[derive(Drop, starknet::Event)] - struct OwnershipTransferred { - previous_owner: ContractAddress, - new_owner: ContractAddress, - } +* Failure in a Cairo Zero contract call. +* Library call with a non-existent class hash. +* Contract call to a non-existent contract address. +* Failure within the `deploy` syscall (e.g., panic in the constructor, deploying to an existing address). +* Using the `deploy` syscall with a non-existent class hash. +* Using the `replace_class` syscall with a non-existent class hash. - // Embeddable implementation of the interface - #[embeddable_as(OwnableImpl)] - impl Ownable< - TContractState, +HasComponent, - > of super::IOwnable> { - fn owner(self: @ComponentState) -> ContractAddress { - self.owner.read() - } +#### Using `call_contract_syscall` - fn transfer_ownership( - ref self: ComponentState, new_owner: ContractAddress, - ) { - // assert!(!new_owner.is_zero(), Errors::ZERO_ADDRESS_OWNER); // Assuming Errors enum - self.assert_only_owner(); - self._transfer_ownership(new_owner); - } +Another method for contract invocation is directly using the `call_contract_syscall`. This syscall provides greater control over serialization and deserialization compared to the dispatcher pattern. - fn renounce_ownership(ref self: ComponentState) { - self.assert_only_owner(); - self._transfer_ownership(Zero::zero()); - } - } +To use this syscall, one must pass the contract address, the selector of the target function, and the call arguments serialized into a `Span`. Serialization of arguments is achieved using the `Serde` trait, provided the types implement it. The call returns an array of serialized values, which must be deserialized by the caller. - // Internal functions implementation - #[generate_trait] - pub impl InternalImpl< - TContractState, +HasComponent, - > of InternalTrait { - fn initializer(ref self: ComponentState, owner: ContractAddress) { - self._transfer_ownership(owner); - } +Listing 16-4 demonstrates calling the `transfer_from` function of an `ERC20` contract: - fn assert_only_owner(self: @ComponentState) { - let owner: ContractAddress = self.owner.read(); - let caller: ContractAddress = get_caller_address(); - // assert!(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER); // Assuming Errors enum - // assert!(caller == owner, Errors::NOT_OWNER); // Assuming Errors enum - } +```rust +use starknet::ContractAddress; - fn _transfer_ownership( - ref self: ComponentState, new_owner: ContractAddress, - ) { - let previous_owner: ContractAddress = self.owner.read(); - self.owner.write(new_owner); - self - .emit( - OwnershipTransferred { previous_owner: previous_owner, new_owner: new_owner }, - ); +#[starknet::interface] +trait ITokenWrapper { + fn transfer_token( + ref self: TContractState, + address: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) -> bool; +} + +#[starknet::contract] +mod TokenWrapper { + use starknet::{ContractAddress, SyscallResultTrait, get_caller_address, syscalls}; + use super::ITokenWrapper; + + #[storage] + struct Storage {} + + impl TokenWrapper of ITokenWrapper { + fn transfer_token( + ref self: ContractState, + address: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) -> bool { + let mut call_data: Array = array![]; + Serde::serialize(@get_caller_address(), ref call_data); + Serde::serialize(@recipient, ref call_data); + Serde::serialize(@amount, ref call_data); + + let mut res = syscalls::call_contract_syscall( + address, selector!("transfer_from"), call_data.span(), + ) + .unwrap_syscall(); + + Serde::::deserialize(ref res).unwrap() } } } ``` -## Components Under the Hood +Listing 16-4: A sample contract using `call_contract_sycall` syscall -Components leverage "embeddable impls." An `impl` of a Starknet interface trait can be made embeddable using `#[starknet::embeddable]` or `#[starknet::embeddable_as(name)]`. When embedded, these impls add entry points to a contract's ABI. +--- -Components build on this by providing generic component logic. When a component is used in a contract, the `component!()` macro automatically generates a `HasComponent` trait. This trait provides functions like `get_component` and `get_component_mut` to bridge between the contract's generic `TContractState` and the component's specific `ComponentState`. The `#[embeddable_as(name)]` attribute on the component's `impl` block defines how the compiler generates the final embeddable impl that adapts the `ComponentState` arguments to the contract's `ContractState`. +Sources: -## Stacking Components +- https://www.starknet.io/cairo-book/ch102-03-executing-code-from-another-class.html -The true power of components lies in their composability. Multiple components can be stacked together in a single contract, each adding its features. Libraries like OpenZeppelin provide pre-built, audited components for common functionalities (e.g., ERC20, Access Control, Pausable) that developers can combine to build complex contracts efficiently. +--- -Contract Upgradeability +### Library Calls and Context Execution -# Contract Upgradeability +Library calls allow a contract to execute the logic of another class within its own execution context, which means state updates affect the caller's storage, unlike contract calls. -Starknet's upgradeability mechanism relies on the distinction between contract classes (the code) and contract instances (deployed contracts with their own storage). Multiple contracts can share the same class, and a contract can be upgraded to a new class. +#### Key Differences Between Contract Calls and Library Calls -## How Upgradeability Works in Starknet +| Feature | Contract Call (to deployed **contract**) | Library Call (to **class**) +| :--- | :--- | +| Execution Context | That of the called contract (B) | That of the caller (A) +| `get_caller_address()` in called logic | Address of A | Address of A's caller +| `get_contract_address()` in called logic | Address of B | Address of A +| Storage Updates | Update storage of B | Update storage of A -A contract class is represented by a `ClassHash`. Before a contract can be deployed, its class hash must be declared. A contract instance is a deployed contract associated with a specific class, possessing its own storage. +Library calls are performed using a class hash instead of a contract address when using the dispatcher pattern. -## Replacing Contract Classes +#### Library Dispatcher Implementation -### The `replace_class_syscall` +The library dispatcher uses `starknet::syscalls::library_call_syscall` instead of `starknet::syscalls::call_contract_syscall`. -The `replace_class` system call allows a deployed contract to update its source code by changing its associated class hash. To implement this, an entry point in the contract should execute `starknet::syscalls::replace_class_syscall` with the new class hash. +``` +use starknet::ContractAddress; -```cairo -use core::num::traits::Zero; -use starknet::{ClassHash, syscalls}; +trait IERC20DispatcherTrait { + fn name(self: T) -> felt252; + fn transfer(self: T, recipient: ContractAddress, amount: u256); +} -fn upgrade(new_class_hash: ClassHash) { - assert!(!new_class_hash.is_zero(), "Class hash cannot be zero"); - syscalls::replace_class_syscall(new_class_hash).unwrap(); +#[derive(Copy, Drop, starknet::Store, Serde)] +struct IERC20LibraryDispatcher { + class_hash: starknet::ClassHash, } -``` -Listing: Exposing `replace_class_syscall` to update the contract's class +impl IERC20LibraryDispatcherImpl of IERC20DispatcherTrait { + fn name( + self: IERC20LibraryDispatcher, + ) -> felt252 { // starknet::syscalls::library_call_syscall is called in here + } + fn transfer( + self: IERC20LibraryDispatcher, recipient: ContractAddress, amount: u256, + ) { // starknet::syscalls::library_call_syscall is called in here + } +} +``` -If a contract is deployed without a dedicated upgrade mechanism, its class hash can still be replaced using library calls. +#### Using the Library Dispatcher -### Class Hash Management Example +To use library calls via the dispatcher, an instance of the library dispatcher (`IValueStoreLibraryDispatcher` in the example) is created, passing the `class_hash` of the target class. Functions called on this dispatcher execute in the caller's context. -The `ClassHash` type represents the hash of a contract's code. It's used to deploy multiple contracts from the same code or to upgrade a contract. +In the example using `ValueStoreExecutor` calling `ValueStoreLogic`: -```cairo -use starknet::ClassHash; +* Calling `set_value` updates `ValueStoreExecutor`'s storage variable `value`. +* Calling `get_value` reads `ValueStoreExecutor`'s storage variable `value`. +``` #[starknet::interface] -pub trait IClassHashExample { - fn get_implementation_hash(self: @TContractState) -> ClassHash; - fn upgrade(ref self: TContractState, new_class_hash: ClassHash); +trait IValueStore { + fn set_value(ref self: TContractState, value: u128); + fn get_value(self: @TContractState) -> u128; } #[starknet::contract] -mod ClassHashExample { +mod ValueStoreLogic { + use starknet::ContractAddress; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::syscalls::replace_class_syscall; - use super::ClassHash; #[storage] struct Storage { - implementation_hash: ClassHash, - } - - #[constructor] - fn constructor(ref self: ContractState, initial_class_hash: ClassHash) { - self.implementation_hash.write(initial_class_hash); + value: u128, } #[abi(embed_v0)] - impl ClassHashExampleImpl of super::IClassHashExample { - fn get_implementation_hash(self: @ContractState) -> ClassHash { - self.implementation_hash.read() + impl ValueStore of super::IValueStore { + fn set_value(ref self: ContractState, value: u128) { + self.value.write(value); } - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - replace_class_syscall(new_class_hash).unwrap(); - self.implementation_hash.write(new_class_hash); + fn get_value(self: @ContractState) -> u128 { + self.value.read() } } } -``` - -## OpenZeppelin's Upgradeable Component - -OpenZeppelin Contracts for Cairo offers the `UpgradeableComponent` to simplify adding upgradeability. It's often used with the `OwnableComponent` for access control, restricting upgrades to the contract owner. - -### Usage -The `UpgradeableComponent` provides: - -- An internal `upgrade` function for safe class replacement. -- An `Upgraded` event emitted upon successful upgrade. -- Protection against upgrading to a zero class hash. - -```cairo #[starknet::contract] -mod UpgradeableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; +mod ValueStoreExecutor { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use starknet::{ClassHash, ContractAddress}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - // Upgradeable - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + use super::{IValueStoreDispatcherTrait, IValueStoreLibraryDispatcher}; #[storage] struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event, + logic_library: ClassHash, + value: u128, } #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); + fn constructor(ref self: ContractState, logic_library: ClassHash) { + self.logic_library.write(logic_library); } #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - // This function can only be called by the owner - self.ownable.assert_only_owner(); + impl ValueStoreExecutor of super::IValueStore { + fn set_value(ref self: ContractState, value: u128) { + IValueStoreLibraryDispatcher { class_hash: self.logic_library.read() } + .set_value((value)); + } - // Replace the class hash upgrading the contract - self.upgradeable.upgrade(new_class_hash); + fn get_value(self: @ContractState) -> u128 { + IValueStoreLibraryDispatcher { class_hash: self.logic_library.read() }.get_value() } } + + #[external(v0)] + fn get_value_local(self: @ContractState) -> u128 { + self.value.read() + } } ``` -Listing: Integrating OpenZeppelin's Upgradeable component in a contract +#### Calling Classes using Low-Level Calls -## Security Considerations +Directly using `library_call_syscall` offers more control over serialization and error handling. -Upgrades are sensitive operations requiring careful consideration: +Arguments must be serialized to a `Span` (using `Serde` trait) and passed along with the class hash and function selector. -- **API Changes:** Modifications to function signatures can break integrations. -- **Storage Changes:** Altering storage variable names, types, or organization can lead to data loss or corruption. Collisions can occur if storage slots are reused. -- **Backwards Compatibility:** Ensure compatibility with previous versions, especially when upgrading OpenZeppelin Contracts. +``` +#[starknet::contract] +mod ValueStore { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ClassHash, SyscallResultTrait, syscalls}; -Always ensure that only authorized roles can perform upgrades, pauses, or other critical functions. Use components like `OwnableComponent` to guard these privileged paths. + #[storage] + struct Storage { + logic_library: ClassHash, + value: u128, + } -```cairo -// components -component!(path: OwnableComponent, storage: ownable); -component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + #[constructor] + fn constructor(ref self: ContractState, logic_library: ClassHash) { + self.logic_library.write(logic_library); + } -#[abi(embed_v0)] -impl OwnableImpl = OwnableComponent::OwnableImpl; -impl InternalUpgradeableImpl = UpgradeableComponent::InternalImpl; + #[external(v0)] + fn set_value(ref self: ContractState, value: u128) -> bool { + let mut call_data: Array = array![]; + Serde::serialize(@value, ref call_data); -#[event] -fn Upgraded(new_class_hash: felt252) {} + let mut res = syscalls::library_call_syscall( + self.logic_library.read(), selector!("set_value"), call_data.span(), + ) + .unwrap_syscall(); -fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); - self.upgradeable._upgrade(new_class_hash); - Upgraded(new_class_hash); // emit explicit upgrade event + Serde::::deserialize(ref res).unwrap() + } + + #[external(v0)] + fn get_value(self: @ContractState) -> u128 { + self.value.read() + } } ``` +The call returns serialized values that must be deserialized manually. + +--- + +Sources: -L1-L2 Messaging +- https://www.starknet.io/cairo-book/ch103-04-L1-L2-messaging.html -# L1-L2 Messaging +--- -Starknet features a distinct `L1-L2` messaging system, separate from its consensus mechanism and state update submissions. This system enables "cross-chain" transactions, allowing smart contracts on different chains to interact. For instance, computations performed on one chain can be utilized on another. Bridges on Starknet heavily rely on this `L1-L2` messaging. +### Starknet L1-L2 Messaging System -Key characteristics of Starknet's messaging system: +Starknet utilizes an **L1-L2 messaging system** to enable interaction between smart contracts on Layer 1 (L1) and Layer 2 (L2), facilitating cross-chain transactions like token bridging. -- **Asynchronous**: Results of messages sent to the other chain cannot be awaited within the contract code execution. -- **Asymmetric**: - - `L1->L2`: Messages are automatically delivered to the target L2 contract by the Starknet sequencer. - - `L2->L1`: Only the hash of the message is sent to L1 by the sequencer; it must be consumed manually via an L1 transaction. +#### Overview and Properties -## The StarknetMessaging Contract +The messaging system is characterized by two key properties: -The core component of the `L1-L2` messaging system is the [`StarknetCore`][starknetcore etherscan] contract deployed on Ethereum. Within `StarknetCore`, the `StarknetMessaging` contract is responsible for passing messages between Starknet and Ethereum. It exposes an interface with functions to send messages to L2, receive messages from L2 on L1, and cancel messages. +* **Asynchronous**: Contract code (Solidity or Cairo) cannot await the result of a message sent on the other chain during its execution. +* **Asymmetric**: + * `L1->L2`: Messages are automatically delivered to the target L2 contract by the Starknet sequencer. + * `L2->L1`: Only the hash of the message is sent to L1 by the sequencer; consumption must be done manually via a transaction on L1. -```js +#### The StarknetMessaging Contract + +The core component is the `StarknetMessaging` contract, part of the `StarknetCore` Solidity contracts deployed on Ethereum. It manages sending messages to L2, receiving messages from L2, and message cancellation. + +The interface (`IStarknetMessaging`) includes: + +```solidity interface IStarknetMessaging is IStarknetMessagingEvents { function sendMessageToL2( @@ -8279,36 +7440,20 @@ interface IStarknetMessaging is IStarknetMessagingEvents { uint256 nonce ) external; } -``` - - Starknet messaging contract interface - -### Sending Messages from Ethereum to Starknet (L1->L2) - -To send messages from Ethereum to Starknet, your Solidity contracts must call the `sendMessageToL2` function of the `StarknetMessaging` contract. The Starknet sequencer monitors logs emitted by `StarknetMessaging` and executes an `L1HandlerTransaction` to call the target L2 contract. This process typically takes 1-2 minutes. - -**Fees**: +``` -- A minimum of 20,000 wei must be sent with the message to register its hash in Ethereum's storage. -- Additional L1 fees are required for the `L1HandlerTransaction` to cover deserialization and processing costs on L2. These fees can be estimated using tools like `starkli` or `snforge`. +For `L1->L2` messages, the sequencer listens to logs emitted by `StarknetMessaging` and executes an `L1HandlerTransaction` to call the target L2 function. -The `sendMessageToL2` function signature is: +#### Sending Messages from Ethereum to Starknet (L1 -> L2) -```js -function sendMessageToL2( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload -) external override returns (bytes32); -``` +To send a message from L1, Solidity contracts must call `sendMessageToL2` on the `StarknetMessaging` contract. -- `toAddress`: The target contract address on L2. -- `selector`: The selector of the function on the L2 contract. This function must be annotated with `#[l1_handler]`. -- `payload`: An array of `felt252` values (represented as `uint256` in Solidity). +1. **Execution**: The call requires a value (`msg.value`) of at least 20,000 wei to register the message hash in Ethereum storage. Additionally, fees must be paid on L1 for the subsequent `L1HandlerTransaction` executed by the sequencer on L2. +2. **Target Function**: The receiving function on Starknet must be annotated with `#[l1_handler]`. It is crucial to verify the `from_address` within the handler to ensure messages originate from a trusted L1 contract. -**Example (Solidity)**: +Example of sending a message with a single felt value from Solidity: -```js +```solidity // Sends a message on Starknet with a single felt. function sendMessageFelt( uint256 contractAddress, @@ -8331,27 +7476,25 @@ function sendMessageFelt( } ``` -On the Starknet side, functions intended to receive L1 messages must be annotated with `#[l1_handler]`. It's crucial to verify the sender's address (`from_address`) to ensure messages originate from trusted L1 contracts. +Example of the corresponding Cairo handler function: -**Example (Cairo)**: - -```cairo,noplayground -#[l1_handler] -fn msg_handler_felt(ref self: ContractState, from_address: felt252, my_felt: felt252) { - assert!(from_address == self.allowed_message_sender.read(), "Invalid message sender"); +```rust + #[l1_handler] + fn msg_handler_felt(ref self: ContractState, from_address: felt252, my_felt: felt252) { + assert!(from_address == self.allowed_message_sender.read(), "Invalid message sender"); - // You can now use the data, automatically deserialized from the message payload. - assert!(my_felt == 123, "Invalid value"); -} + // You can now use the data, automatically deserialized from the message payload. + assert!(my_felt == 123, "Invalid value"); + } ``` -### Sending Messages from Starknet to Ethereum (L2->L1) +#### Sending Messages from Starknet to Ethereum (L2 -> L1) -To send messages from Starknet to Ethereum, use the `send_message_to_l1_syscall` within your Cairo contracts. This syscall sends messages to the `StarknetMessaging` contract on L1. These messages are not automatically consumed; they require a manual call to `consumeMessageFromL2` on L1. +Messages are initiated on Starknet using the `syscalls::send_message_to_l1_syscall` in Cairo contracts. The L1 contract must then manually consume this message by calling `consumeMessageFromL2` on the `StarknetMessaging` contract after the L2 block is verified on L1 (approx. 3-4 hours). -**Example (Cairo)**: +Cairo example using the syscall: -```cairo,noplayground +```rust fn send_message_felt(ref self: ContractState, to_address: EthAddress, my_felt: felt252) { // Note here, we "serialize" my_felt, as the payload must be // a `Span`. @@ -8360,25 +7503,9 @@ To send messages from Starknet to Ethereum, use the `send_message_to_l1_syscall` } ``` -The `send_message_to_l1_syscall` arguments are: - -- `class_hash`: The hash of the class you want to use. -- `function_selector`: A selector for a function within that class, computed with the `selector!` macro. -- `calldata`: The calldata. - -**Example (Cairo `send_message_to_l1` syscall)**: - -```cairo,noplayground -pub extern fn send_message_to_l1_syscall( - to_address: felt252, payload: Span, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` - -On L1, your Solidity contract must call `consumeMessageFromL2`, providing the L2 contract address that sent the message and the payload. The `consumeMessageFromL2` function verifies the message's integrity. - -**Example (Solidity)**: +Solidity example for consumption: -```js +```solidity function consumeMessageFelt( uint256 fromAddress, uint256[] calldata payload @@ -8398,1222 +7525,1588 @@ function consumeMessageFelt( require(my_felt > 0, "Invalid value"); } ``` +The `consumeMessageFromL2` call validates the inputs (L2 contract address and payload) against the recorded message hash. -Note that `consumeMessageFromL2` uses `msg.sender` internally to compute the message hash, so the Solidity contract calling it must match the `to_address` provided in the L2 `send_message_to_l1_syscall`. +#### Cairo Serialization Considerations (Serde) -## Cairo Serde +Data sent between L1 and L2 must be serialized as an array of `felt252`. Since `felt252` is smaller than Solidity's `uint256`, values exceeding the maximum `felt252` limit will cause the message to get stuck. -When sending messages between L1 and L2, data must be serialized into an array of `felt252`. Since `felt252` is slightly smaller than Solidity's `uint256`, careful serialization is required to avoid message failure. Values exceeding the maximum `felt252` limit will cause messages to be stuck. +A Cairo `u256` struct (composed of `low: u128` and `high: u128`) serializes into **two** felts: -For example, a `u256` type in Cairo is represented as: - -```cairo,does_not_compile +```rust struct u256 { low: u128, high: u128, } ``` -This `u256` will be serialized into **two** `felt252` values (one for `low`, one for `high`). Therefore, sending a single `u256` from L1 to L2 requires a payload with two `uint256` elements in Solidity. +To send a single `u256` value (e.g., value 1) from L1, the payload must contain two `uint256` elements in Solidity: -**Example (Solidity serialization of `u256`)**: - -```js +```solidity uint256[] memory payload = new uint256[](2); // Let's send the value 1 as a u256 in cairo: low = 1, high = 0. payload[0] = 1; payload[1] = 0; ``` -For more details, refer to the [Starknet documentation][starknet messaging doc] or the [detailed guide][glihm messaging guide]. - -[starknetcore etherscan]: https://etherscan.io/address/0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4 -[IStarknetMessaging]: https://github.com/starkware-libs/cairo-lang/blob/4e233516f52477ad158bc81a86ec2760471c1b65/src/starkware/starknet/eth/IStarknetMessaging.sol#L6 -[messaging contract]: https://github.com/glihm/starknet-messaging-dev/blob/main/solidity/src/ContractMsg.sol -[starknet addresses]: https://docs.starknet.io/documentation/tools/important_addresses/ -[starknet messaging doc]: https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/messaging-mechanism/ -[glihm messaging guide]: https://github.com/glihm/starknet-messaging-dev - -Oracles and Randomness - -# Oracles and Randomness - -Oracles are essential for bringing off-chain data to the Starknet blockchain, enabling smart contracts to access real-world information like asset prices or weather data. Verifiable Random Functions (VRFs) provided by oracles are crucial for applications requiring unpredictable randomness, such as gaming or NFT generation, ensuring fairness and transparency. - -## Oracles +--- -Oracles act as secure intermediaries, transmitting external data to blockchains and smart contracts. This section details interactions with the Pragma oracle on Starknet. +Sources: -### Pragma Oracle for Price Feeds +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html -Pragma is a zero-knowledge oracle providing verifiable off-chain data on Starknet. +--- -#### Setting Up Your Contract for Price Feeds +### Practical Deployment and CLI Interaction -1. **Add Pragma as a Project Dependency**: - Edit your `Scarb.toml` file: - ```toml - [dependencies] - pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" - ``` - -Development Tools and Best Practices +The class hash for the contract is: `0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52`. This hash can be used for declaration on Sepolia testnet. -# Development Tools and Best Practices +#### Deployment using starkli -### Starkli Tooling +The `starkli deploy` command requires specifying the RPC endpoint via `--rpc` (e.g., `http://0.0.0.0:5050` for Katana) and the signing account via `--account` (e.g., `katana-0`). When using a local node like Katana, transactions finalize immediately. -Ensure your `starkli` version is up-to-date. The recommended version is `0.3.6`. You can check your version with `starkli --version` and upgrade using `starkliup` or `starkliup -v 0.3.6`. - -To retrieve a smart wallet's class hash using `starkli`, use: +The command deploys the contract and registers initial voters using constructor arguments: ```bash -starkli class-hash-at --rpc http://0.0.0.0:5050 +starkli deploy --rpc http://0.0.0.0:5050 --account katana-0 ``` -### Contract Deployment - -Declare contracts using the `starkli declare` command: +An example deployment command: ```bash -starkli declare target/dev/listing_99_12_vote_contract_Vote.contract_class.json --rpc http://0.0.0.0:5050 --account katana-0 +starkli deploy 0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 0x033c627a3e5213790e246a917770ce23d7e562baa5b4d2917c23b1be6d91961c 0x01d98d835e43b032254ffbef0f150c5606fa9c5c9310b1fae370ab956a7919f5 --rpc http://0.0.0.0:5050 --account katana-0 ``` -If you encounter `compiler-version` errors, ensure `starkli` is updated or specify the compiler version using `--compiler-version x.y.z`. - -The class hash for a contract can be verified. For example, `0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52` is a known class hash. - -The `--rpc` flag points to the RPC endpoint (e.g., from `katana`), and `--account` specifies the signing account. Transactions on local nodes finalize immediately, while testnets may take seconds. +The deployed contract address will be specific to your deployment, for example: `0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349`. -### Smart Contract Best Practices +#### Reading Voter Eligibility -#### Deployment Safety +The contract includes external read functions to check eligibility without altering state: `voter_can_vote` and `is_voter_registered`. -- **`deploy_syscall(deploy_from_zero=true)` Collisions**: Setting `deploy_from_zero` to `true` can lead to collisions if multiple contracts are deployed with identical calldata. Use `deploy_from_zero=false` unless deterministic deployment from zero is explicitly intended. -- **Avoid `get_caller_address().is_zero()` Checks**: These checks, inherited from Solidity, are ineffective on Starknet as `get_caller_address()` never returns the zero address. +#### Invoking Contract Functions (Voting) -#### Bridging Safety (L1-L2 Interactions) +The `invoke` command is used for state-changing operations, such as voting. Voting requires signing the transaction and incurs a fee. The input for the `vote` function is `1` (Yes) or `0` (No). -- **L1 Handler Caller Validation**: Entrypoints marked with `#[l1_handler]` are callable from L1. It is crucial to validate the caller address to ensure calls originate from trusted L1 contracts. - - ```cairo, noplayground - #[l1_handler] - fn handle_deposit( - ref self: ContractState, - from_address: ContractAddress, - account: ContractAddress, - amount: u256 - ) { - let l1_bridge = self._l1_bridge.read(); - assert!(!l1_bridge.is_zero(), 'UNINIT_BRIDGE'); - assert!(from_address == l1_bridge, 'ONLY_L1_BRIDGE'); - // credit account… - } - ``` - -#### Economic/DoS & Griefing Vulnerabilities - -- **Unbounded Loops**: User-controlled iterations in functions (e.g., claims, batch withdrawals) can exceed Starknet's step limit. Cap the number of iterations or implement pagination to split work across multiple transactions. An unbounded list of items in storage, for instance, could be exploited to make a function run indefinitely until it hits the execution step limit. - -Cairo Virtual Machine (VM) and Execution - -Introduction to Cairo and its Virtual Machine (VM) - -# Introduction to Cairo and its Virtual Machine (VM) - -Cairo programs are compiled by the Cairo Compiler and then executed by the Cairo Virtual Machine (VM). The VM generates an execution trace used by the Prover to create a STARK proof, which is later verified by a Verifier. The upcoming chapters will detail the Cairo VM's architecture, memory model, execution model, builtins, hints, and the runner. - -## Virtual Machine +Voting Yes: +```bash +starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 1 --rpc http://0.0.0.0:5050 --account katana-0 +``` -A Virtual Machine (VM) is a software emulation of a physical computer, providing a complete programming environment through an API for program execution. Every VM has an Instruction Set Architecture (ISA) for expressing programs, which can be a standard ISA (like RISC-V) or a dedicated one (like Cairo assembly, CASM). +Voting No: +```bash +starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 0 --rpc http://0.0.0.0:5050 --account katana-0 +``` -There are two types of VMs: +After submission, you receive a transaction hash, which can be queried for details: -- **System Virtual Machines**: Emulate an operating system (e.g., Xen, VMWare). -- **Process Virtual Machines**: Provide the environment for a single user-level process. The Java Virtual Machine (JVM) is a well-known example. A Java program is compiled to Java bytecode, which the JVM verifies for safety. The bytecode is then either interpreted or Just-In-Time (JIT) compiled to machine code during execution. +```bash +starkli transaction --rpc http://0.0.0.0:5050 +``` -Cairo VM Architecture and Execution Flow +The output provides details like transaction hash, max fee, and signature. -# Cairo VM Architecture and Execution Flow +#### Error Handling -## Execution Model +Attempting to vote twice with the same signer results in a generic error: +``` +Error: code=ContractError, message="Contract error" +``` +More detailed error reasons, such as `"USER_ALREADY_VOTED"`, can be found by inspecting the output logs of the running `katana` node. -The CPU architecture of the Cairo VM defines how the VM processes instructions and changes its state, mirroring a physical CPU's fetch-decode-execute cycle. The VM's execution model is defined by its registers, a unique instruction set architecture, and the VM's state transition function. +--- -### Registers +Sources: -The Cairo VM has three dedicated registers: +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html -- **`pc` (Program Counter):** Holds the memory address of the next instruction. It's incremented after most instructions, but jump and call instructions can alter it. -- **`ap` (Allocation Pointer):** Acts as a stack pointer, typically pointing to the next free memory cell. Many instructions increment `ap` by 1. -- **`fp` (Frame Pointer):** Provides a stable reference for the current function's execution context (stack frame). It's set to the current `ap` when a function is called, allowing reliable access to arguments and return addresses. +--- -The state of the VM at any moment is defined by the values of these three registers. +### Transaction Receipts and Event Handling -## Instructions and Opcodes +To understand what happens internally, transaction receipts show emitted events. -A Cairo instruction is a 64-bit field element containing three 16-bit signed offsets (`off_dst`, `off_op0`, `off_op1`) and 15 boolean flags that dictate register usage, operations, and state updates. +#### Example 1: Add a book -The VM supports three core opcodes: +When invoking `add_book` (e.g., id=42, title='Misery', author='S. King'), the transaction receipt's "events" section contains serialized event data. -1. **`CALL`**: Saves the current context (`fp` and return `pc`) to the stack and initiates a function call. -2. **`RET`**: Restores the caller's context from the stack, executing a function return. -3. **`ASSERT_EQ`**: Enforces an equality constraint. +The structure observed in the receipt is: +```json +"events": [ + { + "from_address": "0x27d07155a12554d4fd785d0b6d80c03e433313df03bb57939ec8fb0652dbe79", + "keys": [ + "0x2d00090ebd741d3a4883f2218bd731a3aaa913083e84fcf363af3db06f235bc", + "0x532e204b696e67" + ], + "data": [ + "0x2a", + "0x4d6973657279" + ] + } + ] +``` -### Cairo Assembly (CASM) +Interpretation of the receipt fields for this event: +* `from_address`: The smart contract's address. +* `keys`: Contains serialized `felt252` key fields. + * The first key is the event selector, `selector!("BookAdded")`. + * The second key, `0x532e204b696e67` ('S. King'), corresponds to the `author` field defined with `#[key]`. +* `data`: Contains serialized `felt252` data fields. + * `0x2a` (42) is the `id` data field. + * `0x4d6973657279` ('Misery') is the `title` data field. -CASM is the human-readable assembly language for Cairo, representing machine instructions textually. Each line of CASM corresponds to a specific instruction. +#### Example 2: Update a book author -## State Transition +Invoking `change_book_author` (id=42, new\_author='Stephen King') emits a `FieldUpdated` event. The receipt structure reflects this: -The state of the Cairo VM at step \(i\) is defined by \((pc*i, ap_i, fp_i)\). The **state transition function** deterministically computes the next state \((pc*{i+1}, ap*{i+1}, fp*{i+1})\) based on the current state and the fetched instruction. This process is part of the algebraic constraints in the Cairo AIR; if all steps satisfy the constraints, a proof can be generated. +```json +"events": [ + { + "from_address": "0x27d07155a12554d4fd785d0b6d80c03e433313df03bb57939ec8fb0652dbe79", + "keys": [ + "0x1b90a4a3fc9e1658a4afcd28ad839182217a69668000c6104560d6db882b0e1", + "0x2a" + ], + "data": [ + "0x5374657068656e204b696e67" + ] + } + ] +``` +This event structure corresponds to `FieldUpdated::Author(UpdatedAuthorData { id: 42, title: author: 'Stephen King' })`. The key includes the selector and the book ID (0x2a), while the data contains the new author name serialized. -Each step checks one instruction and enforces its semantics as algebraic constraints. For example, an instruction might load values from memory, perform a computation, write to memory, and update the `pc`, `ap`, and `fp` registers. These rules are deterministic, ensuring a single valid next state. If any step violates the constraints, the execution cannot be proven. +--- -### Deterministic and Non-deterministic Cairo Machine +Sources: -- **Deterministic Machine:** Used by the prover. It takes a trace and the whole memory, verifying that the transition between consecutive states is valid. It returns `accept` if all transitions are valid, `reject` otherwise. -- **Non-deterministic Machine:** Used by the verifier. It takes the initial and final states and public memory. It returns `accept` if a valid trace and memory extension exist that the deterministic machine accepts. This allows for succinct, zero-knowledge verification. +- https://www.starknet.io/cairo-book/ch103-02-00-composability-and-components.html +- https://www.starknet.io/cairo-book/ch103-05-02-randomness.html +- https://www.starknet.io/cairo-book/ch103-06-02-working-with-erc20-token.html +- https://www.starknet.io/cairo-book/ch12-11-offloading-computations-with-oracles.html +- https://www.starknet.io/cairo-book/ch103-02-01-under-the-hood.html +- https://www.starknet.io/cairo-book/ch103-02-02-component-dependencies.html +- https://www.starknet.io/cairo-book/ch103-05-01-price-feeds.html +- https://www.starknet.io/cairo-book/ch103-02-03-testing-components.html +- https://www.starknet.io/cairo-book/ch103-03-upgradeability.html +- https://www.starknet.io/cairo-book/appendix-05-common-error-messages.html +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html +- https://www.starknet.io/cairo-book/ch103-04-L1-L2-messaging.html +- https://www.starknet.io/cairo-book/ch103-05-oracle-interactions.html +- https://www.starknet.io/cairo-book/ch104-01-general-recommendations.html -The Cairo Runner is the entrypoint for running a Cairo program and generating the AIR inputs needed for proof. +--- -## Circuit Evaluation +--- -To evaluate a circuit and obtain results, you first initialize inputs by calling `next` on each `CircuitInputAccumulator` variant. After initialization, the `done` function provides the complete circuit data. +Sources: -Define the modulus (e.g., BN254 prime field modulus) using `CircuitModulus`. The evaluation is performed using `instance.eval(bn254_modulus).unwrap()`. Results for specific gates can be retrieved using `res.get_output(gate_element)`. +- https://www.starknet.io/cairo-book/ch103-02-00-composability-and-components.html +- https://www.starknet.io/cairo-book/ch103-06-02-working-with-erc20-token.html -```cairo -# use core::circuit::{ -# AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, -# CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, -# }; -# -# // Circuit: a * (a + b) -# // witness: a = 10, b = 20 -# // expected output: 10 * (10 + 20) = 300 -# fn eval_circuit() -> (u384, u384) { -# let a = CircuitElement::> {}; -# let b = CircuitElement::> {}; -# -# let add = circuit_add(a, b); -# let mul = circuit_mul(a, add); -# -# let output = (mul,); -# -# let mut inputs = output.new_inputs(); -# inputs = inputs.next([10, 0, 0, 0]); -# inputs = inputs.next([20, 0, 0, 0]); -# -# let instance = inputs.done(); -# -# let bn254_modulus = TryInto::<_, -# CircuitModulus, -# >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) -# .unwrap(); -# - let res = instance.eval(bn254_modulus).unwrap(); -# -# let add_output = res.get_output(add); -# let circuit_output = res.get_output(mul); -# -# assert!(add_output == u384 { limb0: 30, limb1: 0, limb2: 0, limb3: 0 }, "add_output"); -# assert!(circuit_output == u384 { limb0: 300, limb1: 0, limb2: 0, limb3: 0 }, "circuit_output"); -# -# (add_output, circuit_output) -# } -# -# #[executable] -# fn main() { -# eval_circuit(); -# } -``` +--- -## Program Execution Flow +# Cairo Components: Fundamentals and Implementation -A Cairo program `prgm.cairo` is compiled into `prgm.json`, containing Cairo bytecode (encoded CASM and extra data). The Cairo VM interprets this CASM and generates an execution trace. This trace data is then used by the Cairo Prover to generate a STARK proof, verifying the program's correct execution. +## Components: Lego-Like Building Blocks -Each instruction and its arguments increment the Program Counter (PC) by 1. Instructions like `call` and `ret` manage a function stack. +Components are modular add-ons that encapsulate reusable logic, storage, and events, allowing them to be incorporated into multiple contracts. They extend functionality without reimplementing shared logic. Think of them as Lego blocks that enrich contracts. -- **`call rel offset`**: Increments the PC by `offset` and jumps to the new location. -- **`ret`**: Jumps back to the instruction immediately following the corresponding `call`. +Unlike a contract, a component cannot be deployed on its own; its logic becomes part of the contract's bytecode when embedded. -For example: +### What's in a Component? -- `call rel 3` increments PC by 3, then `call rel 9` increments PC by 9. -- `[ap + 0] = 2, ap++` stores `2` in the next free memory cell pointed to by `ap` and increments `ap`. -- `ret` jumps back after the `call` instruction. +A component is similar to a contract and can contain: -## Memory Model +* Storage variables +* Events +* External and internal functions -The Cairo VM follows a Von Neumann architecture where a single memory space stores both program instructions and data. The memory is non-deterministic and read-only for the verifier. +## Creating Components -### Relocatable Values and Memory Space +To create a component, define it in a module decorated with `#[starknet::component]`. Within this module, declare a `Storage` struct and an `Event` enum. -Relocatable values are represented as `Segment:Offset`. After execution, these values are converted into a contiguous linear memory address space using a relocation table. The relocation table maps segment identifiers to their starting indices in the linear memory. +### Defining the Interface and Implementation -Example memory and relocation table: +1. **Interface**: Define the component interface by declaring a trait with the `#[starknet::interface]` attribute, specifying signatures for external access. +2. **External Logic**: Implement the external logic in an `impl` block marked as `#[embeddable_as(name)]`, usually implementing the interface trait. `name` is the reference used in the contract. +3. **Internal Logic**: Define internal functions by omitting the `#[embeddable_as(name)]` attribute above the `impl` block. These functions are usable within the embedding contract but are not part of its ABI. -``` -Addr Value ------------ -// Segment 0 -0:0 5189976364521848832 -0:1 10 -... -// Segment 1 -1:0 2:0 -... -// Segment 2 -2:0 110 -``` +Functions in these `impl` blocks are generic over the contract state, using arguments like `ref self: ComponentState` (for state modification) or `self: @ComponentState` (for view functions). -**From relocation value to one contiguous memory address space:** +### Example: Ownable Component Interface ``` -Addr Value ------------ -1 5189976364521848832 -2 10 -... -13 22 -14 23 -... -22 110 +#[starknet::interface] +trait IOwnable { + fn owner(self: @TContractState) -> ContractAddress; + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TContractState); +} ``` -**Relocation table:** +### Example: Ownable Component Implementation ``` -segment_id starting_index ----------------------------- -0 1 -1 13 -2 22 -``` - -Cairo VM Memory Model +#[starknet::component] +pub mod OwnableComponent { + use core::num::traits::Zero; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + use super::Errors; -# Cairo VM Memory Model + #[storage] + pub struct Storage { + owner: ContractAddress, + } -The Cairo VM memory model is designed to be efficient for proof generation, functioning as a write-once system. This contrasts with traditional read-write memory models found in systems like the EVM. + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + OwnershipTransferred: OwnershipTransferred, + } -## Overview + #[derive(Drop, starknet::Event)] + struct OwnershipTransferred { + previous_owner: ContractAddress, + new_owner: ContractAddress, + } -In the Cairo VM, memory is crucial not only for storing temporary values during execution but also for recording memory accesses within trace cells to facilitate proof generation. The model prioritizes streamlining the STARK proving process. + #[embeddable_as(OwnableImpl)] + impl Ownable< + TContractState, +HasComponent, + > of super::IOwnable> { + fn owner(self: @ComponentState) -> ContractAddress { + self.owner.read() + } -## Non-Deterministic Read-only Memory + fn transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress, + ) { + assert(!new_owner.is_zero(), Errors::ZERO_ADDRESS_OWNER); + self.assert_only_owner(); + self._transfer_ownership(new_owner); + } -Cairo employs a non-deterministic, read-only memory model, which effectively behaves as a write-once memory: + fn renounce_ownership(ref self: ComponentState) { + self.assert_only_owner(); + self._transfer_ownership(Zero::zero()); + } + } -1. **Non-determinism**: Memory addresses and their values are asserted by the prover rather than being managed by a traditional memory system. For instance, the prover asserts that value `7` is stored at address `x`, without needing to explicitly check its existence. -2. **Read-only**: Once a value is written to a memory address, it cannot be overwritten. Subsequent operations can only read or verify the existing value. + #[generate_trait] + pub impl InternalImpl< + TContractState, +HasComponent, + > of InternalTrait { + fn initializer(ref self: ComponentState, owner: ContractAddress) { + self._transfer_ownership(owner); + } -The memory address space is contiguous; if addresses `x` and `y` are accessed, no address between them can be skipped. The cost of using Cairo's memory is primarily determined by the number of memory accesses, not the number of addresses used. Rewriting to an existing cell incurs a similar cost to writing to a new one. This write-once characteristic simplifies constraint formulation for proving program correctness. + fn assert_only_owner(self: @ComponentState) { + let owner: ContractAddress = self.owner.read(); + let caller: ContractAddress = get_caller_address(); + assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER); + assert(caller == owner, Errors::NOT_OWNER); + } -## Memory Segments + fn _transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress, + ) { + let previous_owner: ContractAddress = self.owner.read(); + self.owner.write(new_owner); + self + .emit( + OwnershipTransferred { previous_owner: previous_owner, new_owner: new_owner }, + ); + } + } +} +``` -Cairo organizes memory addresses into **segments**, allowing for dynamic expansion while ensuring immutability after writing. Each memory address is initially associated with a **relocatable value**, represented as `:`. At the end of execution, these relocatable values are transformed into a single, contiguous memory address space, accompanied by a **relocation table**. +> Note: Follow OpenZeppelin’s pattern: keep the `Impl` suffix in the embeddable name and contract impl alias (e.g., `OwnableImpl`), while the local component impl is named after the trait without the suffix (e.g., `impl Ownable<...>`). -Cairo's memory model includes the following segments: +## Migrating a Contract to a Component -- **Program Segment**: Stores the bytecode (instructions) of the Cairo program. The Program Counter (`pc`) starts at the beginning of this segment. This segment has a fixed size during execution. -- **Execution Segment**: Stores execution data such as temporary variables, function call frames, and pointers. The Allocation Pointer (`ap`) and Frame Pointer (`fp`) start in this segment. -- **Builtin Segment**: Stores builtins actively used by the program. Each builtin has its own dedicated segment, allocated dynamically. -- **User Segment**: Stores program outputs, arrays, and dynamically allocated data structures. +Migration requires minimal changes: -Segments 1 onwards (Execution, Builtin, User) have dynamic address spaces whose lengths are unknown until program completion. +* Add `#[starknet::component]` attribute to the module. +* Add `#[embeddable_as(name)]` attribute to the `impl` block to be embedded. +* Add generic parameters to the `impl` block: `TContractState` and the restriction `+HasComponent`. +* Change the `self` argument type in functions from `ContractState` to `ComponentState`. -The standard layout of memory segments is: +For traits generated via `#[generate_trait]`, the trait is generic over `TContractState` instead of `ComponentState`. -1. Segment 0: Program Segment -2. Segment 1: Execution Segment -3. Segment 2 to x: Builtin Segments -4. Segment x + 1 to y: User Segments +## Using Components Inside a Contract -The number of builtin and user segments is dynamic. +To integrate a component, you must: -## Relocation +1. Declare it using the `component!()` macro, specifying: + * The path to the component (`path::to::component`). + * The name of the storage variable (`ownable`). + * The name of the event enum variant (`OwnableEvent`). +2. Add the path to the component's storage and events to the contract's storage and event definitions. -During execution, memory addresses are managed with relocatable values. At the end of execution, these are mapped to a single, contiguous linear memory address space. A relocation table provides context for this linear space. +### Example: Basic ERC20 Contract Using Components -Consider the following Cairo Zero program example: +``` +#[starknet::contract] +pub mod BasicERC20 { + use openzeppelin_token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; -```cairo -%builtins output + component!(path: ERC20Component, storage: erc20, event: ERC20Event); -func main(output_ptr: felt*) -> (output_ptr: felt*) { + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; - // We are allocating three different values to segment 1. - [ap] = 10, ap++; - [ap] = 100, ap++; - [ap] = [ap - 2] + [ap - 1], ap++; + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + } - // We set value of output_ptr to the address of where the output will be stored. - // This is part of the output builtin requirement. - [ap] = output_ptr, ap++; + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + } - // Asserts that output_ptr equals to 110. - assert [output_ptr] = 110; + #[constructor] + fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { + let name = "MyToken"; + let symbol = "MTK"; - // Returns the output_ptr + 1 as the next unused memory address. - return (output_ptr=output_ptr + 1); + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + } } ``` -This program stores `10`, `100`, and `110` (the sum) in Segment 1. The `output_ptr` also interacts with the output builtin, which handles a dedicated memory region for program outputs. - -## Built-in Segments (Output and Segment Arena) +--- -### Output Builtin +Sources: -The Output Builtin manages a dedicated memory region, often referred to as **public memory**, where program outputs are stored. These outputs are made available in the proof system for verification. The output segment is a contiguous block of cells, and all its cells are public and accessible to verifiers. +- https://www.starknet.io/cairo-book/ch103-02-00-composability-and-components.html +- https://www.starknet.io/cairo-book/ch103-02-01-under-the-hood.html +- https://www.starknet.io/cairo-book/ch103-02-02-component-dependencies.html +- https://www.starknet.io/cairo-book/ch103-06-02-working-with-erc20-token.html +- https://www.starknet.io/cairo-book/ch103-02-03-testing-components.html +- https://www.starknet.io/cairo-book/ch103-03-upgradeability.html +- https://www.starknet.io/cairo-book/appendix-05-common-error-messages.html +- https://www.starknet.io/cairo-book/ch101-02-contract-functions.html +- https://www.starknet.io/cairo-book/ch104-01-general-recommendations.html -Its role in STARK proofs includes: +--- -1. **Public Commitment**: Values written to `output_ptr` are committed in the public memory as part of the proof. -2. **Proof Structure**: The output segment is included in the public input of a trace, with its boundaries tracked for verification. -3. **Verification Process**: Verifiers extract and hash the output segment to create a commitment, allowing verification without re-execution. +## Plugin Diagnostics for Component Integration -### Segment Arena Builtin +Cairo compiler provides helpful diagnostics when integrating components: -The Segment Arena builtin manages dynamic memory segments, such as those used for dictionaries. It tracks segment allocations, their sizes, and ensures rules like each segment being allocated and finalized exactly once are followed. Snapshots of the Segment Arena show how segments are allocated and potentially how errors occur due to invalid states or inconsistent tracking of segments and squashing operations. +* `Plugin diagnostic: name is not a substorage member in the contract's Storage. Consider adding to Storage: (...)`: Indicates that the component's storage was forgotten in the contract's `Storage`. Solution: Add the component's storage path annotated with `#[substorage(v0)]`. +* `Plugin diagnostic: name is not a nested event in the contract's Event enum. Consider adding to the Event enum:`: Suggests missing component events. Solution: Add the path to the component's events in the contract's `Event` enum. -Key validation rules for the Segment Arena include: +## Component Impl Structure and State Access -- Each segment must be allocated and finalized exactly once. -- All cell values must be valid field elements. -- Segment sizes must be non-negative. -- Squashing operations must maintain sequential order. -- Info segment entries must correspond to segment allocations. +Components use generic `impl` blocks marked with `#[embeddable_as()]` to define logic that can be embedded. -Cairo Builtins: Cryptographic and Arithmetic Operations +### The `#[embeddable_as]` Attribute -### Cairo Builtins: Cryptographic and Arithmetic Operations +This attribute marks an impl as embeddable and specifies the name that will be used in the contract to refer to this component's exposed logic. For example, `#[embeddable_as(OwnableImpl)]` results in the component being referred to as `OwnableImpl`. -The Cairo Virtual Machine (VM) provides several built-in components that facilitate cryptographic and arithmetic operations, essential for smart contract development and ZK-proofs. These built-ins are optimized for efficiency and security within the Cairo execution environment. +### Genericity and State Access -#### Keccak Builtin +The implementation is typically generic over `ComponentState`, restricted by traits like `+HasComponent`. -The Keccak builtin computes the Keccak-256 hash of a given input. +Access to storage and events within the component implementation is done via the generic `ComponentState` type, using syntax like `self.storage_var_name.read()` or `self.emit(...)`, instead of the contract's `ContractState`. -##### Syscall Signature +## Contract Integration Steps -```cairo,noplayground -pub extern fn keccak_syscall( - input: Span, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; -``` - -##### Description - -Computes the Keccak-256 hash of a given input. - -##### Arguments - -- `input`: A `Span` representing the Keccak-256 input. - -##### Return Values - -- `SyscallResult`: The computed hash result. - -##### Deduction Property +Integrating a component involves several steps within the host contract: -This builtin utilizes a block of 8 input cells and 8 output cells. The Keccak-f1600 permutation is computed on the entire state when any of the output cells are read. This computation happens only once per block and its result is cached. +1. **Declare Component:** Use the `component!(path: ComponentName, storage: storage_name, event: event_name);` macro. +2. **Define Storage:** Declare the component's storage in the contract's `Storage` struct, annotated with `#[substorage(v0)]`. +3. **Define Events:** Include the component's events in the contract's `Event` enum. +4. **Embed Logic:** Instantiate the component's generic impl with the concrete `ContractState` using an impl alias, annotated with `#[abi(embed_v0)]` to expose public functions. -##### Error Conditions +Internal methods meant only for internal use should be implemented in a separate impl block without `#[abi(embed_v0)]`. -- If any input cell contains a value exceeding 200 bits (≥ 2^200). -- If any input cell contains a relocatable value (pointer) instead of a field element. -- If an output cell is read before all eight input cells have been initialized. +### Example: Embedding the Ownable Component -#### ECDSA Builtin +The following example demonstrates embedding the `Ownable` component into an `OwnableCounter` contract: -The ECDSA (Elliptic Curve Digital Signature Algorithm) builtin verifies cryptographic signatures on the STARK curve. It is primarily used to validate that a message hash was signed by the holder of a specific private key. - -##### Memory Organization - -The ECDSA builtin has a dedicated memory segment for storing public keys and message hashes as field elements, alongside a Signature Dictionary that maps public key offsets to their corresponding signatures. - -##### Cell Layout in the Memory Segment - -The ECDSA segment arranges cells in pairs: - -- **Even offsets** (0, 2, 4, ...) store public keys. -- **Odd offsets** (1, 3, 5, ...) store message hashes. - Each public key at offset `2n` is associated with a message hash at offset `2n+1`. +```cairo +#[starknet::contract] +mod OwnableCounter { + use listing_01_ownable::component::OwnableComponent; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; -##### Signature Verification Process + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); -Before using the ECDSA builtin, signatures must be explicitly registered in the signature dictionary. The VM performs signature verification when the program writes values to the ECDSA segment: + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; -1. When a public key is written at offset `2n`, the VM checks if it matches the key used to create the signature registered at that offset. -2. When a message hash is written at offset `2n+1`, the VM verifies that it matches the hash that was signed. - If either check fails, the VM throws an error immediately. + impl OwnableInternalImpl = OwnableComponent::InternalImpl; -##### Error Conditions + #[storage] + struct Storage { + counter: u128, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } -- **Invalid Hash Error**: Occurs when a program writes a hash that does not match the hash that was signed with the corresponding public key. -- **Invalid Public Key Error**: Occurs when a program writes a public key that does not match the public key used to create the signature. + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + OwnableEvent: OwnableComponent::Event, + } -#### Poseidon Builtin + #[abi(embed_v0)] + fn foo(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.counter.write(self.counter.read() + 1); + } +} +``` -The Poseidon builtin computes cryptographic hashes using the Poseidon hash function, which is specifically optimized for zero-knowledge proofs and efficient computation in algebraic circuits. It uses the Hades permutation strategy. +This integration makes the component's logic seamlessly available in `OwnableCounter`. Functions exposed via `#[abi(embed_v0)]` can be called externally using the component's interface dispatcher. -##### Cells Organization +--- -The Poseidon builtin operates with a dedicated memory segment and follows a deduction property pattern with 6 consecutive cells: +Sources: -- **Input cells [0-2]**: Store input state for the Hades permutation. -- **Output cells [3-5]**: Store computed permutation results. +- https://www.starknet.io/cairo-book/ch103-05-02-randomness.html +- https://www.starknet.io/cairo-book/ch12-11-offloading-computations-with-oracles.html +- https://www.starknet.io/cairo-book/ch103-05-01-price-feeds.html +- https://www.starknet.io/cairo-book/ch103-05-oracle-interactions.html -##### How it Works +--- -Each operation works with a block of 3 inputs followed by 3 outputs. When a program reads any output cell, the VM applies the Hades permutation to the input cells and populates all three output cells with the results. +# Cairo Contracts Integrations -##### Single Value Hashing Example +### Oracles: Integrating External Data and Randomness -For hashing a single value (e.g., 42): +Oracles act as intermediaries to securely transmit external data (like asset prices) or offload computations to the Starknet blockchain. Pragma is presented as an example oracle service supporting both price feeds and verifiable random functions (VRFs). -1. The program writes the value to the first input cell (position 3:0). -2. The other input cells remain at their default value (0). -3. When reading the output cell (3:3), the VM: - - Takes the initial state (42, 0, 0). - - Applies padding: (42+1, 0, 0) = (43, 0, 0). - - Computes the Hades permutation. - - Stores the result in output cell 3:3. +#### Price Feeds -##### Sequence Hashing Example +Price feeds provide real-time pricing data aggregated from trusted external sources. To integrate Pragma for price feeds: -For hashing a sequence of values (e.g., 73, 91): +1. **Dependency**: Add `pragma_lib` to `Scarb.toml`: -1. The program writes values to the first two input cells (positions 3:6 and 3:7). -2. Upon reading any output cell, the VM: - - Takes the state (73, 91, 0). - - Applies appropriate padding: (73, 91+1, 0). - - Computes the Hades permutation. - - Stores all three results in the output cells (3:9, 3:10, 3:11). +```toml +[dependencies] +pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } +``` -##### Error Condition +2. **Interface Definition**: Define an interface including the necessary view function, such as `get_asset_price`: -The Poseidon builtin can throw an error if a program attempts to write a relocatable value (pointer) to an input cell. Input validation occurs at the time the output is read, consistent with the deduction property pattern. +```cairo +#[starknet::interface] +pub trait IPriceFeedExample { + fn buy_item(ref self: TContractState); + fn get_asset_price(self: @TContractState, asset_id: felt252) -> u128; +} +``` -#### Mod Builtin (AddMod, MulMod) +3. **Implementation**: The `get_asset_price` function interacts with the oracle: -The Mod Builtin handles modular arithmetic operations—specifically addition and multiplication—on field elements within a given modulus `p`. It comes in two derivations: `AddModBuiltin` for addition and `MulModBuiltin` for multiplication. +```cairo + fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 { + // Retrieve the oracle dispatcher + let oracle_dispatcher = IPragmaABIDispatcher { + contract_address: self.pragma_contract.read(), + }; -##### Under the Hood + // Call the Oracle contract, for a spot entry + let output: PragmaPricesResponse = oracle_dispatcher + .get_data_median(DataType::SpotEntry(asset_id)); -- **Word Size**: Numbers are broken into 96-bit chunks, aligning with the `range_check96` system. A `UInt384` typically uses four 96-bit words. -- **`AddMod`**: Computes `c ≡ a + b (mod p)`. The quotient `k` (number of times `p` is subtracted to wrap the result) is limited to 2. It can solve for missing operands (e.g., find `a` given `b`, `c`, and `p`) by testing `k=0` or `k=1`. -- **`MulMod`**: Computes `c ≡ a * b (mod p)`. It uses the extended GCD algorithm for deduction. Multiplication can produce larger intermediate values, so it has higher default quotient bounds compared to `AddMod`. + return output.price; + } +``` -##### Error Conditions +Pragma may return values with decimal factors of 6 or 8, requiring conversion by dividing by \({10^{n}}\). -- **`MissingOperand`**: If an operand is missing when required for computation. -- **`ZeroDivisor`**: If `b` and `p` are not coprime for `MulMod`, as this prevents a unique solution. -- **Range Check Failure**: If any 96-bit word of an operand exceeds `2^96`. +An example application consuming this feed (`PriceFeedExample`) calculates required ETH based on the retrieved price: -#### Pedersen Builtin +```cairo +#[starknet::contract] +mod PriceFeedExample { + use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait}; + use pragma_lib::types::{DataType, PragmaPricesResponse}; + use starknet::contract_address::contract_address_const; + use starknet::get_caller_address; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use super::{ContractAddress, IPriceFeedExample}; -The Pedersen builtin is dedicated to computing the Pedersen hash of two field elements (felts). + const ETH_USD: felt252 = 19514442401534788; + const EIGHT_DECIMAL_FACTOR: u256 = 100000000; -##### Cells Organization + #[storage] + struct Storage { + pragma_contract: ContractAddress, + product_price_in_usd: u256, + } -The Pedersen builtin has its own dedicated memory segment and is organized in triplets of cells: + #[constructor] + fn constructor(ref self: ContractState, pragma_contract: ContractAddress) { + self.pragma_contract.write(pragma_contract); + self.product_price_in_usd.write(100); + } -- **Input cells**: Must store field elements (felts); relocatable values (pointers) are forbidden. -- **Output cell**: The value is deduced from the input cells. When an instruction attempts to read the output cell, the VM computes the Pedersen hash of the two input cells and writes the result to the output cell. + #[abi(embed_v0)] + impl PriceFeedExampleImpl of IPriceFeedExample { + fn buy_item(ref self: ContractState) { + let caller_address = get_caller_address(); + let eth_price = self.get_asset_price(ETH_USD).into(); + let product_price = self.product_price_in_usd.read(); + + // Calculate the amount of ETH needed + let eth_needed = product_price * EIGHT_DECIMAL_FACTOR / eth_price; + + let eth_dispatcher = ERC20ABIDispatcher { + contract_address: contract_address_const::< + 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7, + >() // ETH Contract Address + }; + + // Transfer the ETH to the caller + eth_dispatcher + .transfer_from( + caller_address, + contract_address_const::< + 0x0237726d12d3c7581156e141c1b132f2db9acf788296a0e6e4e9d0ef27d092a2, + >(), + eth_needed, + ); + } -##### Error Conditions + fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 { + // Retrieve the oracle dispatcher + let oracle_dispatcher = IPragmaABIDispatcher { + contract_address: self.pragma_contract.read(), + }; -- An output cell is read before all input cells have been initialized. -- An input cell contains a relocatable value (pointer) instead of a field element. + // Call the Oracle contract, for a spot entry + let output: PragmaPricesResponse = oracle_dispatcher + .get_data_median(DataType::SpotEntry(asset_id)); -#### Other Builtins + return output.price; + } + } +} +``` -The Cairo VM implements a variety of other built-ins, each serving specific purposes in computation and proof generation. The following table lists them: +#### Verifiable Random Functions (VRFs) -| Builtin | Description | -| --------------- | ------------------------------------------------------------------------------------------------------------------------ | -| [Output] | Stores all the public memory needed to generate a STARK proof (input & output values, builtin pointers...). | -| [Range Check] | Verify that a felt `x` is within the bounds `[0, 2**128)`. | -| [Bitwise] | Computes the bitwise AND, XOR and OR of two felts `a` and `b`. `a & b`, `a ^ b` and `a \| b`. | -| [EC OP] | Performs Elliptic Curve OPerations - For two points on the STARK curve `P`, `Q` and a scalar `m`, computes `R = P + mQ`. | -| [Range Check96] | Verify that a felt `x` is within the bounds `[0, 2**96)`. | -| [Segment Arena] | Manages the Cairo dictionaries. Not used in Cairo Zero. | -| [Gas] | Manages the available gas during the run. Used by Starknet to handle its gas usage and avoid DoS. | -| [System] | Manages the Starknet syscalls & cheatcodes. | +Since blockchains are deterministic, generating truly unpredictable randomness requires Verifiable Random Functions (VRFs) provided by oracles. VRFs use a secret key and a nonce to generate a random number and a proof, ensuring the result cannot be predicted or tampered with. -[output]: ch204-02-00-output.md -[pedersen]: ch204-02-01-pedersen.md -[rc]: ch204-02-02-range-check.md -[ecdsa]: ch204-02-03-ecdsa.md -[bitwise]: ch204-02-04-bitwise.md -[ec_op]: ch204-02-05-ec-op.md -[keccak]: ch204-02-06-keccak.md -[poseidon]: ch204-02-07-poseidon.md -[rc96]: ch204-02-08-range-check-96.md -[seg_are]: ch204-02-09-segment-arena.md -[add_mod]: ch204-02-10-add-mod.md -[mul_mod]: ch204-02-11-mul-mod.md -[gas]: ch204-02-12-gas.md -[system]: ch204-02-13-system.md +Pragma provides VRF solutions on Starknet. The process involves: -Cairo Builtins: Specialized Functions +1. **Requesting Randomness**: Initiated via `request_randomness_from_pragma` which emits an event triggering off-chain generation and on-chain submission via a callback. +2. **Callback**: The oracle submits results via the `receive_random_words` function. -# Cairo Builtins: Specialized Functions +Key inputs for `request_randomness_from_pragma` include: -Builtins in Cairo are analogous to Ethereum precompiles, offering primitive operations implemented in the client's language rather than relying solely on VM opcodes. The Cairo architecture is flexible, allowing builtins to be added or removed as needed, leading to different VM layouts. Adding builtins introduces constraints to the CPU AIR, which can increase verification time. This chapter details how builtins function, the existing builtins, and their purposes. +* `seed`: Unique value to initialize generation. +* `callback_address`: Address of the contract implementing `receive_random_words`. +* `callback_fee_limit`: Max gas willing to spend on the callback execution. +* `publish_delay`: Minimum block delay before fulfillment. +* `num_words`: Number of random `felt252` values requested. +* `calldata`: Additional data passed to the callback. -## How Builtins Work +**Dice Game Example using Pragma VRF** -A builtin enforces specific constraints on Cairo memory to perform specialized tasks, such as hash computations. Each builtin operates on a dedicated memory segment, which maps to a fixed address range. This interaction method is known as "memory-mapped I/O," where specific memory address ranges are dedicated to builtins. Cairo programs interact with builtins by reading from or writing to these designated memory cells. +This contract uses VRF to determine game winners. It requires defining interfaces for both the game logic (`IDiceGame`) and the oracle (`IPragmaVRF`). -Builtin constraints can be categorized into two types: "validation property" and "deduction property." Builtins with a deduction property are typically divided into blocks of cells, where some cells are constrained by a validation property. If a defined property is not met, the Cairo VM will halt execution. +Key entrypoints in `IPragmaVRF`: -### Validation Property +* `request_randomness_from_pragma`: Owner calls this to initiate the request. +* `receive_random_words`: Called by the oracle to submit the results, which are stored in `last_random_number`. -A validation property defines the constraints a value must satisfy before it can be written to a builtin's memory cell. For instance, the Range Check builtin only accepts field elements (felts) and verifies that they fall within the range `[0, 2**128)`. A program can write to the Range Check builtin only if these constraints hold true. +The full contract implementation (`DiceGame`) demonstrates handling the request, approval of callback fees, and processing the result in `process_game_winners`: -The Range Check builtin validates values immediately upon writing to a cell, enabling early detection of out-of-range values. +```cairo +#[starknet::contract] +mod DiceGame { + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use pragma_lib::abi::{IRandomnessDispatcher, IRandomnessDispatcherTrait}; + use starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + }; + use starknet::{ + ContractAddress, contract_address_const, + get_block_number, get_caller_address, + get_contract_address, + }; -#### Valid Operation Example + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); -In this example, three values are successfully written to the Range Check segment: `0`, `256`, and `2^128-1`. All these values are within the permitted range `[0, 2^128-1]`. + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl InternalImpl = OwnableComponent::InternalImpl; -![Range Check builtin segment with valid values](range-check-builtin-valid.png) + #[storage] + struct Storage { + user_guesses: Map, + pragma_vrf_contract_address: ContractAddress, + game_window: bool, + min_block_number_storage: u64, + last_random_number: felt252, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } -#### Out-of-Range Error Example + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + GameWinner: ResultAnnouncement, + GameLost: ResultAnnouncement, + #[flat] + OwnableEvent: OwnableComponent::Event, + } -This example shows an attempt to write `2^128` to cell `2:2`, exceeding the maximum allowed value. The VM immediately throws an out-of-range error. + #[derive(Drop, starknet::Event)] + struct ResultAnnouncement { + caller: ContractAddress, + guess: u8, + random_number: u256, + } -![Range Check error: Value exceeds maximum range](range-check-builtin-error1.png) + #[constructor] + fn constructor( + ref self: ContractState, + pragma_vrf_contract_address: ContractAddress, + owner: ContractAddress, + ) { + self.ownable.initializer(owner); + self.pragma_vrf_contract_address.write(pragma_vrf_contract_address); + self.game_window.write(true); + } -#### Invalid Type Error Example + #[abi(embed_v0)] + impl DiceGame of super::IDiceGame { + fn guess(ref self: ContractState, guess: u8) { + assert!(self.game_window.read(), "GAME_INACTIVE"); + assert!(guess >= 1 && guess <= 6, "INVALID_GUESS"); -Here, a relocatable address (pointer to cell `1:7`) is written to the Range Check segment. Since the builtin only accepts field elements, the VM throws an error. + let caller = get_caller_address(); + self.user_guesses.entry(caller).write(guess); + } -![Range Check error: Value is a relocatable address](range-check-builtin-error2.png) + fn toggle_play_window(ref self: ContractState) { + self.ownable.assert_only_owner(); -## Bitwise Builtin + let current: bool = self.game_window.read(); + self.game_window.write(!current); + } -The Bitwise Builtin facilitates bitwise operations—AND (`&`), XOR (`^`), and OR (`|`)—on field elements. It supports tasks requiring bit-level manipulation. + fn get_game_window(self: @ContractState) -> bool { + self.game_window.read() + } -### How It Works + fn process_game_winners(ref self: ContractState) { + assert!(!self.game_window.read(), "GAME_ACTIVE"); + assert!(self.last_random_number.read() != 0, "NO_RANDOM_NUMBER_YET"); + + let caller = get_caller_address(); + let user_guess: u8 = self.user_guesses.entry(caller).read(); + let reduced_random_number: u256 = self.last_random_number.read().into() % 6 + 1; + + if user_guess == reduced_random_number.try_into().unwrap() { + self + .emit( + Event::GameWinner( + ResultAnnouncement { + caller: caller, + guess: user_guess, + random_number: reduced_random_number, + }, + ), + ); + } else { + self + .emit( + Event::GameLost( + ResultAnnouncement { + caller: caller, + guess: user_guess, + random_number: reduced_random_number, + }, + ), + ); + } + } + } -The Bitwise builtin uses a dedicated memory segment. Each operation involves a block of 5 cells: + #[abi(embed_v0)] + impl PragmaVRFOracle of super::IPragmaVRF { + fn get_last_random_number(self: @ContractState) -> felt252 { + let last_random = self.last_random_number.read(); + last_random + } -| Offset | Description | Role | -| ------ | ------------- | ------ | -| 0 | x value | Input | -| 1 | y value | Input | -| 2 | x & y result | Output | -| 3 | x ^ y result | Output | -| 4 | x \| y result | Output | + fn request_randomness_from_pragma( + ref self: ContractState, + seed: u64, + callback_address: ContractAddress, + callback_fee_limit: u128, + publish_delay: u64, + num_words: u64, + calldata: Array, + ) { + self.ownable.assert_only_owner(); -For example, if `x = 5` (binary `101`) and `y = 3` (binary `011`): + let randomness_contract_address = self.pragma_vrf_contract_address.read(); + let randomness_dispatcher = IRandomnessDispatcher { + contract_address: randomness_contract_address, + }; + + // Approve the randomness contract to transfer the callback fee + // You would need to send some ETH to this contract first to cover the fees + let eth_dispatcher = ERC20ABIDispatcher { + contract_address: contract_address_const::<\n 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7,\n >() // ETH Contract Address + }; + eth_dispatcher + .approve( + randomness_contract_address, + (callback_fee_limit + callback_fee_limit / 5).into(), + ); -- `5 & 3 = 1` (binary `001`) -- `5 ^ 3 = 6` (binary `110`) -- `5 | 3 = 7` (binary `111`) + // Request the randomness + randomness_dispatcher + .request_random( + seed, callback_address, callback_fee_limit, publish_delay, num_words, calldata, + ); -### Example Usage + let current_block_number = get_block_number(); + self.min_block_number_storage.write(current_block_number + publish_delay); + } -This Cairo function demonstrates the use of the Bitwise Builtin: + fn receive_random_words( + ref self: ContractState, + requester_address: ContractAddress, + request_id: u64, + random_words: Span, + calldata: Array, + ) { + // Have to make sure that the caller is the Pragma Randomness Oracle contract + let caller_address = get_caller_address(); + assert!( + caller_address == self.pragma_vrf_contract_address.read(), + "caller not randomness contract", + ); + // and that the current block is within publish_delay of the request block + let current_block_number = get_block_number(); + let min_block_number = self.min_block_number_storage.read(); + assert!(min_block_number <= current_block_number, "block number issue"); -```cairo -from starkware.cairo.common.cairo_builtins import BitwiseBuiltin + let random_word = *random_words.at(0); + self.last_random_number.write(random_word); + } -func bitwise_ops{bitwise_ptr: BitwiseBuiltin*}(x: felt, y: felt) -> (and: felt, xor: felt, or: felt) { - assert [bitwise_ptr] = x; // Input x - assert [bitwise_ptr + 1] = y; // Input y - let and = [bitwise_ptr + 2]; // x & y - let xor = [bitwise_ptr + 3]; // x ^ y - let or = [bitwise_ptr + 4]; // x | y - let bitwise_ptr = bitwise_ptr + 5; - return (and, xor, or); + fn withdraw_extra_fee_fund(ref self: ContractState, receiver: ContractAddress) { + self.ownable.assert_only_owner(); + let eth_dispatcher = ERC20ABIDispatcher { + contract_address: contract_address_const::<\n 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7,\n >() // ETH Contract Address + }; + let balance = eth_dispatcher.balance_of(get_contract_address()); + eth_dispatcher.transfer(receiver, balance); + } + } } ``` -## EC OP Builtin - -The EC OP (Elliptic Curve Operation) builtin performs elliptic curve operations on the STARK curve, specifically computing `R = P + mQ`, where `P` and `Q` are points on the curve and `m` is a scalar. Each point is represented by its x and y coordinates as a pair of field elements. +**Funding Requirement**: Contracts utilizing Pragma VRF must be funded with sufficient ETH post-deployment to cover both the randomness generation and the callback execution fees. -### Cells Organization +### External Computation and Off-Chain Interaction -The EC OP builtin uses a dedicated memory segment. Each operation is defined by a block of 7 cells: +--- -| Offset | Description | Role | -| ------ | -------------- | ------ | -| 0 | P.x coordinate | Input | -| 1 | P.y coordinate | Input | -| 2 | Q.x coordinate | Input | -| 3 | Q.y coordinate | Input | -| 4 | m scalar value | Input | -| 5 | R.x coordinate | Output | -| 6 | R.y coordinate | Output | +Sources: -The first five cells are inputs provided by the program, and the last two cells are outputs computed by the VM upon reading. +- https://www.starknet.io/cairo-book/ch103-04-L1-L2-messaging.html -#### Valid Operation Example +--- -This example shows a correctly configured EC OP builtin operation with all input values set. +## System-Level Patterns and Messaging +### Local Messaging System Testing +You can also find a detailed guide here to test the messaging system locally. -![EC OP builtin segment with complete input values](ecop-segment.png) +--- -When the program reads cells at offsets 5 and 6, the VM computes `R = P + mQ` and returns the resulting coordinates. +Sources: -#### Error Condition Example +- https://www.starknet.io/cairo-book/ch10-01-how-to-write-tests.html +- https://www.starknet.io/cairo-book/ch104-02-testing-smart-contracts.html +- https://www.starknet.io/cairo-book/ch09-02-recoverable-errors.html +- https://www.starknet.io/cairo-book/ch10-02-test-organization.html +- https://www.starknet.io/cairo-book/ch104-01-general-recommendations.html +- https://www.starknet.io/cairo-book/ch09-01-unrecoverable-errors-with-panic.html +- https://www.starknet.io/cairo-book/appendix-05-common-error-messages.html +- https://www.starknet.io/cairo-book/ch103-02-03-testing-components.html +- https://www.starknet.io/cairo-book/ch01-03-proving-a-prime-number.html +- https://www.starknet.io/cairo-book/ch09-00-error-handling.html +- https://www.starknet.io/cairo-book/ch10-00-testing-cairo-programs.html +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html +- https://www.starknet.io/cairo-book/ch104-00-starknet-smart-contracts-security.html +- https://www.starknet.io/cairo-book/ch104-03-static-analysis-tools.html -This example illustrates an error condition where the program attempts to read the output cells with incomplete inputs. +--- -![Incomplete input values in EC OP builtin segment](ecop-invalid-inputs.png) +--- -The VM cannot compute `R = P + mQ` because the coordinates for point `Q` are missing. The EC OP builtin fails if any input value is invalid or missing. All five input cells must contain valid field elements before the output cells are accessed. +Sources: -## Keccak Builtin +- https://www.starknet.io/cairo-book/ch09-02-recoverable-errors.html +- https://www.starknet.io/cairo-book/ch09-01-unrecoverable-errors-with-panic.html +- https://www.starknet.io/cairo-book/appendix-05-common-error-messages.html +- https://www.starknet.io/cairo-book/ch01-03-proving-a-prime-number.html +- https://www.starknet.io/cairo-book/ch09-00-error-handling.html +- https://www.starknet.io/cairo-book/ch10-01-how-to-write-tests.html +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html +- https://www.starknet.io/cairo-book/ch104-01-general-recommendations.html -The Keccak builtin implements the core functionality of the SHA-3 hash functions, specifically the keccak-f1600 permutation. This is crucial for Ethereum compatibility, as Keccak-256 is used in various cryptographic operations. +--- -### Cells Organization +#### Error Handling and Program Robustness -The Keccak builtin uses a dedicated memory segment organized in blocks of 16 cells: +In Cairo, error handling involves techniques to manage potential issues, ensuring programs are adaptable and maintainable. Approaches include pattern matching with `Result`, using the `?` operator for ergonomic propagation, and using `unwrap` or `expect` for handling recoverable errors. -| Cell Range | Purpose | Description | -| ------------- | ----------------- | ------------------------------------------------------ | -| First 8 cells | Input state `s` | Each cell stores 200 bits of the 1600-bit input state | -| Next 8 cells | Output state `s'` | Each cell stores 200 bits of the 1600-bit output state | - -The builtin processes each block independently with the following rules: - -1. **Input validation**: Each input cell must hold a valid field element (0 ≤ value < 2^200). -2. **Lazy computation**: The output state is computed only when an output cell is accessed. -3. **Caching**: Computed results are cached to avoid redundant calculations. - -#### Example Operation +## Unrecoverable Errors with `panic` -![Keccak builtin segment with a complete operation](keccak-segment.png) +Unexpected issues can cause runtime errors, leading to program termination via `panic`. A panic can occur inadvertently (e.g., array out of bounds) or deliberately by invoking the `panic` function. When a panic occurs, it terminates execution, drops variables, and squashes dictionaries to ensure program soundness. -## Mod Builtin +### Calling `panic` -The Mod builtin is designed for modular arithmetic operations, specifically computing `a % b` and `a // b` (integer division). It leverages the Range Check builtin to validate values and ensure they fall within specific bounds, typically `[0, 2**96)`. +The `panic` function takes an array as an argument: -### How It Works +```cairo +#[executable] +fn main() { + let mut data = array![2]; -The Mod builtin operates on a dedicated memory segment. For each modular arithmetic operation, it uses a block of 3 cells: + if true { + panic(data); + } + println!("This line isn't reached"); +} +``` -| Offset | Description | Role | -| ------ | ----------- | ------ | -| 0 | `a` value | Input | -| 1 | `b` value | Input | -| 2 | `a % b` | Output | +Executing this produces output like: +``` +$ scarb execute + Compiling no_listing_01_panic v0.1.0 (listings/ch09-error-handling/no_listing_01_panic/Scarb.toml) + Finished `dev` profile target(s) in 1 second + Executing no_listing_01_panic +error: Panicked with 0x2. +``` -The VM computes `a % b` and `a // b` when the output cell is accessed. The values `a` and `b` are validated against the `range_check96_ptr` to ensure they are within the `[0, 2**96)` range. +### Alternatives to `panic` -Another design choice is the batch size, which is often just 1 in practice. The builtin can process multiple operations at once—`batch_size` triplets of `a`, `b`, and `c`—but keeping it at 1 simplifies things for most cases. It’s like handling one addition or multiplication at a time, which is plenty for many programs, though the option to scale up is there if you need it. +1. **`panic_with_felt252`**: A more idiomatic one-liner abstraction that takes a `felt252` error message: -Why tie the values table to `range_check96_ptr`? It’s about efficiency again. The VM’s range-checking system is already set up to monitor that segment, so using it for the builtin’s values—like `a`, `b`, and `c`—means those numbers get validated automatically. + ```cairo + use core::panic_with_felt252; -## Segment Arena Builtin + #[executable] + fn main() { + panic_with_felt252(2); + } + ``` -The Segment Arena builtin enhances Cairo VM's memory management by tracking segment endpoints, simplifying memory operations involving segment allocation and finalization. +2. **`panic!` Macro**: More convenient than `panic`, as it takes a string literal, allowing error messages longer than 31 bytes: -### Cells Organization + ```cairo + #[executable] + fn main() { + if true { + panic!("2"); + } + println!("This line isn't reached"); + } + ``` -Each Segment Arena builtin instance manages state using blocks of 3 cells: +### The `nopanic` Notation -- First cell: Base address of the info pointer. -- Second cell: Current number of allocated segments. -- Third cell: Current number of finalized segments. +The `nopanic` notation indicates a function will never panic, allowing it to be called only from other `nopanic` functions. -This structure works in conjunction with an Info segment, also organized in blocks of 3 cells: +```cairo +fn function_never_panic() -> felt252 nopanic { + 42 +} +``` -- First cell: Base address of the segment. -- Second cell: End address of the segment (when finalized). -- Third cell: Current number of finalized segments (squashing index). +If a function declared as `nopanic` calls a function that may panic (like `assert!`), compilation will fail: +``` +error: Function is declared as nopanic but calls a function that may panic. +``` -![Segment Arena builtin segment](segment-arena.png) +### `panic_with` Attribute -Cairo Compilation: Sierra and Casm +This attribute marks a function returning an `Option` or `Result`. It creates a wrapper that panics with a specified error reason if the original function returns `None` or `Err`. -# Cairo Compilation: Sierra and Casm +```cairo +#[panic_with('value is 0', wrap_not_zero)] +fn wrap_if_not_zero(value: u128) -> Option { + if value == 0 { + None + } else { + Some(value) + } +} -## Sierra and Casm +#[executable] +fn main() { + wrap_if_not_zero(0); // this returns None + wrap_not_zero(0); // this panics with 'value is 0' +} +``` -Sierra (Safe Intermediate Representation) is an intermediate representation used in Starknet contracts since version v0.11.0. After compilation from Cairo, contracts are in Sierra format. This Sierra code is then compiled by the sequencer into Cairo Assembly (Casm), which is executed by the Starknet OS. +## Recoverable Errors with `Result` and Propagation -## Why Casm is Needed +Most errors are recoverable, allowing functions to return an error value instead of terminating. -Starknet, as a validity rollup, requires proofs for block execution using STARKs. STARKs work with polynomial constraints, necessitating a translation layer from smart contract execution to these constraints. Cairo, and its assembly language Casm, provide this layer by translating Cairo semantics into polynomial constraints, enabling the proof of block validity. +### The `Result` Enum -## Safe Casm +The `Result` enum signifies success or failure: -To ensure provability, Sierra is compiled into a subset of Casm called "safe Casm." Safe Casm guarantees provable execution for all inputs. This is achieved by avoiding constructs like `assert` in favor of `if/else` to ensure graceful failures and deterministic execution. For example, a `find_element` function that might fail if the element is not found cannot be directly compiled to safe Casm. +```cairo +enum Result { + Ok: T, + Err: E, +} +``` -## Compilation of Loops and Recursion +### `ResultTrait` Methods -In Cairo, loops and recursion are compiled into similar low-level representations. Compiling examples to Sierra reveals that loops are often translated into recursive functions within the Sierra statements. To observe this, one can enable `sierra-text = true` in `Scarb.toml` and run `scarb build`. +The `ResultTrait` provides methods for working with `Result`: -## Sierra Code Structure +| Method | Behavior on `Ok(x)` | Behavior on `Err(e)` | +| :--- | :--- | :--- | +| `unwrap()` | Returns `x` (panics with default message otherwise) | Panics | +| `expect(err)` | Returns `x` | Panics with custom message `err` | +| `unwrap_err()` | Panics | Returns `e` | +| `expect_err(err)` | Panics with custom message `err` | Returns `e` | +| `is_ok()` | Returns `true` | Returns `false` | +| `is_err()` | Returns `false` | Returns `true` | -Sierra files are structured into three main parts: +These methods require generic type constraints, such as `<+Drop>` for `unwrap`. -1. **Type and libfunc declarations:** Definitions of data types and library functions used. -2. **Statements:** The sequence of operations forming the program. -3. **Function declarations:** Mapping of function definitions to their corresponding statements. +### Propagating Errors with the `?` Operator -The statements in Sierra code correspond to the order of function declarations in the Cairo program. For instance, the `main` function's statements are located between specific line numbers, followed by the statements for other functions. +Error propagation allows a function to return an error to its caller instead of handling it internally. This is commonly done using the `?` operator, which simplifies error handling boilerplate. -### Example: Inlining in Sierra +If a `Result` value has `Ok(x)`, `?` unwraps `x` and continues execution. If it has `Err(e)`, the `?` operator returns `Err(e)` early from the entire function, propagating the error to the caller. -Consider a program with inlined and non-inlined functions. The Sierra code shows how function calls are represented. For example, `function_call()` is used to execute a non-inlined function. The execution then proceeds through `felt252_const`, `store_temp`, and `felt252_add` libfuncs. Inlined code might use different variable IDs due to prior assignments. The `return` instruction of called functions is often omitted in favor of integrating their results into the calling function's logic. +The `?` operator can only be used in functions whose return type is compatible (e.g., returns `Result` or `Option`). -#### Casm Code Example +```cairo +// A hypothetical function that might fail +fn parse_u8(input: felt252) -> Result { + let input_u256: u256 = input.into(); + if input_u256 < 256 { + Result::Ok(input.try_into().unwrap()) + } else { + Result::Err('Invalid Integer') + } +} -Here's a corresponding Casm code snippet for the described program: +fn mutate_byte(input: felt252) -> Result { + let input_to_u8: u8 = parse_u8(input)?; + let res = input_to_u8 - 1; + Ok(res) +} +``` -```cairo,noplayground -1 call rel 3 -2 ret -3 call rel 9 -4 [ap + 0] = 1, ap++ -5 [ap + 0] = [ap + -1] + [ap + -2], ap++ -6 ret -7 [ap + 0] = 1, ap++ -8 ret -9 [ap + 0] = 2, ap++ -10 ret -11 ret +If used incorrectly (e.g., in a function returning `()`), it results in: +``` +error: `?` can only be used in a function with `Option` or `Result` return type. ``` -This Casm code involves instructions like `call rel`, `ret`, and memory operations (`[ap + offset] = value, ap++`). +## Common Error Messages -Security and Provability in Cairo +Encountering specific error messages can be resolved by checking the following: -### Security and Provability in Cairo +* **`Variable not dropped.`**: A variable of a non-`Drop`/non-`Destruct` type is going out of scope without being destroyed. +* **`Variable was previously moved.`**: Attempting to use a variable whose ownership was transferred (if it doesn't implement `Copy`). Use `.clone()` to avoid this. +* **`error: Trait has no implementation in context: core::fmt::Display...`**: Occurs when using `{}` for custom types in `print!`. Implement `Display` or use `derive(Debug)` and print with `{:?}`. +* **`Got an exception while executing a hint: Hint Error: Failed to deserialize param #x.`**: An entrypoint was called without expected arguments. For `u256` (a struct of two `u128`), two values must be passed. +* **`Item path::item is not visible in this context.`**: Visibility issue. Declare modules and items using `pub(crate)` or `pub`. +* **`Identifier not found.`**: May mean a variable is used before declaration (`let`) or the path to an item is incorrect. -Cairo 1.0's compiled Casm (Common Assembly) is designed to ensure provability, a key difference from Cairo 0 where certain execution paths might not be provable. Malicious provers can exploit non-provable paths to deceive users, for example, by falsely claiming an element is not in an array when it actually is. +## Robustness and Security Considerations -#### Provability and Malicious Provers +Certain programming patterns can lead to vulnerabilities if not handled carefully: -- **Happy Flow (Element Present):** The safe Casm verifies that the array at a given index contains the requested element. -- **Unhappy Flow (Element Absent):** In Cairo 0, this path was often not provable. In Cairo 1.0, the entire array must be traversed to verify the element's absence. +### Operator Precedence +`&&` has higher precedence than `||`. Parentheses must be used to enforce correct precedence in combined boolean expressions: -#### Gas Metering Complications +```cairo +// ❌ buggy: ctx.coll_ok and ctx.debt_ok are only required in Recovery +assert!( + mode == Mode::None || mode == Mode::Recovery && ctx.coll_ok && ctx.debt_ok, + "EMERGENCY_MODE" +); -Sierra's gas metering introduces further challenges. A prover might exploit situations where the user has enough gas for the "happy flow" but not the "unhappy flow" (element not found). This could allow the execution to halt mid-search, enabling the prover to falsely claim the element is absent. +// ✅ fixed +assert!( + (mode == Mode::None || mode == Mode::Recovery) && (ctx.coll_ok && ctx.debt_ok), + "EMERGENCY_MODE" +); +``` -To address this, the plan is to require users to have sufficient gas for the unhappy flow before initiating operations like `find_element`. +### Unsigned Loop Underflow +Using an unsigned integer (`u32`) for a loop counter that is decremented past zero causes an underflow panic. Use signed integers (`i32`) for counters that might go below zero: -#### Hints in Cairo +```cairo +// ✅ prefer signed counters or explicit break +let mut i: i32 = (n.try_into().unwrap()) - 1; +while i >= 0 { // This would never trigger if `i` was a u32. + // ... + i -= 1; +} +``` -Smart contracts written in Cairo for Starknet cannot include user-defined hints. While Cairo 0 allowed only whitelisted hints, Cairo 1.0's Sierra to Casm compilation process strictly determines the hints in use, ensuring only "safe" Casm is generated. This eliminates the possibility of non-compiler-generated hints. Future native Cairo versions might support hint syntax similar to Cairo 0, but this will not be available in Starknet smart contracts. L3s built on Starknet might utilize such functionality. +### Bit-Packing into `felt252` +Packing fields into a single `felt252` requires strict bounds checking. The sum of the sizes of packed values should not exceed 251 bits. -Cairo Runner and Proof Generation +```cairo +fn pack_order(book_id: u256, tick_u24: u256, index_u40: u256) -> felt252 { + // width checks + assert!(book_id < (1_u256 * POW_2_187), "BOOK_OVER"); + assert!(tick_u24 < (1_u256 * POW_2_24), "TICK_OVER"); + assert!(index_u40 < (1_u256 * POW_2_40), "INDEX_OVER"); -# Cairo Runner and Proof Generation + let packed: u256 = + (book_id * POW_2_64) + (tick_u24 * POW_2_40) + index_u40; + packed.try_into().expect("PACK_OVERFLOW") +} +``` -The Cairo Runner is the primary executable program responsible for orchestrating the execution of compiled Cairo programs. It implements the theoretical Cairo machine, integrating the memory model, execution model, builtins, and hints. Currently, it is implemented in Rust by LambdaClass and is available as both a standalone binary and a library. +--- -## Runner Modes +Sources: -The Cairo Runner can operate in different modes tailored to specific execution purposes, taking compiled Cairo bytecode and hints to produce an execution trace and memory, which then serve as inputs for the STARK prover. +- https://www.starknet.io/cairo-book/ch10-01-how-to-write-tests.html +- https://www.starknet.io/cairo-book/ch104-02-testing-smart-contracts.html +- https://www.starknet.io/cairo-book/ch10-02-test-organization.html +- https://www.starknet.io/cairo-book/ch103-02-03-testing-components.html +- https://www.starknet.io/cairo-book/ch10-00-testing-cairo-programs.html -### Execution Mode +--- -In this mode, the runner executes the program to completion, including hints and the Cairo VM's state transition function. This mode is primarily for debugging or testing program logic without the overhead of proof generation. It simulates the program step-by-step, using hints for nondeterministic values, and constructs the complete state trace and final memory. The output comprises the trace, memory, and initial/final register states (`pc`, `ap`, `fp`). Execution halts upon failure of any hint or instruction check. +## Cairo Testing Mechanics and Organization -### Proof Mode +Cairo includes support for writing tests to verify program correctness beyond what the type system can prove. Testing is complex, but Cairo provides specific annotations and macros to facilitate writing tests. -This mode extends Execution Mode by not only running the program but also preparing the necessary inputs for proof generation. It is the standard mode for production use cases where a proof of execution is required. As the runner executes the program, it logs the VM state at each step, building the execution trace and final memory state. Upon completion, the memory dump and sequential register states (composing the execution trace) can be extracted. +### Anatomy of a Test Function -Advanced Topics and Utilities +Tests are Cairo functions annotated with the `#[test]` attribute. A typical test function performs three actions: -Development Tools and Utilities +1. Set up any needed data or state. +2. Run the code you want to test. +3. Assert the results are what you expect. -# Development Tools and Utilities +When running tests with `scarb test`, Scarb executes Starknet Foundry's test runner binary against these annotated functions. -This section covers useful development tools provided by the Cairo project, including automatic formatting, quick warning fixes, a linter, and IDE integration. +Cairo features available for writing tests include: -## Automatic Formatting with `scarb fmt` +* `#[test]` attribute: Marks a function as a test. +* Assertion macros: `assert!`, `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!`. +* `#[should_panic]` attribute: Marks a test that is expected to panic. -Scarb projects can be formatted using the `scarb fmt` command. For direct Cairo binary usage, `cairo-format` can be used. This tool is often used in collaborative projects to maintain a consistent code style. +### The `tests` Module and Configuration -To format a Cairo project, navigate to the project directory and run: +To contain unit tests within the same file as the code they test, a module named `tests` is conventionally created and annotated with `#[cfg(test)]`. This attribute ensures the test code is compiled and run only when tests are executed (e.g., via `scarb test`), saving compile time and space in the final artifact. -```bash -scarb fmt -``` +```cairo +pub fn add(left: usize, right: usize) -> usize { + left + right +} -Code sections that should not be formatted can be marked with `#[cairofmt::skip]`: +#[cfg(test)] +mod tests { + use super::*; -```cairo, noplayground -#[cairofmt::skip] -let table: Array = array![ - "oxo", - "xox", - "oxo", -]; + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} ``` -## IDE Integration Using `cairo-language-server` - -The Cairo community recommends using `cairo-language-server` for IDE integration. This tool provides compiler-centric utilities and communicates using the Language Server Protocol (LSP). - -Cryptography and Zero-Knowledge Proofs - -# Cryptography and Zero-Knowledge Proofs - -## Hash Functions in Cairo - -Cairo's core library offers two hash functions: Pedersen and Poseidon. - -### Pedersen Hash +### Assertion Macros -Pedersen hash functions are based on elliptic curve cryptography. They perform operations on points along an elliptic curve, which are easy to compute in one direction but computationally infeasible to reverse due to the Elliptic Curve Discrete Logarithm Problem (ECDLP). +Tests fail when code inside the function panics. Assertions are used to check conditions: -### Poseidon Hash +* `assert!(condition, message)`: Fails if the condition evaluates to `false`. +* `assert_eq!(left, right)` and `assert_ne!(left, right)`: Compare two arguments for equality or inequality, printing the values upon failure. +* Comparison macros: `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!` check relative ordering. -Poseidon is a family of hash functions optimized for efficiency in algebraic circuits, making it suitable for Zero-Knowledge proof systems like STARKs (used in Cairo). It employs a sponge construction and a three-element state permutation. +When assertions fail, custom failure messages can be added as optional arguments to these macros, which are passed to the `format!` macro. -## Arithmetic Circuits in Zero-Knowledge Proof Systems +### Test Organization -Zero-knowledge proof systems allow a prover to demonstrate the validity of a computation to a verifier without revealing private inputs. Statements must be converted into a representation suitable for the proof system. +Tests are categorized into two main types: -### zk-SNARKs Approach +#### Unit Tests -zk-SNARKs utilize arithmetic circuits over a finite field \(F_p\), with constraints represented as equations. A witness is an assignment of signals satisfying these constraints. Proofs verify knowledge of a witness without revealing private signals. +Unit tests are focused on testing one module in isolation, often including private functions. They reside in the `src` directory within the file they are testing, typically inside a `#[cfg(test)] mod tests` block. -### zk-STARKs Approach +#### Integration Tests -STARKs, used by Cairo, employ an Algebraic Intermediate Representation (AIR) consisting of polynomial constraints, rather than arithmetic circuits. Cairo's ability to emulate arithmetic circuits allows for the implementation of zk-SNARKs verifiers within STARK proofs. +Integration tests verify that multiple parts of the library work together correctly, using only the public interface. They are placed in a top-level `tests` directory, where each file is compiled as an individual crate. To make the `tests` directory behave as a single crate, a `tests/lib.cairo` file can be added. -## Implementing Arithmetic Circuits in Cairo +### Controlling Test Execution -Cairo provides circuit constructs in the `core::circuit` module for building arithmetic circuits. These circuits utilize builtins for operations like addition and multiplication modulo \(p\). +* **Filtering:** Running `scarb test ` runs only tests whose names match the provided string (or substring). +* **Ignoring:** The `#[ignore]` attribute excludes a test from standard runs. It can be included later using `scarb test --include-ignored`. +* **Gas Limits:** For recursive functions or loops, the execution gas can be overridden using `#[available_gas()]` on the test function. -### Basic Arithmetic Gates +### Benchmarking -- `AddMod` builtin for addition modulo \(p\) -- `MulMod` builtin for multiplication modulo \(p\) +Starknet Foundry's profiling feature, used with `snforge test --build-profile`, generates execution traces for successful tests to analyze and optimize performance, visualized using tools like `go tool pprof`. -These enable the construction of gates such as `AddModGate`, `SubModGate`, `MulModGate`, and `InvModGate`. +--- -### Example Circuit: `a * (a + b)` +Sources: -The following code demonstrates the creation and evaluation of a circuit that computes \(a \cdot (a + b)\\) over the BN254 prime field: +- https://www.starknet.io/cairo-book/ch104-01-general-recommendations.html +- https://www.starknet.io/cairo-book/ch104-00-starknet-smart-contracts-security.html +- https://www.starknet.io/cairo-book/ch104-03-static-analysis-tools.html -```cairo, noplayground -use core::circuit::{ - AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, - CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, -}; +--- -// Circuit: a * (a + b) -// witness: a = 10, b = 20 -// expected output: 10 * (10 + 20) = 300 -fn eval_circuit() -> (u384, u384) { - let a = CircuitElement::> {}; - let b = CircuitElement::> {}; +### Security Best Practices and Contract Testing - let add = circuit_add(a, b); - let mul = circuit_mul(a, add); +#### General Recommendations and Focus Areas +Writing secure code is crucial. Focus areas derived from audits include: +* Access control and upgrades. +* Safe ERC20 token integrations. +* Cairo-specific pitfalls. +* Cross-domain/bridging safety. +* Economic/Denial of Service (DoS) considerations. - let output = (mul,); +#### Access Control, Upgrades & Initializers +The most common critical issues involve access control ("who can call this?") and re-initialization. - let mut inputs = output.new_inputs(); - inputs = inputs.next([10, 0, 0, 0]); - inputs = inputs.next([20, 0, 0, 0]); +##### Own Your Privileged Paths +Ensure upgrades, pause/resume, bridge handling, and meta-execution are guarded, typically using `OwnableComponent`. - let instance = inputs.done(); +```cairo +// components +component!(path: OwnableComponent, storage: ownable); +component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - let bn254_modulus = TryInto::< - _, CircuitModulus, - >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) - .unwrap(); +#[abi(embed_v0)] +impl OwnableImpl = OwnableComponent::OwnableImpl; +impl InternalUpgradeableImpl = UpgradeableComponent::InternalImpl; - let res = instance.eval(bn254_modulus).unwrap(); +#[event] +fn Upgraded(new_class_hash: felt252) {} - let add_output = res.get_output(add); - let circuit_output = res.get_output(mul); +fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + Upgraded(new_class_hash); // emit explicit upgrade event +} +``` - assert!(add_output == u384 { limb0: 30, limb1: 0, limb2: 0, limb3: 0 }, "add_output"); - assert!(circuit_output == u384 { limb0: 300, limb1: 0, limb2: 0, limb3: 0 }, "circuit_output"); +##### Initializers Must Be Idempotent +A publicly exposed initializer called post-deploy can lead to vulnerabilities if it can be called multiple times. Ensure initialization logic is idempotent by checking a flag first. - (add_output, circuit_output) +```cairo +#[storage] +struct Storage { + _initialized: u8, + // ... } -#[executable] -fn main() { - eval_circuit(); +fn initializer(ref self: ContractState, owner: ContractAddress) { + assert!(self._initialized.read() == 0, "ALREADY_INIT"); + self._initialized.write(1); + self.ownable.initialize(owner); + // init the rest… } ``` +Rule: If a function must be external during deployment, ensure it can only be called once; otherwise, keep it internal. -The process involves defining inputs, describing the circuit, specifying outputs, assigning values, defining the modulus, evaluating the circuit, and retrieving output values. - -## Modular Arithmetic Builtin +##### Emit Events +Emit events for upgrades, configuration changes, pausing, liquidations, and any privileged action to aid incident response and indexers. Include addresses (e.g., token) to remove ambiguity. -The Mod Builtin optimizes modular arithmetic operations, crucial for cryptographic protocols and zero-knowledge proofs, by reducing computational overhead compared to pure Cairo implementations. It is implicitly used when working with Arithmetic Circuits. +#### Safe Token Integrations +##### Always Check Boolean Returns +Not all ERC20 implementations revert on failure; some return `false`. Verify the boolean flags returned by `transfer` and `transfer_from` to confirm success. -### Structure and Operation +##### Naming Conventions +Most Starknet ERC20 tokens use `snake_case`. Be aware that legacy tokens might use `camelCase` entrypoints, requiring adaptation if interacting with them. -The Mod Builtin uses seven input cells: four for the modulus `p` (as a multi-word integer), and others for operands and results. It processes operations in batches, ensuring `op(a, b) = c + k * p`, where `k` is a quotient within bounds. The `run_mod_p_circuit` function orchestrates these operations. Values are typically kept under `2^96` using `range_check96_ptr`. +#### Cairo-Specific Pitfalls +##### `deploy_syscall(deploy_from_zero=true)` Collisions +Setting `deploy_from_zero` to `true` enables deterministic deployment, which can cause collisions if two contracts attempt deployment with the same calldata. Set this to `false` unless this behavior is explicitly desired. -### Modular Addition Example +##### Useless Zero-Address Checks +Checks like `get_caller_address().is_zero()` are inherited from Solidity but are useless on Starknet, as `get_caller_address()` is never the zero address. -The `AddMod` builtin can be used to compute `x + y (mod p)` for `UInt384` values, as illustrated in Cairo Zero code. +#### Cross-Domain / Bridging Safety +L1-L2 interactions require specific validation. -Oracles +##### L1 Handler Caller Validation +Entrypoints marked with `#[l1_handler]` must validate that the caller address originates from a trusted L1 contract. -# Oracles +```cairo +#[l1_handler] +fn handle_deposit( + ref self: ContractState, + from_address: ContractAddress, + account: ContractAddress, + amount: u256 +) { + let l1_bridge = self._l1_bridge.read(); + assert!(!l1_bridge.is_zero(), 'UNINIT_BRIDGE'); + assert!(from_address == l1_bridge, 'ONLY_L1_BRIDGE'); + // credit account… +} +``` -Oracles are an experimental Scarb feature that allows an external helper process to perform computations and provide values to the Cairo VM. These values are then constrained within the Cairo program, becoming part of the proof. Oracles are available for Cairo executables run with `--experimental-oracles` and are not supported in Starknet contracts. +#### Economic/DoS & Griefing +##### Unbounded Loops +User-controlled iterations (e.g., batch withdrawals, order sweeps) can exceed Starknet's execution step limit if the list size is unbounded. Cap the number of iterations or implement a pagination pattern to split work across multiple transactions. -> Note: This "oracle" system is distinct from Smart Contract Oracles, though the concept of using external processes for data in a constrained system is similar. +#### Static Analysis Tools (Contract Testing) +Static analysis examines code structure, syntax, and properties without execution to identify vulnerabilities or rule violations. Developers should use these tools to automatically check code against defined security guidelines. +Reference tools include: +* Semgrep Cairo 1.0 support +* Caracal, a Starknet static analyzer -## Why use Oracles? +--- -Oracles introduce non-determinism to the Cairo VM, enabling the prover to assign arbitrary values to memory cells. This allows for the injection of external data without implementing complex algorithms directly in Cairo. For instance, instead of implementing a square-root algorithm, one can obtain the square root from an oracle and then simply assert the mathematical property (e.g., `sqrt * sqrt == number`). +Sources: -## What We’ll Build +- https://www.starknet.io/cairo-book/appendix-02-operators-and-symbols.html +- https://www.starknet.io/cairo-book/appendix-01-keywords.html +- https://www.starknet.io/cairo-book/appendix-04-cairo-prelude.html +- https://www.starknet.io/cairo-book/appendix-00.html +- https://www.starknet.io/cairo-book/appendix-000.html +- https://www.starknet.io/cairo-book/appendix-06-useful-development-tools.html +- https://www.starknet.io/cairo-book/ch103-06-00-other-examples.html -We will create a Cairo executable that interacts with two oracle endpoints: one for integer square roots and another for decomposing a number into little-endian bytes. A Rust process will implement these endpoints, communicating with Cairo via standard input/output using the `stdio:` protocol supported by Scarb's executor. +--- -The complete example is located in the `listing_oracles/` directory. +--- -## The Cairo Package +Sources: -The `Scarb.toml` manifest declares an executable package, depends on `cairo_execute` for running with Scarb, and includes the `oracle` crate for `oracle::invoke`. +- https://www.starknet.io/cairo-book/appendix-00.html +- https://www.starknet.io/cairo-book/appendix-000.html -Filename: listing_oracles/Scarb.toml +--- -```toml -[package] -name = "example" -version = "0.1.0" -edition = "2024_07" -publish = false +### Reference Material Overview -[dependencies] -cairo_execute = "2.12.0" -oracle = "0.1.0-dev.4" +#### Appendix (Cairo) +The Cairo Programming Language appendix includes: +* Light +* Rust +* Coal +* Navy +* Ayu -[executable] +The following sections contain reference material you may find useful in your Cairo journey. -[cairo] -enable-gas = false +#### Appendix (Starknet) +The following sections contain reference material you may find useful in your Starknet journey. +]] -[dev-dependencies] -cairo_test = "2" -``` +--- -The Cairo code defines helper functions to call the Rust oracle using a `stdio:...` connection string. After each call, it asserts properties to maintain the program's soundness. +Sources: -Filename: listing_oracles/src/lib.cairo +- https://www.starknet.io/cairo-book/appendix-01-keywords.html -```cairo -use core::num::traits::Pow; +--- -// Call into the Rust oracle to get the square root of an integer. -fn sqrt_call(x: u64) -> oracle::Result { - oracle::invoke("stdio:cargo -q run --manifest-path ./src/my_oracle/Cargo.toml", 'sqrt', (x,)) -} +### Cairo Keywords and Built-ins -// Call into the Rust oracle to convert an integer to little-endian bytes. -fn to_le_bytes(val: u64) -> oracle::Result> { - oracle::invoke( - "stdio:cargo -q run --manifest-path ./src/my_oracle/Cargo.toml", 'to_le_bytes', (val,), - ) -} +The Cairo language keywords are divided into three reserved categories: strict, loose, and reserved. Additionally, built-in function names should not be used for other items as good practice. -fn oracle_calls(x: u64) -> Result<(), oracle::Error> { - let sqrt = sqrt_call(x)?; - // CONSTRAINT: sqrt * sqrt == x - assert!(sqrt * sqrt == x, "Expected sqrt({x}) * sqrt({x}) == x, got {sqrt} * {sqrt} == {x}"); - println!("Computed sqrt({x}) = {sqrt}"); +#### Keyword Categories - let bytes = to_le_bytes(x)?; - // CONSTRAINT: sum(bytes_i * 256^i) == x - let mut recomposed_val = 0; - for (i, byte) in bytes.span().into_iter().enumerate() { - recomposed_val += (*byte).into() * 256_u64.pow(i.into()); - } - assert!( - recomposed_val == x, - "Expected recomposed value {recomposed_val} == {x}, got {recomposed_val}" - ); - println!("le_bytes decomposition of {x}) = {:?}", bytes.span()); +Keywords fall into three categories: strict, loose, and reserved. A fourth category includes core library functions whose names are not reserved but are discouraged from reuse. - Ok(()) -} +#### Strict Keywords -#[executable] -fn main(x: u64) -> bool { - match oracle_calls(x) { - Ok(()) => true, - Err(e) => panic!("Oracle call failed: {e:?}"), - } -} -``` +These keywords must only be used in their correct contexts and cannot serve as item names. -### Key Concepts in Cairo Oracle Interaction +* `as` - Rename import +* `break` - Exit a loop immediately +* `const` - Define constant items +* `continue` - Continue to the next loop iteration +* `else` - Fallback for `if` and `if let` control flow constructs +* `enum` - Define an enumeration +* `extern` - Function defined at the compiler level that can be compiled to CASM +* `false` - Boolean false literal +* `fn` - Define a function +* `if` - Branch based on the result of a conditional expression +* `impl` - Implement inherent or trait functionality +* `implicits` - Special kind of function parameters that are required to perform certain actions +* `let` - Bind a variable +* `loop` - Loop unconditionally +* `match` - Match a value to patterns +* `mod` - Define a module +* `mut` - Denote variable mutability +* `nopanic` - Functions marked with this notation mean that the function will never panic. +* `of` - Implement a trait +* `pub` - Denote public visibility in items, such as struct and struct fields, enums, consts, traits and impl blocks, or modules +* `ref` - Parameter passed implicitly returned at the end of a function +* `return` - Return from function +* `struct` - Define a structure +* `trait` - Define a trait +* `true` - Boolean true literal +* `type` - Define a type alias +* `use` - Bring symbols into scope +* `while` - loop conditionally based on the result of an expression + +#### Loose Keywords + +These keywords are associated with specific behavior but can also be used to define items. + +* `self` - Method subject +* `super` - Parent module of the current module + +#### Reserved Keywords + +These keywords are reserved for future use. While it is currently possible to use them to define items, doing so is highly discouraged to ensure forward compatibility. + +* `Self` +* `do` +* `dyn` +* `for` +* `hint` +* `in` +* `macro` +* `move` +* `static_assert` +* `static` +* `try` +* `typeof` +* `unsafe` +* `where` +* `with` +* `yield` + +#### Built-in Functions + +These functions serve a special purpose in Cairo. Using their names for other items is not recommended. + +* `assert` - This function checks a boolean expression, and if it evaluates to false, it triggers the panic function. +* `panic` - This function acknowledges the occurrence of an error and terminates the program. + +--- + +Sources: + +- https://www.starknet.io/cairo-book/appendix-02-operators-and-symbols.html + +--- + +# Syntax Glossary and Symbols + +This section provides a glossary of Cairo's syntax, including operators and other symbols used in paths, generics, macros, attributes, comments, tuples, and brackets. + +## Operators + +Table B-1 lists Cairo operators, their context, explanation, and overloadability details. + +| Operator | Example | Explanation | Overloadable? | +| --- | --- | --- | --- | +| `!` | `!expr` | Logical complement | `Not` | +| `~` | `~expr` | Bitwise NOT | `BitNot` | +| `!=` | `expr != expr` | Non-equality comparison | `PartialEq` | +| `%` | `expr % expr` | Arithmetic remainder | `Rem` | +| `%=` | `var %= expr` | Arithmetic remainder and assignment | `RemEq` | +| `&` | `expr & expr` | Bitwise AND | `BitAnd` | +| `&&` | `expr && expr` | Short-circuiting logical AND | | +| `*` | `expr * expr` | Arithmetic multiplication | `Mul` | +| `*=` | `var *= expr` | Arithmetic multiplication and assignment | `MulEq` | +| `@` | `@var` | Snapshot | | +| `*` | `*var` | Desnap | | +| `+` | `expr + expr` | Arithmetic addition | `Add` | +| `+=` | `var += expr` | Arithmetic addition and assignment | `AddEq` | +| `,` | `expr, expr` | Argument and element separator | | +| `-` | `-expr` | Arithmetic negation | `Neg` | +| `-` | `expr - expr` | Arithmetic subtraction | `Sub` | +| `-=` | `var -= expr` | Arithmetic subtraction and assignment | `SubEq` | +| `->` | `fn(...) -> type`, `|...| -> type` | Function and closure return type | | +| `.` | `expr.ident` | Member access | | +| `/` | `expr / expr` | Arithmetic division | `Div` | +| `/=` | `var /= expr` | Arithmetic division and assignment | `DivEq` | +| `:` | `pat: type`, `ident: type` | Constraints | | +| `:` | `ident: expr` | Struct field initializer | | +| `;` | `expr;` | Statement and item terminator | | +| `<` | `expr < expr` | Less than comparison | `PartialOrd` | +| `<=` | `expr <= expr` | Less than or equal to comparison | `PartialOrd` | +| `=` | `var = expr` | Assignment | | +| `==` | `expr == expr` | Equality comparison | `PartialEq` | +| `=>` | `pat => expr` | Part of match arm syntax | | +| `>` | `expr > expr` | Greater than comparison | `PartialOrd` | +| `>=` | `expr >= expr` | Greater than or equal to comparison | `PartialOrd` | +| `^` | `expr ^ expr` | Bitwise exclusive OR | `BitXor` | +| `|` | `expr | expr` | Bitwise OR | `BitOr` | +| `||` | `expr || expr` | Short-circuiting logical OR | | +| `?` | `expr?` | Error propagation | | + +## Non-Operator Symbols + +These symbols do not behave like a function or method call. + +### Stand-Alone Syntax + +| Symbol | Explanation | +| --- | --- | +| `..._u8`, `..._usize`, `..._bool`, etc. | Numeric literal of specific type | +| `\"...\"` | String literal | +| `'...'` | Short string, 31 ASCII characters maximum | +| `_` | “Ignored” pattern binding | + +### Path-Related Syntax + +Symbols used within the context of a module hierarchy path to access an item. + +| Symbol | Explanation | +| --- | --- | +| `ident::ident` | Namespace path | +| `super::path` | Path relative to the parent of the current module | +| `trait::method(...)` | Disambiguating a method call by naming the trait that defines it | -1. **`oracle::invoke` Function**: This function is used for all oracle interactions. It takes a `connection` string (specifying the transport and process, e.g., `stdio:` with a Cargo command), a `selector` (the endpoint name defined in Rust), and a tuple of inputs. The return type is `oracle::Result`, allowing for explicit error handling. -2. **Constraining Oracle Outputs**: It is crucial to immediately constrain the values returned by the oracle. For the square root, this involves asserting `sqrt * sqrt == x`. For byte decomposition, the value is recomputed from its bytes and asserted to equal the original number. These assertions are vital for the soundness of the ZK-proof, preventing a malicious prover from injecting arbitrary values. +### Generic Type Parameters -## The Rust Oracle +Symbols appearing in the context of using generic type parameters. -The Rust side implements the oracle endpoints. The `cairo_oracle_server` crate handles input decoding and output encoding back to Cairo. +| Symbol | Explanation | +| --- | --- | +| `path<...>` | Specifies parameters to generic type in a type (e.g., `Array`) | -Filename: listing_oracles/src/my_oracle/Cargo.toml +### Macros -```toml -[package] -name = "my_oracle" -version = "0.1.0" -edition = "2021" -publish = false +| Symbol | Explanation | +| :--- | :--- | +| `get_dep_component_mut!` | Returns the requested component state from a reference of the state inside a component | +| `component!` | Macro used in Starknet contracts to embed a component inside a contract | -[dependencies] -anyhow = "1" -cairo-oracle-server = "0.1" -starknet-core = "0.11" -``` +### Comments -Filename: listing_oracles/src/my_oracle/src/main.rs +Symbols that create comments. -```rust, noplayground -use anyhow::ensure; -use cairo_oracle_server::Oracle; -use std::process::ExitCode; +| Symbol | Explanation | +| --- | --- | +| `//` | Line comment | -fn main() -> ExitCode { - Oracle::new() - .provide("sqrt", |value: u64| { - let sqrt = (value as f64).sqrt() as u64; - ensure!( - sqrt * sqrt == value, - "Cannot compute integer square root of {value}" - ); - Ok(sqrt) - }) - .provide("to_le_bytes", |value: u64| { - let value_bytes = value.to_le_bytes(); - Ok(value_bytes.to_vec()) - }) - .run() -} -``` +### Tuples -The `sqrt` endpoint computes the integer square root, rejecting inputs without an exact square root. The `to_le_bytes` endpoint returns the little-endian byte representation of a `u64`. +Symbols appearing in the context of using tuples. -## Running the Example +| Symbol | Explanation | +| --- | --- | +| `()` | Empty tuple (aka unit), both literal and type | +| `(expr)` | Parenthesized expression | +| `(expr,)` | Single-element tuple expression | +| `(type,)` | Single-element tuple type | +| `(expr, ...)` | Tuple expression | +| `(type, ...)` | Tuple type | +| `expr(expr, ...)` | Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants | -To run the example, navigate to the example directory and execute the following command: +### Curly Braces -```bash -scarb execute --experimental-oracles --print-program-output --arguments 25000000 -``` +Contexts in which curly braces are used. -This command will execute the program with oracles enabled, printing the output. The program should return `1`, indicating success. It calls the oracle for `sqrt(25000000)`, verifies the result, decomposes `25000000` into bytes, and verifies the recomposition. +| Context | Explanation | +| --- | --- | +| `{...}` | Block expression | +| `Type {...}` | `struct` literal | -To observe an error, try a non-perfect square like `27`: +--- -```bash -scarb execute --experimental-oracles --print-program-output --arguments 27 -``` +Sources: -The `sqrt` endpoint will return an error, which propagates to Cairo, causing the program to panic. +- https://www.starknet.io/cairo-book/appendix-04-cairo-prelude.html -## Summary +--- -This example demonstrates how to offload computations to an external process and incorporate the results into a Cairo proof. This pattern is useful for integrating fast, flexible helpers during client-side proving. Remember that oracles are an experimental feature, intended for runners only, and all data received from them must be rigorously validated within the Cairo code. +## Cairo Prelude and Editions -Appendices and References +### The Cairo Prelude -# Appendices and References +The Cairo prelude is a collection of commonly used modules, functions, data types, and traits that are automatically brought into scope of every module in a Cairo crate without needing explicit import statements. It provides the basic building blocks for starting Cairo programs and writing smart contracts. -## Appendix A - Keywords +The core library prelude is defined in the *lib.cairo* file of the corelib crate and contains: +* Data types: integers, bools, arrays, dicts, etc. +* Traits: behaviors for arithmetic, comparison, and serialization operations. +* Operators: arithmetic, logical, bitwise. +* Utility functions - helpers for arrays, maps, boxing, etc. -The following list contains keywords reserved for current or future use by the Cairo language. There are three main categories: strict, loose, and reserved. A fourth category includes functions from the core library; while their names aren't reserved, it's good practice to avoid using them as identifiers. +Since the core library prelude is automatically imported, its contents are available for use in any Cairo crate without explicit imports. This allows usage like `ArrayTrait::append()` or the `Default` trait without explicit scoping. -### Strict Keywords +### Cairo Editions -These keywords can only be used in their correct contexts and cannot be used as names for items. +You can choose which prelude version to use by specifying the edition in the *Scarb.toml* configuration file. For example, adding `edition = "2024_07"` loads the prelude from July 2024. New projects created via `scarb new` automatically include `edition = "2024_07"`. Different prelude versions expose different functions and traits, making the specification of the correct edition important. Generally, new projects should start with the latest edition. -- `as` - Rename import -- `break` - Exit a loop immediately -- `const` - Define constant items -- `continue` - Continue to the next loop iteration -- `else` - Fallback for `if` and `if let` control flow constructs -- `enum` - Define an enumeration -- `extern` - Function defined at the compiler level that can be compiled to CASM -- `false` - Boolean false literal -- `fn` - Define a function -- `if` - Branch based on the result of a conditional expression -- `impl` - Implement inherent or trait functionality -- `implicits` - Special kind of function parameters required for certain actions -- `let` - Bind a variable -- `loop` - Loop unconditionally -- `match` - Match a value to patterns -- `mod` - Define a module -- `mut` - Denote variable mutability -- `nopanic` - Functions marked with this notation will never panic. -- `of` - Implement a trait -- `pub` - Denote public visibility in items (structs, fields, enums, consts, traits, impl blocks, modules) -- `ref` - Parameter passed implicitly returned at the end of a function -- `return` - Return from function -- `struct` - Define a structure -- `trait` - Define a trait -- `true` - Boolean true literal +Here is the list of available Cairo editions (i.e prelude versions) with their details: -## Appendix B - Syntax +| Version | Details | +| --- | --- | +| `2024-07` | details for 2024-07 | +| `2023-11` | details for 2023-11 | +| `2023-10` / `2023-1` | details for 2023-10 | -This section details the usage of specific syntax elements in Cairo. +--- -### Tuples +Sources: -Tuples are used for grouping multiple values. +- https://www.starknet.io/cairo-book/appendix-06-useful-development-tools.html +- https://www.starknet.io/cairo-book/ch103-06-00-other-examples.html -| Syntax | Description | -| :---------------- | :------------------------------------------------------------------------------------------- | -| `()` | Empty tuple (unit), both literal and type. | -| `(expr)` | Parenthesized expression. | -| `(expr,)` | Single-element tuple expression. | -| `(type,)` | Single-element tuple type. | -| `(expr, ...)` | Tuple expression. | -| `(type, ...)` | Tuple type. | -| `expr(expr, ...)` | Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants. | +--- -### Curly Braces +## Development Tools and Examples -Curly braces `{}` have specific contexts in Cairo. +### Useful Development Tools -| Context | Explanation | -| :----------- | :--------------- | -| `{...}` | Block expression | -| `Type {...}` | `struct` literal | +This section covers useful development tools provided by the Cairo project, including automatic formatting, quick warning fixes, a linter, and IDE integration. -## Appendix C - Derivable Traits +#### Automatic Formatting with `scarb fmt` -The `derive` attribute automatically generates code to implement a default trait on a struct or enum. The following traits from the standard library are compatible with the `derive` attribute. +Scarb projects can be formatted using the `scarb fmt` command. If using Cairo binaries directly, run `cairo-format`. This tool ensures consistent style across collaborative projects. -### Hashing with `Hash` +To format any Cairo project, run the following inside the project directory: +``` +scarb fmt +``` -Deriving the `Hash` trait allows structs and enums to be easily hashed. For a type to derive `Hash`, all its fields or variants must themselves be hashable. +To skip formatting for specific code sections, use `#[cairofmt::skip]`: +``` +#[cairofmt::skip] +let table: Array = array![ + "oxo", + "xox", + "oxo", +]; +``` -### Starknet Storage with `starknet::Store` +#### IDE Integration Using `cairo-language-server` -The `starknet::Store` trait is applicable when building on Starknet. It enables a type to be used in smart contract storage by automatically implementing the necessary read and write functions. +The Cairo community recommends using the `cairo-language-server` for IDE integration. This tool speaks the Language Server Protocol, enabling features like autocompletion, jump to definition, and inline errors when used with clients like the Cairo extension for Visual Studio Code. -## Appendix D - The Cairo Prelude +Visit the `vscode-cairo` page to install it on VSCode. -The Cairo prelude is a collection of commonly used modules, functions, data types, and traits automatically included in every Cairo module without explicit import statements. It provides essential building blocks for Cairo programs and smart contracts. +> Note: If you have Scarb installed, it should work out of the box with the Cairo VSCode extension, without a manual installation of the language server. -The core library prelude is defined in the `lib.cairo` file of the corelib crate. It includes: +### Other Examples -- **Data types:** Integers, booleans, arrays, dictionaries, etc. -- **Traits:** Behaviors for arithmetic, comparison, and serialization operations. -- **Operators:** Arithmetic, logical, and bitwise operators. -- **Utility functions:** Helpers for arrays, maps, boxing, and more. +This section contains additional examples of Starknet smart contracts, utilizing various features of the Cairo programming language. Contributions of diverse examples are welcome. From 2a183da4ea44d35b32f3f45d28788bde751aa266 Mon Sep 17 00:00:00 2001 From: enitrat Date: Sun, 5 Oct 2025 10:57:34 +0200 Subject: [PATCH 2/2] fmt --- ingesters/src/ingesters/CairoBookIngester.ts | 1 - ingesters/src/utils/vectorStoreUtils.ts | 5 +- .../generated/cairo_book_summary.md | 1178 ++++++++++------- 3 files changed, 687 insertions(+), 497 deletions(-) diff --git a/ingesters/src/ingesters/CairoBookIngester.ts b/ingesters/src/ingesters/CairoBookIngester.ts index 4b64c9e..8fbdbb3 100644 --- a/ingesters/src/ingesters/CairoBookIngester.ts +++ b/ingesters/src/ingesters/CairoBookIngester.ts @@ -103,7 +103,6 @@ export class CairoBookIngester extends MarkdownIngester { }); }); - return localChunks; } diff --git a/ingesters/src/utils/vectorStoreUtils.ts b/ingesters/src/utils/vectorStoreUtils.ts index 9b11c03..f8c205a 100644 --- a/ingesters/src/utils/vectorStoreUtils.ts +++ b/ingesters/src/utils/vectorStoreUtils.ts @@ -102,10 +102,7 @@ export async function updateVectorStore( // Find chunks to update and remove const { contentChanged, metadataOnlyChanged, chunksToRemove } = - findChunksToUpdateAndRemove( - chunks, - storedChunkHashes, - ); + findChunksToUpdateAndRemove(chunks, storedChunkHashes); logger.info( `Found ${storedChunkHashes.length} stored chunks for source: ${source}. ${contentChanged.length} content changes, ${metadataOnlyChanged.length} metadata-only changes, and ${chunksToRemove.length} removals`, diff --git a/python/src/scripts/summarizer/generated/cairo_book_summary.md b/python/src/scripts/summarizer/generated/cairo_book_summary.md index d4f7734..dddc3cc 100644 --- a/python/src/scripts/summarizer/generated/cairo_book_summary.md +++ b/python/src/scripts/summarizer/generated/cairo_book_summary.md @@ -1,13 +1,10 @@ --- - Sources: - -- https://www.starknet.io/cairo-book/ch00-00-introduction.html -- https://www.starknet.io/cairo-book/ch00-01-foreword.html -- https://www.starknet.io/cairo-book/ -- https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html -- https://www.starknet.io/cairo-book/title-page.html - + - https://www.starknet.io/cairo-book/ch00-00-introduction.html + - https://www.starknet.io/cairo-book/ch00-01-foreword.html + - https://www.starknet.io/cairo-book/ + - https://www.starknet.io/cairo-book/ch100-00-introduction-to-smart-contracts.html + - https://www.starknet.io/cairo-book/title-page.html --- # Introduction to Cairo and The Cairo Book @@ -17,41 +14,43 @@ Sources: This version of the text assumes you’re using Cairo version 2.12.0 and Starknet Foundry version 0.48.0. The book is open source, supported by the Cairo Community, StarkWare, and Voyager. Additional resources for mastering Cairo include: -* **The Cairo Playground**: A browser-based environment for writing, compiling, debugging, and proving Cairo code without setup. It shows compilation into Sierra (Intermediate Representation) and Casm (Cairo Assembly). -* **The Cairo Core Library Docs**: Documentation for the standard set of types, traits, and utilities built into the language. -* **The Cairo Package Registry**: Hosts reusable libraries like Alexandria and Open Zeppelin Contracts for Cairo, integrated via Scarb. -* **The Scarb documentation**: Official documentation for Cairo’s package manager and build tool. -* **The Cairo whitepaper**: Explains Cairo as a language for writing provable programs and its architecture for scalable, verifiable computation. + +- **The Cairo Playground**: A browser-based environment for writing, compiling, debugging, and proving Cairo code without setup. It shows compilation into Sierra (Intermediate Representation) and Casm (Cairo Assembly). +- **The Cairo Core Library Docs**: Documentation for the standard set of types, traits, and utilities built into the language. +- **The Cairo Package Registry**: Hosts reusable libraries like Alexandria and Open Zeppelin Contracts for Cairo, integrated via Scarb. +- **The Scarb documentation**: Official documentation for Cairo’s package manager and build tool. +- **The Cairo whitepaper**: Explains Cairo as a language for writing provable programs and its architecture for scalable, verifiable computation. ## What is Cairo? Cairo is a programming language designed specifically to leverage mathematical proofs for computational integrity, allowing programs to prove they executed correctly, even on untrusted machines. -* **Foundation**: The language is built on STARK technology, a modern evolution of Probabilistically Checkable Proofs (PCPs), which transforms computational claims into verifiable constraint systems. -* **Design**: Strongly inspired by Rust, Cairo abstracts away deep cryptographic complexities, allowing developers to focus on program logic while maintaining the full power of STARKs. -* **Performance**: Powered by a Rust VM and a next-generation prover, Cairo execution and proof generation are very fast. +- **Foundation**: The language is built on STARK technology, a modern evolution of Probabilistically Checkable Proofs (PCPs), which transforms computational claims into verifiable constraint systems. +- **Design**: Strongly inspired by Rust, Cairo abstracts away deep cryptographic complexities, allowing developers to focus on program logic while maintaining the full power of STARKs. +- **Performance**: Powered by a Rust VM and a next-generation prover, Cairo execution and proof generation are very fast. ## Applications and Context Cairo's primary application today is **Starknet**, a Layer 2 scaling solution for Ethereum. -* **Scalability**: Starknet addresses the Blockchain Trilemma by offloading complex computations from Ethereum L1. It uses Cairo's proof system where computations are executed off-chain, and a STARK proof is generated. This proof is then verified on L1 using significantly less power than re-executing the computation, enabling massive scalability while maintaining security. -* **General Purpose**: Beyond blockchain, Cairo can be used for any scenario where computational integrity needs efficient verification across different machines. +- **Scalability**: Starknet addresses the Blockchain Trilemma by offloading complex computations from Ethereum L1. It uses Cairo's proof system where computations are executed off-chain, and a STARK proof is generated. This proof is then verified on L1 using significantly less power than re-executing the computation, enabling massive scalability while maintaining security. +- **General Purpose**: Beyond blockchain, Cairo can be used for any scenario where computational integrity needs efficient verification across different machines. ## Audience and Prerequisites This book assumes basic programming knowledge (variables, functions, data structures). Prior Rust experience is helpful but not required. The book caters to three main audiences with recommended reading paths: + 1. **General-Purpose Developers**: Focus on chapters 1-12 for core language features, avoiding deep smart contract specifics. 2. **New Smart Contract Developers**: Read the book front to back for a solid foundation in both language and contract development. 3. **Experienced Smart Contract Developers**: Focus on Cairo basics (Chapters 1-3), the trait/generics system (Chapter 8), and then smart contract development (Chapter 15+). ## References -* Cairo CPU Architecture: https://eprint.iacr.org/2021/1063 -* Cairo, Sierra and Casm: https://medium.com/nethermind-eth/under-the-hood-of-cairo-1-0-exploring-sierra-7f32808421f5 -* State of non determinism: https://twitter.com/PapiniShahar/status/1638203716535713798 +- Cairo CPU Architecture: https://eprint.iacr.org/2021/1063 +- Cairo, Sierra and Casm: https://medium.com/nethermind-eth/under-the-hood-of-cairo-1-0-exploring-sierra-7f32808421f5 +- State of non determinism: https://twitter.com/PapiniShahar/status/1638203716535713798 --- @@ -79,6 +78,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.dev | sh ``` If successful, the output will show: + ``` starkup: Installation complete. ``` @@ -101,8 +101,8 @@ snforge 0.48.0 Cairo provides a VSCode extension offering syntax highlighting and code completion. Install it from the VSCode Marketplace. After installation, navigate to the extension settings and ensure the following options are ticked: -* `Enable Language Server` -* `Enable Scarb` +- `Enable Language Server` +- `Enable Scarb` --- @@ -117,7 +117,7 @@ Sources: ### Creating a Project Directory -You should start by making a directory for your Cairo code. For the examples in this book, making a *cairo_projects* directory in your home directory is suggested. +You should start by making a directory for your Cairo code. For the examples in this book, making a _cairo_projects_ directory in your home directory is suggested. For Linux, macOS, and PowerShell on Windows, use: @@ -143,15 +143,15 @@ scarb new hello_world Scarb will prompt for a test runner setup; `❯ Starknet Foundry (default)` is generally preferred. -This command creates a directory named *hello_world* containing configuration files and source directories. +This command creates a directory named _hello_world_ containing configuration files and source directories. ### Project Structure and Configuration -After running `scarb new hello_world`, navigate into the directory (`cd hello_world`). Scarb generates a *Scarb.toml* file, a *src* directory with *lib.cairo*, and a *tests* directory (which can be removed for simple executable programs). A Git repository is also initialized. +After running `scarb new hello_world`, navigate into the directory (`cd hello_world`). Scarb generates a _Scarb.toml_ file, a _src_ directory with _lib.cairo_, and a _tests_ directory (which can be removed for simple executable programs). A Git repository is also initialized. #### Scarb.toml Manifest -The *Scarb.toml* file uses TOML format for configuration. The initial structure for a Starknet contract project looks like this: +The _Scarb.toml_ file uses TOML format for configuration. The initial structure for a Starknet contract project looks like this: Filename: Scarb.toml @@ -179,7 +179,7 @@ test = "snforge test" # ... ``` -For a basic Cairo executable program instead of a contract, you can modify *Scarb.toml* to resemble the following: +For a basic Cairo executable program instead of a contract, you can modify _Scarb.toml_ to resemble the following: Filename: Scarb.toml @@ -202,15 +202,15 @@ function = "hello_world::hello_world::main" #### Source File Organization -Scarb reserves the top-level directory for non-code content. Source files must reside in the *src* directory. +Scarb reserves the top-level directory for non-code content. Source files must reside in the _src_ directory. -1. Delete the content of *src/lib.cairo* and replace it with a module declaration: +1. Delete the content of _src/lib.cairo_ and replace it with a module declaration: ```rust mod hello_world; ``` -2. Create a new file named *src/hello_world.cairo* with the following code: +2. Create a new file named _src/hello_world.cairo_ with the following code: Filename: src/hello_world.cairo @@ -232,7 +232,7 @@ The resulting structure is: ### Building and Executing the Project -From the *hello_world* directory, build the project using: +From the _hello_world_ directory, build the project using: ```bash $ scarb build @@ -252,10 +252,10 @@ Hello, World! ### Summary of Scarb Commands -* Install Scarb versions using `asdf`. -* Create a project using `scarb new`. -* Build a project using `scarb build` to generate compiled Sierra code. -* Execute a Cairo program using the `scarb execute` command. +- Install Scarb versions using `asdf`. +- Create a project using `scarb new`. +- Build a project using `scarb build` to generate compiled Sierra code. +- Execute a Cairo program using the `scarb execute` command. Scarb commands are consistent across different operating systems. @@ -371,7 +371,7 @@ fn main(input: u32) -> bool { The `is_prime` function handles edge cases (numbers $\le 1$, 2, and even numbers) and then iterates through odd divisors up to $\sqrt{n}$. The `main` function, marked with `#[executable]`, serves as the entry point, taking a `u32` input and returning the boolean result of `is_prime`. -*Note: Later modifications might change `u32` to `u128` for a larger range and add panicking behavior for inputs exceeding a certain limit (e.g., 1,000,000), which prevents proof generation if the program panics.* +_Note: Later modifications might change `u32` to `u128` for a larger range and add panicking behavior for inputs exceeding a certain limit (e.g., 1,000,000), which prevents proof generation if the program panics._ ### Executing the Program @@ -397,10 +397,11 @@ The output `1` indicates `true` (17 is prime). If the input was 4, the output wo ### Summary This guided project demonstrated: -* Defining executable targets in `Scarb.toml`. -* Writing functions and control flow in Cairo. -* Using `scarb execute` to run programs and generate execution traces. -* The overall workflow for proving and verifying computations using `scarb prove` and `scarb verify` (though the latter commands are not explicitly run here, they are the next logical step). + +- Defining executable targets in `Scarb.toml`. +- Writing functions and control flow in Cairo. +- Using `scarb execute` to run programs and generate execution traces. +- The overall workflow for proving and verifying computations using `scarb prove` and `scarb verify` (though the latter commands are not explicitly run here, they are the next logical step). --- @@ -414,18 +415,19 @@ Sources: ### Dependency Management in Scarb -If you want to import multiple packages, create only one `[dependencies]` section in *Scarb.toml* and list all desired packages beneath it. Development dependencies are declared in a `[dev-dependencies]` section. +If you want to import multiple packages, create only one `[dependencies]` section in _Scarb.toml_ and list all desired packages beneath it. Development dependencies are declared in a `[dev-dependencies]` section. Run `scarb build` to fetch all external dependencies and compile your package. Dependencies can also be managed via the command line: -* Use `scarb add` to add a dependency, which automatically edits *Scarb.toml*. -* Use `scarb add --dev` for development dependencies. -* To remove a dependency, edit *Scarb.toml* or use the `scarb rm` command. + +- Use `scarb add` to add a dependency, which automatically edits _Scarb.toml_. +- Use `scarb add --dev` for development dependencies. +- To remove a dependency, edit _Scarb.toml_ or use the `scarb rm` command. ### The Glob Operator -To bring *all* public items defined in a path into scope, specify that path followed by the `*` glob operator in a `use` statement: +To bring _all_ public items defined in a path into scope, specify that path followed by the `*` glob operator in a `use` statement: ```rust #![allow(unused)] @@ -458,10 +460,11 @@ The language varies by blockchain; Solidity is common for EVM, while Cairo is us ### Smart Contract Use Cases Smart contracts facilitate various applications: -* **Tokenization:** Tokenizing real-world assets (real estate, art) to increase liquidity and enable fractional ownership. -* **Voting:** Creating secure, transparent voting systems where results are tallied automatically. -* **Royalties:** Automating royalty payments to creators upon content consumption or sale. -* **Decentralized Identities (DIDs):** Managing digital identities, allowing users control over personal information sharing and access verification. + +- **Tokenization:** Tokenizing real-world assets (real estate, art) to increase liquidity and enable fractional ownership. +- **Voting:** Creating secure, transparent voting systems where results are tallied automatically. +- **Royalties:** Automating royalty payments to creators upon content consumption or sale. +- **Decentralized Identities (DIDs):** Managing digital identities, allowing users control over personal information sharing and access verification. ## The Rise of Starknet and Cairo @@ -549,37 +552,44 @@ Sources: Every executable Cairo program begins execution in the `main` function. The `main` function declaration: + ```rust fn main() { } ``` -* The `main` function takes no parameters (if it did, they would be inside `()`). -* The function body is enclosed in curly brackets `{}`. -* It is standard style to place the opening curly bracket on the same line as the function declaration, separated by one space. -* Cairo style dictates using four spaces for indentation. + +- The `main` function takes no parameters (if it did, they would be inside `()`). +- The function body is enclosed in curly brackets `{}`. +- It is standard style to place the opening curly bracket on the same line as the function declaration, separated by one space. +- Cairo style dictates using four spaces for indentation. The body of `main` often contains code that performs actions, such as printing to the terminal: + ```rust println!("Hello, World!"); ``` + The `println!` syntax calls a Cairo macro; calling a function would omit the exclamation mark (e.g., `println`). ### Statements and Expressions Cairo is an expression-based language, distinguishing between statements and expressions: -* **Statements** are instructions that perform an action but do not return a value. -* **Expressions** evaluate to a resultant value. +- **Statements** are instructions that perform an action but do not return a value. +- **Expressions** evaluate to a resultant value. Function definitions are statements. Creating a variable assignment using `let` is also a statement, as shown in Listing 2-1: + ```rust #[executable] fn main() { let y = 6; } ``` + Since statements do not return values, attempting to assign a `let` statement to another variable results in an error: + ```rust #[executable] fn main() { @@ -593,12 +603,15 @@ Comments are ignored by the compiler but aid human readers. **Standard Comments:** Idiomatic comments start with two slashes (`//`) and continue until the end of the line. For multi-line comments, `//` must prefix every line: + ```rust // So we’re doing something complicated here, long enough that we need // multiple lines of comments to do it! Whew! Hopefully, this comment will // explain what’s going on. ``` + Comments can also appear at the end of lines containing code: + ```rust #[executable] fn main() -> felt252 { @@ -608,7 +621,8 @@ fn main() -> felt252 { **Item-level Documentation:** These comments refer to specific items (functions, traits, etc.) and are prefixed with three slashes (`///`). They provide detailed descriptions, usage examples, and panic conditions. -```rust + +````rust /// Returns the sum of `arg1` and `arg2`. /// `arg1` cannot be zero. /// @@ -628,7 +642,7 @@ fn add(arg1: felt252, arg2: felt252) -> felt252 { assert!(arg1 != 0, "Cannot be zero"); arg1 + arg2 } -``` +```` --- @@ -706,10 +720,11 @@ The value of x is: 6 #### Constants -*Constants* are similar to immutable variables but have key differences: +_Constants_ are similar to immutable variables but have key differences: + 1. They cannot be declared mutable (`mut` is disallowed). 2. They are declared using the `const` keyword instead of `let`. -3. The type of the value *must* always be annotated. +3. The type of the value _must_ always be annotated. 4. They can only be declared in the global scope. 5. They can only be set to a constant expression, not a value computed at runtime. @@ -807,7 +822,7 @@ error: `scarb` command exited with error #### Variable Scope -A *scope* is the range within a program for which an item is valid. A variable is valid from the point it is declared until it goes out of scope. +A _scope_ is the range within a program for which an item is valid. A variable is valid from the point it is declared until it goes out of scope. ```rust //TAG: ignore_fmt @@ -824,7 +839,7 @@ Listing 4-1: A variable and the scope in which it is valid ##### Moving Values -*Moving* a value means passing it to another function. When this happens, the variable referring to that value in the original scope is destroyed and can no longer be used; a new variable is created to hold the value. Complex types, like Arrays, are moved when passed to another function. +_Moving_ a value means passing it to another function. When this happens, the variable referring to that value in the original scope is destroyed and can no longer be used; a new variable is created to hold the value. Complex types, like Arrays, are moved when passed to another function. Array example: @@ -846,19 +861,24 @@ Sources: --- # Data Types: Scalars and Primitives + ... + ## Felt Type + ... + ### Unsigned Integers + ... This seems the safest interpretation of the conflicting requirements (explicit syntax vs relative depth based on ToC context). The chunks start with `## Data Types - The Cairo Programming Language\n\n# Data Types\n\n...`. I must ignore this initial context and start with the required heading derived from the TOC path. -```markdown +````markdown # Data Types: Scalars and Primitives -Cairo is a *statically typed* language, which means that it must know the types of all variables at compile time. The compiler can usually infer the desired type based on the value and its usage. In cases when many types are possible, a conversion method can specify the desired output type: +Cairo is a _statically typed_ language, which means that it must know the types of all variables at compile time. The compiler can usually infer the desired type based on the value and its usage. In cases when many types are possible, a conversion method can specify the desired output type: ```rust #[executable] @@ -867,6 +887,7 @@ fn main() { let y: u32 = x.try_into().unwrap(); } ``` +```` Scalar types represent a single value. Cairo has three primary scalar types: felts, integers, and booleans. @@ -884,15 +905,15 @@ It is highly recommended to use integer types over `felt252` due to added securi Built-in unsigned integer types: -| Length | Unsigned | -| :--- | :--- | -| 8-bit | `u8` | -| 16-bit | `u16` | -| 32-bit | `u32` | -| 64-bit | `u64` | -| 128-bit | `u128` | -| 256-bit | `u256` | -| 32-bit | `usize` | +| Length | Unsigned | +| :------ | :------- | +| 8-bit | `u8` | +| 16-bit | `u16` | +| 32-bit | `u32` | +| 64-bit | `u64` | +| 128-bit | `u128` | +| 256-bit | `u256` | +| 32-bit | `usize` | `usize` is currently an alias for `u32`. Since variables are unsigned, attempting to create a negative result causes a panic: @@ -961,8 +982,8 @@ Cairo handles strings using short strings (simple quotes) or `ByteArray` (double Short strings are ASCII strings encoded on one byte per character, stored in a `felt252`. A short string is limited to 31 characters. -* `'a'` is equivalent to `0x61`. -* `0x616263` is equivalent to `'abc'`. +- `'a'` is equivalent to `0x61`. +- `0x616263` is equivalent to `'abc'`. Examples of declaration: @@ -978,6 +999,7 @@ fn main() { let long_string: ByteArray = "this is a string which has more than 31 characters"; } ``` + This covers all points concisely using H2/H3 subheadings relative to the H1 section start. --- @@ -1012,9 +1034,9 @@ fn main() { ## The Tuple Type -A *tuple* groups together values of potentially varying types into one compound type. Tuples have a fixed length. +A _tuple_ groups together values of potentially varying types into one compound type. Tuples have a fixed length. -Tuples are created using a comma-separated list of values inside parentheses. Individual values can be accessed by *destructuring* the tuple using a pattern with `let`: +Tuples are created using a comma-separated list of values inside parentheses. Individual values can be accessed by _destructuring_ the tuple using a pattern with `let`: ```cairo #[executable] @@ -1047,7 +1069,7 @@ fn main() { ### The Unit Type () -A *unit type* has only one value `()`, represented by an empty tuple. It has zero size and is guaranteed not to exist in compiled code. It is the implicit return value for expressions that return nothing. +A _unit type_ has only one value `()`, represented by an empty tuple. It has zero size and is guaranteed not to exist in compiled code. It is the implicit return value for expressions that return nothing. ## The Fixed Size Array Type @@ -1200,6 +1222,7 @@ Sources: The `Copy` trait allows for the duplication of values. It can be derived on any type whose parts all implement `Copy`. Example: + ```rust #[derive(Copy, Drop)] struct A { @@ -1221,6 +1244,7 @@ fn main() { The `Debug` trait enables debug formatting in format strings, indicated by adding `:?` within `{}` placeholders. It allows printing instances for inspection. The `Debug` trait is required when using `assert_xx!` macros in tests to print failing values. Example: + ```rust #[derive(Copy, Drop, Debug)] struct Point { @@ -1234,7 +1258,9 @@ fn main() { println!("{:?}", p); } ``` + Output from running this example: + ``` scarb execute Point { x: 1, y: 3 } @@ -1245,6 +1271,7 @@ Point { x: 1, y: 3 } The `Default` trait allows creation of a default value of a type, most commonly zero for primitive types. If deriving `Default` on a composite type, all its elements must implement `Default`. For an `enum`, the default value must be declared using the `#[default]` attribute on one of its variants. Example: + ```rust #[derive(Default, Drop)] struct A { @@ -1276,6 +1303,7 @@ fn main() { The `PartialEq` trait allows comparison between instances using `==` and `!=`. When derived, two instances are equal only if all their fields are equal (for structs), or if they are the same variant (for enums). It is required for the `assert_eq!` macro in tests. Custom implementations are possible; for instance, two rectangles can be considered equal if they have the same area. Example of custom implementation: + ```rust #[derive(Copy, Drop)] struct Rectangle { @@ -1303,6 +1331,7 @@ fn main() { ``` Example of derived usage: + ```rust #[derive(PartialEq, Drop)] struct A { @@ -1326,6 +1355,7 @@ Cairo uses the `TryInto` and `Into` traits for type conversion. The `Into` trait is used for infallible conversions, typically when the source type is smaller than the destination type. Conversion is done via `var.into()`, and the new variable's type must be explicitly defined. Example with built-in types: + ```rust #[executable] fn main() { @@ -1344,6 +1374,7 @@ fn main() { ``` Implementing `Into` for custom types: + ```rust #[derive(Drop, PartialEq)] struct Rectangle { @@ -1380,6 +1411,7 @@ fn main() { The `TryInto` trait is used for fallible conversions (e.g., when the target type might not fit the source value), returning `Option`. Conversion is performed via `var.try_into()`. Example with built-in types: + ```rust #[executable] fn main() { @@ -1399,6 +1431,7 @@ fn main() { ``` Implementing `TryInto` for custom types: + ```rust #[derive(Drop)] struct Rectangle { @@ -1452,7 +1485,7 @@ Sources: ## Functions: Definition and Execution -Cairo code conventionally uses *snake case* for function and variable names (lowercase with underscores separating words). +Cairo code conventionally uses _snake case_ for function and variable names (lowercase with underscores separating words). ### Function Definition and Execution @@ -1474,7 +1507,7 @@ Functions are called by their name followed by parentheses. The definition order ### Parameters -Functions can accept *parameters*, which are variables in the function signature. The concrete values passed are called *arguments*. Parameter types must be declared in the signature. +Functions can accept _parameters_, which are variables in the function signature. The concrete values passed are called _arguments_. Parameter types must be declared in the signature. Example with a single parameter: @@ -1579,7 +1612,7 @@ fn main() { #### Returning Multiple Values -Cairo supports returning multiple values using a tuple. However, to avoid moving values passed into a function when they are needed afterwards, Cairo offers *references* and *snapshots*. +Cairo supports returning multiple values using a tuple. However, to avoid moving values passed into a function when they are needed afterwards, Cairo offers _references_ and _snapshots_. Example returning multiple values via a tuple: @@ -1662,6 +1695,7 @@ fn main() { ``` Running with `number = 3` yields: + ``` $ scarb execute Compiling no_listing_24_if v0.1.0 (listings/ch02-common-programming-concepts/no_listing_27_if/Scarb.toml) @@ -1722,6 +1756,7 @@ fn main() { ``` Output when `number = 3`: + ``` $ scarb execute Compiling no_listing_25_else_if v0.1.0 (listings/ch02-common-programming-concepts/no_listing_30_else_if/Scarb.toml) @@ -1772,6 +1807,7 @@ Infinite loops are prevented by a gas meter. If gas runs out, the program stops. The `break` keyword exits the loop. The `continue` keyword skips the rest of the current iteration and proceeds to the next one. Example using `break` and `continue`: + ```rust #[executable] fn main() { @@ -1789,6 +1825,7 @@ fn main() { } } ``` + Executing this program skips printing when `i` equals 5. ### Returning Values from Loops @@ -1810,6 +1847,7 @@ fn main() { println!("The result is {}", result); } ``` + This prints: `The result is 20`. ### Conditional Loops with `while` @@ -1864,6 +1902,7 @@ fn main() { Loops and recursive functions are conceptually equivalent and compile down to similar low-level representations in Sierra. The following `loop` example: + ```rust #[executable] fn main() -> felt252 { @@ -1878,7 +1917,9 @@ fn main() -> felt252 { x } ``` + Is equivalent to this recursive function: + ```rust #[executable] fn main() -> felt252 { @@ -1893,6 +1934,7 @@ fn recursive_function(mut x: felt252) -> felt252 { } } ``` + Both run until `x == 2` is met. ## Concise Control Flow with `if let` and `while let` @@ -1982,8 +2024,9 @@ fn main() { ## Practice Summary To practice these concepts, try building programs to: -* Generate the $n$-th Fibonacci number. -* Compute the factorial of a number $n$. + +- Generate the $n$-th Fibonacci number. +- Compute the factorial of a number $n$. --- @@ -2058,6 +2101,7 @@ struct User { sign_in_count: u64, } ``` + You can derive multiple traits on structs, such as `Drop`, `PartialEq` for comparison, and `Debug` for debug-printing. #### Instance Creation @@ -2120,6 +2164,7 @@ fn build_user_short(email: ByteArray, username: ByteArray) -> User { User { active: true, username, email, sign_in_count: 1 } } ``` + A new instance can be constructed as the last expression in a function body to implicitly return it. ### Struct Construction Shorthands @@ -2133,6 +2178,7 @@ fn build_user_short(email: ByteArray, username: ByteArray) -> User { User { active: true, username, email, sign_in_count: 1 } } ``` + This is equivalent to `username: username` and `email: email`. #### Struct Update Syntax @@ -2162,6 +2208,7 @@ fn main() { let user2 = User { email: "[email protected]", ..user1 }; } ``` + This syntax uses `=` like an assignment because it moves data. If a field contains a non-`Copy` type (like `ByteArray`), the data for that field is moved from the original instance (`user1`) into the new instance (`user2`), invalidating `user1` for that field. If fields implement `Copy` (like `u64` or `bool`), the values are copied instead. --- @@ -2321,6 +2368,7 @@ fn main() { Variants can be associated with specific data types, which can be the same type for all variants or different types for different variants. Example associating a `u128` value: + ```rust #[derive(Drop)] enum Direction { @@ -2346,6 +2394,7 @@ enum Message { Move: (u128, u128), } ``` + Here, `Quit` has no associated value, `Echo` holds a `felt252`, and `Move` holds a tuple of two `u128` values. ### Trait Implementations for Enums @@ -2410,6 +2459,7 @@ The `match` expression compares a value against a series of patterns and execute An arm consists of a pattern, the `=>` operator, and the code to run. The result of the expression in the matching arm is the result of the entire `match` expression. Example using a simple `Coin` enum: + ```rust enum Coin { Penny, @@ -2475,6 +2525,7 @@ fn value_in_cents(coin: Coin) -> felt252 { } } ``` + When `Coin::Quarter(state)` matches, `state` binds to the inner `UsState` value. ### Matching with `Option` @@ -2520,6 +2571,7 @@ fn vending_machine_accept(coin: Coin) -> bool { } } ``` + Note: There is no catch-all pattern in Cairo that allows you to use the value of the pattern. ### Multiple Patterns with the `|` Operator @@ -2555,11 +2607,13 @@ fn vending_machine_accept(c: (DayType, Coin)) -> bool { } } ``` + The `_` can be used within tuple matching to ignore parts of the tuple. ### Matching `felt252` and Integer Variables Integers fitting into a single `felt252` can be matched, but restrictions apply: + 1. The first arm must start at 0. 2. Each arm must cover a sequential segment contiguously. @@ -2825,6 +2879,7 @@ fn main() { The closure's arguments are placed between pipes (`|`). Types for arguments and the return value are usually inferred from usage, unlike named functions where types form an explicit interface. The body is an expression, optionally enclosed in curly braces `{}` if it spans multiple lines. Type annotations can be added for clarity: + ```cairo fn add_one_v1 (x: u32) -> u32 { x + 1 } let add_one_v2 = |x: u32| -> u32 { x + 1 }; @@ -2844,6 +2899,7 @@ fn main() { let n = example_closure(5_u32); } ``` + The compiler infers `u64` from the first call (`s`), and the second call (`n`) fails because the closure implementation is locked to the `Fn` trait for `u64` arguments. ## Capturing the Environment @@ -2887,6 +2943,7 @@ fn main() { panic(output_array); } ``` + Running this results in `Run panicked with [2, 99 ('c'), ]`. Deserialization can convert the array back into the struct using `Serde::
::deserialize(ref span_array).unwrap()`. ## Serialization Details for Complex Types @@ -2894,25 +2951,27 @@ Running this results in `Run panicked with [2, 99 ('c'), ]`. Deserialization can ### Types with Non-Trivial Serialization Data types using more than 252 bits require special serialization handling: -* Unsigned integers larger than 252 bits: `u256` and `u512`. -* Arrays and spans. -* Enums. -* Structs and tuples. -* Byte arrays (strings). + +- Unsigned integers larger than 252 bits: `u256` and `u512`. +- Arrays and spans. +- Enums. +- Structs and tuples. +- Byte arrays (strings). Basic types are serialized as a single-member list containing one `felt252` value. ### Serialization of `u256` A `u256` value is represented by two `felt252` values: + 1. The 128 least significant bits (low part). 2. The 128 most significant bits (high part). -| Decimal Value | Binary Split (High | Low) | Serialized Array | -| :--- | :--- | :--- | :--- | -| 2 | 0...0 | 0...10 | `[2, 0]` | -| $2^{128}$ | 0...01 | 0...0 | `[0, 1]` | -| $2^{129}+2^{128}+20$ | 0...011 | 0...10100 | `[20, 3]` | +| Decimal Value | Binary Split (High | Low) | Serialized Array | +| :------------------- | :----------------- | :-------- | :--------------- | +| 2 | 0...0 | 0...10 | `[2, 0]` | +| $2^{128}$ | 0...01 | 0...0 | `[0, 1]` | +| $2^{129}+2^{128}+20$ | 0...011 | 0...10100 | `[20, 3]` | ### Serialization of `u512` @@ -2934,11 +2993,13 @@ Total serialization: `[2,0,5,3,1,2,3]`. ### Serialization of Byte Arrays A `ByteArray` (string) is serialized via a struct containing: -* `data: Array`: Contains 31-byte chunks, where each `felt252` holds 31 bytes. -* `pending_word: felt252`: Remaining bytes (at most 30). -* `pending_word_len: usize`: Length of `pending_word` in bytes. + +- `data: Array`: Contains 31-byte chunks, where each `felt252` holds 31 bytes. +- `pending_word: felt252`: Remaining bytes (at most 30). +- `pending_word_len: usize`: Length of `pending_word` in bytes. **Example (`hello`, 5 bytes: `0x68656c6c6f`):** + ``` 0, // Number of 31-byte words in the data array. 0x68656c6c6f, // Pending word @@ -2953,8 +3014,8 @@ Hashing converts input data (message) of any length into a fixed-size hash value The Cairo core library provides two native hash functions: -* **Pedersen**: A cryptographic algorithm based on elliptic curve cryptography and the Elliptic Curve Discrete Logarithm Problem (ECDLP), ensuring one-way security. -* **Poseidon**: Designed for efficiency in algebraic circuits, particularly for Zero-Knowledge proof systems like STARKs. It uses a sponge construction with a three-element state permutation. +- **Pedersen**: A cryptographic algorithm based on elliptic curve cryptography and the Elliptic Curve Discrete Logarithm Problem (ECDLP), ensuring one-way security. +- **Poseidon**: Designed for efficiency in algebraic circuits, particularly for Zero-Knowledge proof systems like STARKs. It uses a sponge construction with a three-element state permutation. ### Using Hash Functions @@ -3062,8 +3123,8 @@ Sources: Cairo uses two macros for printing standard data types: -* `println!`: prints on a new line. -* `print!`: prints inline. +- `println!`: prints on a new line. +- `print!`: prints inline. Both take a `ByteArray` string as the first parameter, which can contain placeholders `{}` for values given as subsequent parameters, or named placeholders `{variable_name}`. @@ -3172,9 +3233,9 @@ Integer values can be printed in hexadecimal format using the `{:x}` notation, w The `Debug` trait allows printing complex data types, especially useful for debugging. It is used by adding `:?` within placeholders (e.g., `println!("{:?}", my_struct)`). -* It is implemented by default for basic data types. -* For complex data types, it can be derived using `#[derive(Debug)]`, provided all contained types also implement `Debug`. -* `assert_xx!` macros in tests require implemented `Debug` traits for provided values. +- It is implemented by default for basic data types. +- For complex data types, it can be derived using `#[derive(Debug)]`, provided all contained types also implement `Debug`. +- `assert_xx!` macros in tests require implemented `Debug` traits for provided values. --- @@ -3223,10 +3284,13 @@ fn main() { ``` Type specification examples: + ```rust let mut arr = ArrayTrait::::new(); ``` + or + ```rust let mut arr:Array = ArrayTrait::new(); ``` @@ -3273,18 +3337,21 @@ For an array of `u256` values like `array![10, 20, POW_2_128]`, where each `u256 An enum is serialized as: `,`. Enum variant indices are 0-based. **Example 1 (`Week` enum):** + ```rust enum Week { Sunday: (), // Index=0. Monday: u256, // Index=1. } ``` -| Instance | Index | Type | Serialization | -| --- | --- | --- | --- | -| `Week::Sunday` | `0` | unit | `[0]` | -| `Week::Monday(5)` | `1` | `u256` | `[1,5,0]` | + +| Instance | Index | Type | Serialization | +| ----------------- | ----- | ------ | ------------- | +| `Week::Sunday` | `0` | unit | `[0]` | +| `Week::Monday(5)` | `1` | `u256` | `[1,5,0]` | **Example 2 (`MessageType` enum):** + ```rust enum MessageType { A, @@ -3293,17 +3360,19 @@ enum MessageType { C } ``` -| Instance | Index | Type | Serialization | -| --- | --- | --- | --- | -| `MessageType::A` | `0` | unit | `[0]` | -| `MessageType::B(6)` | `1` | `u128` | `[1,6]` | -| `MessageType::C` | `2` | unit | `[2]` | + +| Instance | Index | Type | Serialization | +| ------------------- | ----- | ------ | ------------- | +| `MessageType::A` | `0` | unit | `[0]` | +| `MessageType::B(6)` | `1` | `u128` | `[1,6]` | +| `MessageType::C` | `2` | unit | `[2]` | ### String Serialization (Long Strings) For strings longer than 31 bytes, serialization involves 31-byte words, followed by a pending word and its length: For the string `Long string, more than 31 characters.`: + ``` 1, // Number of 31-byte words in the array construct. 0x4c6f6e6720737472696e672c206d6f7265207468616e203331206368617261, // 31-byte word. @@ -3373,17 +3442,18 @@ struct Entry { ``` Every interaction generates a new `Entry`: -* `get`: Registers an entry where `previous_value` equals `new_value`. -* `insert`: Registers an entry where `new_value` is the inserted element, and `previous_value` is the last value associated with the key (or zero if it's the first entry). + +- `get`: Registers an entry where `previous_value` equals `new_value`. +- `insert`: Registers an entry where `new_value` is the inserted element, and `previous_value` is the last value associated with the key (or zero if it's the first entry). This approach avoids rewriting memory cells, instead creating new entries for every access. For example, sequential operations on 'Alex' (insert 100, insert 200) and 'Maria' (insert 50, then read) yield entries like: -| key | previous | new | -| --- | --- | --- | -| Alex | 0 | 100 | -| Maria | 0 | 50 | -| Alex | 100 | 200 | -| Maria | 50 | 50 | +| key | previous | new | +| ----- | -------- | --- | +| Alex | 0 | 100 | +| Maria | 0 | 50 | +| Alex | 100 | 200 | +| Maria | 50 | 50 | ### Dictionary Destruction and Squashing @@ -3462,15 +3532,17 @@ Sources: # Ownership Model and Type Traits -Cairo utilizes a linear type system where every value must be used exactly once, either by being *destroyed* or *moved*. This system statically ensures that operations that could cause runtime errors, such as writing twice to the same memory cell, are caught at compile time. +Cairo utilizes a linear type system where every value must be used exactly once, either by being _destroyed_ or _moved_. This system statically ensures that operations that could cause runtime errors, such as writing twice to the same memory cell, are caught at compile time. ## Linear Type System and Usage In a linear type system, any value must be used once. "Used" means the value is either destroyed or moved: -* **Destruction** occurs when a variable goes out of scope, is destructured, or explicitly destroyed via `destruct()`. -* **Moving** a value means passing it to another function. -Cairo's ownership model focuses on *variables*, not values, because the underlying memory is immutable. A value can be safely referred to by many variables, but variables themselves follow strict ownership rules: +- **Destruction** occurs when a variable goes out of scope, is destructured, or explicitly destroyed via `destruct()`. +- **Moving** a value means passing it to another function. + +Cairo's ownership model focuses on _variables_, not values, because the underlying memory is immutable. A value can be safely referred to by many variables, but variables themselves follow strict ownership rules: + 1. Each variable has exactly one owner at any time. 2. When the owner goes out of scope, the variable is destroyed. @@ -3493,9 +3565,9 @@ fn main() { The `Copy` trait allows simple types to be duplicated by copying felts without allocating new memory segments, bypassing default move semantics. -* Basic types implement `Copy` by default. -* Types like `Array` and `Felt252Dict` cannot implement `Copy` because manipulating them across different scopes is forbidden by the type system. -* Custom types that do not contain non-Copy components can implement `Copy` using `#[derive(Copy)]`. +- Basic types implement `Copy` by default. +- Types like `Array` and `Felt252Dict` cannot implement `Copy` because manipulating them across different scopes is forbidden by the type system. +- Custom types that do not contain non-Copy components can implement `Copy` using `#[derive(Copy)]`. If a type implements `Copy`, passing the variable transfers a copy, and the original variable remains valid, retaining ownership. For example, with a `Point` struct implementing `Copy`: @@ -3516,6 +3588,7 @@ fn main() { fn foo(p: Point) { // do something with p } ``` + If `Copy` is not derived, attempting to pass `p1` twice results in a compile-time error. ## Cloning and Return Values @@ -3530,7 +3603,7 @@ fn main() { } ``` -Returning a value from a function is equivalent to *moving* it to the caller. The following example illustrates scope and moves: +Returning a value from a function is equivalent to _moving_ it to the caller. The following example illustrates scope and moves: ```cairo #[derive(Drop)] @@ -3573,9 +3646,10 @@ fn takes_and_gives_back(some_a: A) -> A { // some_a comes into scope ## Passing Variables to Functions When passing a variable to a function, ownership rules dictate how the variable can be used afterward: -* **Pass-by-value**: Ownership of the variable is transferred to the function. -* **Snapshot (`@`)**: Ownership is retained by the caller, and the function receives an immutable view. -* **Mutable Reference (`ref`)**: Ownership is retained by the caller, and the function receives a mutable view. + +- **Pass-by-value**: Ownership of the variable is transferred to the function. +- **Snapshot (`@`)**: Ownership is retained by the caller, and the function receives an immutable view. +- **Mutable Reference (`ref`)**: Ownership is retained by the caller, and the function receives a mutable view. --- @@ -3598,7 +3672,7 @@ The `get` function returns an `Option>`. This returns a snapshot of the Here is an example with the `get()` method: -```\n#[executable]\nfn main() -> u128 {\n let mut arr = ArrayTrait::::new();\n arr.append(100);\n let index_to_access =\n 1; // Change this value to see different results, what would happen if the index doesn't exist?\n match arr.get(index_to_access) {\n Some(x) => {\n *x\n .unbox() // Don't worry about * for now, if you are curious see Chapter 4.2 #desnap operator\n // It basically means "transform what get(idx) returned into a real value"\n },\n None => { panic!("out of bounds") },\n }\n}\n``` +`\n#[executable]\nfn main() -> u128 {\n let mut arr = ArrayTrait::::new();\n arr.append(100);\n let index_to_access =\n 1; // Change this value to see different results, what would happen if the index doesn't exist?\n match arr.get(index_to_access) {\n Some(x) => {\n *x\n .unbox() // Don't worry about * for now, if you are curious see Chapter 4.2 #desnap operator\n // It basically means "transform what get(idx) returned into a real value"\n },\n None => { panic!("out of bounds") },\n }\n}\n` ### Snapshots @@ -3608,19 +3682,21 @@ Accessing fields of a snapshot yields snapshots of those fields, which must be d Attempting to modify values associated with snapshots results in a compiler error: -```\n#[derive(Copy, Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let rec = Rectangle { height: 3, width: 10 };\n flip(@rec);\n}\n\nfn flip(rec: @Rectangle) {\n let temp = rec.height;\n rec.height = rec.width;\n rec.width = temp;\n}\n```\n +`\n#[derive(Copy, Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let rec = Rectangle { height: 3, width: 10 };\n flip(@rec);\n}\n\nfn flip(rec: @Rectangle) {\n let temp = rec.height;\n rec.height = rec.width;\n rec.width = temp;\n}\n`\n + #### Desnap Operator The desnap operator `*` is the opposite of the `@` operator, used to convert a snapshot back into a regular variable. This is only possible for `Copy` types, and it is a completely free operation. -```\n#[derive(Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let rec = Rectangle { height: 3, width: 10 };\n let area = calculate_area(@rec);\n println!("Area: {}", area);\n}\n\nfn calculate_area(rec: @Rectangle) -> u64 {\n // As rec is a snapshot to a Rectangle, its fields are also snapshots of the fields types.\n // We need to transform the snapshots back into values using the desnap operator `*`.\n // This is only possible if the type is copyable, which is the case for u64.\n // Here, `*` is used for both multiplying the height and width and for desnapping the snapshots.\n *rec.height * *rec.width\n}\n```\n +``\n#[derive(Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let rec = Rectangle { height: 3, width: 10 };\n let area = calculate_area(@rec);\n println!("Area: {}", area);\n}\n\nfn calculate_area(rec: @Rectangle) -> u64 {\n // As rec is a snapshot to a Rectangle, its fields are also snapshots of the fields types.\n // We need to transform the snapshots back into values using the desnap operator `*`.\n // This is only possible if the type is copyable, which is the case for u64.\n // Here, `*` is used for both multiplying the height and width and for desnapping the snapshots.\n *rec.height * *rec.width\n}\n``\n + ### Mutable References -To allow mutation while keeping ownership in the calling context, a *mutable reference* is used with the `ref` modifier. The variable must be declared mutable (`mut`) in the caller. Like snapshots, `ref` arguments are passed by value (copied) to the function, ensuring the function operates on a local version which is implicitly returned at the end of execution. +To allow mutation while keeping ownership in the calling context, a _mutable reference_ is used with the `ref` modifier. The variable must be declared mutable (`mut`) in the caller. Like snapshots, `ref` arguments are passed by value (copied) to the function, ensuring the function operates on a local version which is implicitly returned at the end of execution. In Listing 4-5, a mutable reference swaps fields: -```\n#[derive(Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let mut rec = Rectangle { height: 3, width: 10 };\n flip(ref rec);\n println!("height: {}, width: {}", rec.height, rec.width);\n}\n\nfn flip(ref rec: Rectangle) {\n let temp = rec.height;\n rec.height = rec.width;\n rec.width = temp;\n}\n``` +`\n#[derive(Drop)]\nstruct Rectangle {\n height: u64,\n width: u64,\n}\n\n#[executable]\nfn main() {\n let mut rec = Rectangle { height: 3, width: 10 };\n flip(ref rec);\n println!("height: {}, width: {}", rec.height, rec.width);\n}\n\nfn flip(ref rec: Rectangle) {\n let temp = rec.height;\n rec.height = rec.width;\n rec.width = temp;\n}\n` --- @@ -3787,15 +3863,16 @@ Sources: ## Smart Pointers Overview -A pointer is a variable holding a memory address. To prevent bugs and security vulnerabilities associated with unsafe memory access, Cairo uses *Smart Pointers*. These act like pointers but include extra metadata and capabilities, ensuring memory is accessed safely via strict type checking and ownership rules. Types like `Felt252Dict` and `Array` are examples of smart pointers, as they own a memory segment and manage access (e.g., arrays track their length). When an array is created, a new segment is allocated for its elements, and the array itself holds a pointer to that segment. +A pointer is a variable holding a memory address. To prevent bugs and security vulnerabilities associated with unsafe memory access, Cairo uses _Smart Pointers_. These act like pointers but include extra metadata and capabilities, ensuring memory is accessed safely via strict type checking and ownership rules. Types like `Felt252Dict` and `Array` are examples of smart pointers, as they own a memory segment and manage access (e.g., arrays track their length). When an array is created, a new segment is allocated for its elements, and the array itself holds a pointer to that segment. ## The `Box` Type -The principal smart pointer in Cairo is `Box`. Instantiating `Box` stores the data of type `T` in a dedicated memory area called the *boxed segment*. The execution segment only holds a pointer to this boxed data. +The principal smart pointer in Cairo is `Box`. Instantiating `Box` stores the data of type `T` in a dedicated memory area called the _boxed segment_. The execution segment only holds a pointer to this boxed data. ### Storing Data in the Boxed Segment Boxes have minimal performance overhead but are useful when: + 1. The size of a type is unknown at compile time, and a fixed size is required. 2. Transferring ownership of large data volumes, where copying the data is slow. Storing large data in a box means only the small pointer is copied during ownership transfer. @@ -3808,6 +3885,7 @@ fn main() { println!("b = {}", b.unbox()) } ``` + Listing 12-1: Storing a `u128` value in the boxed segment using a box When instantiated, the value is stored in the boxed segment, and `b.unbox()` accesses it. @@ -3858,6 +3936,7 @@ fn main() { println!("{:?}", root); } ``` + Listing 12-3: Defining a recursive Binary Tree using Boxes The `Node` variant now holds `(u32, Box, Box)`, allowing the compiler to calculate the size. @@ -3892,6 +3971,7 @@ fn main() { pass_pointer(new_box); } ``` + Listing 12-4: Storing large amounts of data in a box for performance. When calling `pass_data`, the entire `Cart` struct is copied. When calling `pass_pointer`, only the pointer stored in `new_box` is copied. If the data in the box is mutated, a new `Box` will be created, requiring the data to be copied into the new box. @@ -3921,6 +4001,7 @@ fn main() { //... ``` + This approach is necessary because `Array` does not implement the `Copy` trait required for reading from a dictionary, whereas `Span` does. --- @@ -3974,10 +4055,10 @@ As programs grow, organizing code by grouping related functionality into modules Cairo uses several concepts to manage large codebases: -* **Packages:** A Scarb feature enabling building, testing, and sharing crates. -* **Crates:** A tree of modules forming a single compilation unit. A crate has a root directory and a root module defined in `lib.cairo` within that directory. -* **Modules and `use`:** Control the organization and scope of items. -* **Paths:** Name items within the module tree. +- **Packages:** A Scarb feature enabling building, testing, and sharing crates. +- **Crates:** A tree of modules forming a single compilation unit. A crate has a root directory and a root module defined in `lib.cairo` within that directory. +- **Modules and `use`:** Control the organization and scope of items. +- **Paths:** Name items within the module tree. A related concept is **scope**, the context where code is written, which defines which names are available. You cannot have two items with the same name in the same scope. @@ -3989,7 +4070,7 @@ Modules let us organize code within a crate for readability and control privacy. Module documentation comments provide an overview of the entire module, prefixed with `//!`, and are placed above the module they describe. -```cairo +````cairo //! # my_module and implementation //! //! This is an example description of my_module and some of its features. @@ -4007,7 +4088,7 @@ Module documentation comments provide an overview of the entire module, prefixed //! ``` mod my_module { // rest of implementation... } -``` +```` ### Modules Cheat Sheet (Rules for Organization) @@ -4015,11 +4096,11 @@ When organizing code, the compiler follows these rules: 1. **Start from the crate root:** Compilation begins in the crate root file (`src/lib.cairo`). 2. **Declaring modules (in crate root):** Use `mod garden;`. The compiler looks: - * Inline, within curly brackets replacing the semicolon. - * In the file `src/garden.cairo`. + - Inline, within curly brackets replacing the semicolon. + - In the file `src/garden.cairo`. 3. **Declaring submodules (in files other than crate root):** Use `mod vegetables;` in `src/garden.cairo`. The compiler looks: - * Inline, directly following `mod vegetables`, within curly brackets. - * In the file `src/garden/vegetables.cairo`. + - Inline, directly following `mod vegetables`, within curly brackets. + - In the file `src/garden/vegetables.cairo`. 4. **Paths to code in modules:** Items are referenced using paths, e.g., `crate::garden::vegetables::Asparagus`. 5. **Private vs public:** Code is private by default. Use `pub mod` to make a module public. Use `pub` before item declarations to make them public. `pub(crate)` restricts visibility to within the defining crate. 6. **The `use` keyword:** Creates shortcuts to paths within a scope. @@ -4030,8 +4111,8 @@ The module tree structure mirrors the filesystem directory tree. The crate root When modules grow, their definitions can be moved to separate files. -* If `mod front_of_house;` is declared in the crate root (`src/lib.cairo`), the compiler looks for its content in `src/front_of_house.cairo`. -* For a submodule like `pub mod hosting;` declared in `src/front_of_house.cairo`, the compiler looks for its content in a directory named after the parent, i.e., `src/front_of_house/hosting.cairo`. +- If `mod front_of_house;` is declared in the crate root (`src/lib.cairo`), the compiler looks for its content in `src/front_of_house.cairo`. +- For a submodule like `pub mod hosting;` declared in `src/front_of_house.cairo`, the compiler looks for its content in a directory named after the parent, i.e., `src/front_of_house/hosting.cairo`. The `mod` keyword declares modules and tells Cairo where to look; it is not an "include" operation. @@ -4040,8 +4121,9 @@ The `mod` keyword declares modules and tells Cairo where to look; it is not an " Paths name items in the module tree, similar to filesystem navigation. A path can be: -* **Absolute path:** Starts from the crate root, beginning with the crate name. -* **Relative path:** Starts from the current module. + +- **Absolute path:** Starts from the crate root, beginning with the crate name. +- **Relative path:** Starts from the current module. Both forms use double colons (`::`) as separators. @@ -4096,12 +4178,13 @@ By default, code within a module is private to its parent modules. Items in chil To expose items: -* Mark the module declaration with `pub`. Making a module public only allows access to the module itself, not its contents. -* Mark items within the module with `pub`. +- Mark the module declaration with `pub`. Making a module public only allows access to the module itself, not its contents. +- Mark items within the module with `pub`. For structs and enums: -* `pub struct` makes the struct public, but its fields remain private unless explicitly marked `pub`. -* `pub enum` makes the enum and all its variants public. + +- `pub struct` makes the struct public, but its fields remain private unless explicitly marked `pub`. +- `pub enum` makes the enum and all its variants public. To allow an item to be visible only within the defining crate, use `pub(crate)`. @@ -4212,6 +4295,7 @@ fn eat_at_restaurant() { hosting::add_to_waitlist(); } ``` + This allows external code to use `crate::hosting::add_to_waitlist()` instead of the longer internal path. --- @@ -4236,8 +4320,8 @@ Generics in Cairo provide tools to handle the duplication of concepts by using a Generics are defined using angle brackets `<...>` in various declarations: -* **Definition**: `fn ident<...> ...`, `struct ident<...> ...`, `enum ident<...> ...`, `impl<...> ...` -* **Specification**: `path::<...>` or `method::<...>` (turbofish) is used to specify parameters in an expression. +- **Definition**: `fn ident<...> ...`, `struct ident<...> ...`, `enum ident<...> ...`, `impl<...> ...` +- **Specification**: `path::<...>` or `method::<...>` (turbofish) is used to specify parameters in an expression. ### Generic Functions @@ -4434,6 +4518,7 @@ A trait defines a set of methods that can be implemented by a type, grouping met To declare a trait, use the `trait` keyword followed by the name. If it needs to be accessible by other crates, declare it as `pub`. Inside, declare method signatures ending with a semicolon. If the trait is generic, it includes a generic type parameter, like `pub trait Summary { ... }`. **Example of a non-generic trait definition:** + ```cairo #[derive(Drop, Clone)] struct NewsArticle { @@ -4459,6 +4544,7 @@ impl NewsArticleSummary of Summary { Implementing a trait on a type involves using `impl ImplementationName of TraitName` followed by the type parameter in angle brackets (if generic). Within the block, method signatures are provided with their concrete implementation bodies. **Example of implementing a generic trait:** + ```cairo mod aggregator { pub trait Summary { @@ -4530,6 +4616,7 @@ Methods are defined within `impl` blocks, which organize capabilities associated To avoid manually defining a trait when it's only intended for implementation on a specific type, Cairo provides the `#[generate_trait]` attribute above the implementation block. This tells the compiler to generate the corresponding trait definition automatically. **Example using `#[generate_trait]`:** + ```cairo #[derive(Copy, Drop)] struct Rectangle { @@ -4556,6 +4643,7 @@ fn main() { Traits can provide default behavior for methods. Implementors can either use the default by providing an empty `impl` block or override it by providing a custom implementation for that method signature. **Example with default implementation:** + ```cairo mod aggregator { pub trait Summary { @@ -4594,6 +4682,7 @@ fn main() { println!("1 new tweet: {}", tweet.summarize()); } ``` + In this case, `summarize` uses its default implementation because only `summarize_author` was implemented. ### Derivable Traits @@ -4601,6 +4690,7 @@ In this case, `summarize` uses its default implementation because only `summariz The `derive` attribute generates code to implement a default trait on a type. Traits compatible with `derive` in the standard library include `Clone` (for deep copying) and `Drop` (for automatic resource cleanup like squashing Dictionaries via the `Destruct` trait). **Example deriving `Clone` and `Drop`:** + ```cairo #[derive(Clone, Drop)] struct A { @@ -4847,16 +4937,18 @@ When called as `make_array![1, 2, 3]`, the `$x` pattern matches `1`, `2`, and `3 #### Macro Pattern Syntax Macro patterns are matched against Cairo source code structure. Key components include: -* Parentheses enclose the whole matcher pattern. -* `$dollar_sign` introduces a macro variable that captures matching code. `$x:expr` matches any Cairo expression and names it `$x`. -* A comma following `$()` requires literal commas between matched expressions. -* The `*` quantifier specifies the subpattern can repeat zero or more times. + +- Parentheses enclose the whole matcher pattern. +- `$dollar_sign` introduces a macro variable that captures matching code. `$x:expr` matches any Cairo expression and names it `$x`. +- A comma following `$()` requires literal commas between matched expressions. +- The `*` quantifier specifies the subpattern can repeat zero or more times. #### Hygiene: `$defsite`, `$callsite`, and `expose!` Cairo’s inline macros are hygienic, meaning names introduced in the definition do not leak into the call site unless explicitly exposed. Name resolution references the definition site or call site using `$defsite::` and `$callsite::`. To use user-defined inline macros, you must enable the experimental feature in `Scarb.toml`: + ```toml # [package] # name = "listing_inline_macros" @@ -4880,6 +4972,7 @@ experimental-features = ["user_defined_inline_macros"] # [tool.scarb] # allow-prebuilt-plugins = ["snforge_std"] ``` + Macros are expected to expand to a single expression; wrapping multiple statements in `{}` achieves this. The following example demonstrates hygiene and name resolution: @@ -4968,10 +5061,12 @@ fn test_hygiene_e2e() { assert_eq!(w, 8); } ``` + This demonstrates: -* `$defsite::...` resolves to items next to the macro definition. -* `$callsite::...` resolves to items visible where the macro is invoked. -* `expose!` deliberately introduces new items into the call site, accessible via `$callsite::name`. + +- `$defsite::...` resolves to items next to the macro definition. +- `$callsite::...` resolves to items visible where the macro is invoked. +- `expose!` deliberately introduces new items into the call site, accessible via `$callsite::name`. ### Procedural Macros @@ -4980,27 +5075,29 @@ Procedural macros are Rust functions that transform Cairo code. They are preferr #### Procedural Macro Types and Signatures Macros are defined using one of three attributes: -* `#[inline_macro]`: For function-like calls (e.g., `println!()`). - ```c - #[inline_macro] - pub fn inline(code: TokenStream) -> ProcMacroResult {} - ``` -* `#[attribute_macro]`: For custom attributes. - ```c - #[attribute_macro] - pub fn attribute(attr: TokenStream, code: TokenStream) -> ProcMacroResult {} - ``` -* `#[derive_macro]`: For automatic trait implementations. - ```c - #[derive_macro] - pub fn derive(code: TokenStream) -> ProcMacroResult {} - ``` + +- `#[inline_macro]`: For function-like calls (e.g., `println!()`). + ```c + #[inline_macro] + pub fn inline(code: TokenStream) -> ProcMacroResult {} + ``` +- `#[attribute_macro]`: For custom attributes. + ```c + #[attribute_macro] + pub fn attribute(attr: TokenStream, code: TokenStream) -> ProcMacroResult {} + ``` +- `#[derive_macro]`: For automatic trait implementations. + ```c + #[derive_macro] + pub fn derive(code: TokenStream) -> ProcMacroResult {} + ``` #### Project Setup and Dependencies Creating a procedural macro requires a Rust project structure with `Cargo.toml` (defining `crate-type = ["cdylib"]` and `cairo-lang-macro` dependency) and a Cairo project structure with `Scarb.toml` marking the package as a plugin: `Cargo.toml` example: + ```toml [package] name = "pow" @@ -5021,6 +5118,7 @@ cairo-lang-syntax = "2.12.0" ``` `Scarb.toml` example for the macro package: + ```toml [package] name = "pow" @@ -5068,6 +5166,7 @@ pub fn hello_macro(token_stream: TokenStream) -> ProcMacroResult { "#, struct_name})) } ``` + The trait `Hello` must be defined or imported in the consuming code. #### Attribute Macros @@ -5089,7 +5188,9 @@ pub fn rename(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult )) } ``` + Usage example in a Cairo file: + ```c #[executable] fn main() { @@ -5430,6 +5531,7 @@ Components provide powerful modularity to Starknet contracts. This section dives An impl of a Starknet interface trait (marked with `#[starknet::interface]`) can be made embeddable. Embeddable impls can be injected into any contract, adding new entry points and modifying the ABI of the contract. Example demonstrating embeddable impls: + ```rust #[starknet::interface] trait SimpleTrait { @@ -5452,6 +5554,7 @@ mod simple_contract { impl MySimpleImpl = super::SimpleImpl; } ``` + By embedding `SimpleImpl`, we externally expose `ret4` in the contract's ABI. ### Inside Components: Generic Impls @@ -5466,9 +5569,10 @@ Components build upon this embedding mechanism using generic impls, as seen in t ``` The key points are: -* The generic impl `Ownable` requires the implementation of the `HasComponent` trait by the underlying contract. -* This trait is automatically generated with the `component!()` macro when using a component inside a contract. -* The compiler will generate an embeddable impl that wraps any function in `Ownable`. + +- The generic impl `Ownable` requires the implementation of the `HasComponent` trait by the underlying contract. +- This trait is automatically generated with the `component!()` macro when using a component inside a contract. +- The compiler will generate an embeddable impl that wraps any function in `Ownable`. --- @@ -5487,9 +5591,10 @@ Inlining replaces a function call with the body of the called function at the ca In Cairo, the `inline` attribute suggests whether the Sierra code should be injected directly into the caller's context instead of using a `function_call` libfunc. The variants are: -* `#[inline]`: Suggests performing an inline expansion. -* `#[inline(always)]`: Suggests an inline expansion should always be performed. -* `#[inline(never)]`: Suggests that an inline expansion should never be performed. + +- `#[inline]`: Suggests performing an inline expansion. +- `#[inline(always)]`: Suggests an inline expansion should always be performed. +- `#[inline(never)]`: Suggests that an inline expansion should never be performed. Note that the attribute is only a hint and may be ignored by the compiler, although `#[inline(always)]` is rarely ignored. Annotating functions with `#[inline(always)]` reduces the total number of steps required by avoiding the step-cost associated with function calls and argument handling. @@ -5500,24 +5605,27 @@ For functions without explicit inline directives, the compiler uses a heuristic The compiler calculates a function's complexity using the `ApproxCasmInlineWeight` struct, which estimates the generated Cairo Assembly (CASM) statements. If this weight falls below the threshold, the function is inlined. The raw statement count is also considered. Special cases are handled: -* Very simple functions (e.g., returning a constant or calling another function) are always inlined. -* Functions with complex control flow (like `Match`) or those ending with `Panic` are generally not inlined. + +- Very simple functions (e.g., returning a constant or calling another function) are always inlined. +- Functions with complex control flow (like `Match`) or those ending with `Panic` are generally not inlined. ### Performance Tradeoffs and Code Size Inlining presents a trade-off between the number of execution steps and code length. -* **Benefit:** More frequent calls benefit more from inlining as the step count decreases. -* **Drawback:** Inlining large functions or using `#[inline]` or `#[inline(always)]` indiscriminately increases compile time and code length due to duplication of Sierra code at every call site. + +- **Benefit:** More frequent calls benefit more from inlining as the step count decreases. +- **Drawback:** Inlining large functions or using `#[inline]` or `#[inline(always)]` indiscriminately increases compile time and code length due to duplication of Sierra code at every call site. Inlining is particularly useful for small functions, ideally those with many arguments. In cases where the return value of an inlined function is unused, the compiler may further optimize by omitting the inlined code entirely, reducing both code length and steps, as demonstrated in Listing 12-6. ### Inlining Mechanics Illustrated (Sierra and Casm) -When a function is *not* inlined (e.g., `not_inlined`), the Sierra code uses the `function_call` libfunc. When it *is* inlined (e.g., `inlined`), its Sierra statements are injected directly into the caller's sequence of statements, using different variable IDs to avoid conflicts. +When a function is _not_ inlined (e.g., `not_inlined`), the Sierra code uses the `function_call` libfunc. When it _is_ inlined (e.g., `inlined`), its Sierra statements are injected directly into the caller's sequence of statements, using different variable IDs to avoid conflicts. In the Casm example (Listing 12-5), the non-inlined function involves `call rel X` and `ret` instructions to manage the function stack. The inlined function body, however, appears directly in the sequence without any `call` instruction, leading to fewer total instructions executed for that part of the logic. **Example Code (Listing 12-5):** + ```rust #[executable] fn main() -> felt252 { @@ -5536,6 +5644,7 @@ fn not_inlined() -> felt252 { ``` **Example Code (Listing 12-6):** + ```rust #[executable] fn main() { @@ -5572,10 +5681,10 @@ Arithmetic circuits in Cairo, particularly when emulating zk-SNARKs features, in Circuit elements can be combined using arithmetic operations. For instance, combining `CircuitElement` and `CircuitElement` with an addition gate results in `CircuitElement>`. Direct combination functions are available: -* `circuit_add` -* `circuit_sub` -* `circuit_mul` -* `circuit_inverse` +- `circuit_add` +- `circuit_sub` +- `circuit_mul` +- `circuit_inverse` Note that `CircuitElement` where $T$ is an input or gate type, represents the circuit description, while `CircuitElement<{}>` (empty struct) represents a signal or intermediate element. @@ -5685,9 +5794,10 @@ Sources: ### Macro Implementation Details The core code for macro implementation is written in Rust and relies on three primary Rust crates: -* `cairo_lang_macro`: Specific to macro implementation. -* `cairo_lang_parser`: Used for compiler parser functions. -* `cairo_lang_syntax`: Related to the compiler syntax. + +- `cairo_lang_macro`: Specific to macro implementation. +- `cairo_lang_parser`: Used for compiler parser functions. +- `cairo_lang_syntax`: Related to the compiler syntax. Since macro functions operate at the Cairo syntax level, logic from the syntax functions created for the Cairo compiler can be reused directly. For deeper understanding of concepts like the Cairo parser or syntax, consulting the Cairo compiler workshop is recommended. @@ -5780,23 +5890,29 @@ Sources: ### Introduction to Cairo and Starknet Architecture #### Smart Contract Fundamentals -Smart contracts are characterized as **permissionless** and **transparent**, allowing for **composability** where contracts interact with one another. They are restricted to accessing data only from their deployment blockchain, requiring external software called *oracles* to fetch external data (e.g., token prices). Common development standards include `ERC20` (for tokens like USDC) and `ERC721` (for NFTs like CryptoPunks). Primary use cases currently center around DeFi (decentralized finance applications like lending/DEXs) and Tokenization. + +Smart contracts are characterized as **permissionless** and **transparent**, allowing for **composability** where contracts interact with one another. They are restricted to accessing data only from their deployment blockchain, requiring external software called _oracles_ to fetch external data (e.g., token prices). Common development standards include `ERC20` (for tokens like USDC) and `ERC721` (for NFTs like CryptoPunks). Primary use cases currently center around DeFi (decentralized finance applications like lending/DEXs) and Tokenization. #### Cairo and Starknet Architecture + Cairo is a language developed specifically for STARKs, enabling the writing of **provable code** to verify computation correctness between states. Starknet utilizes its own Virtual Machine (VM) instead of the EVM, which offers benefits like decreased transaction costs, native account abstraction ("Smart Accounts"), and support for emerging use cases such as **transparent AI** and fully **on-chain blockchain games**. #### Cairo Programs vs. Starknet Smart Contracts + Starknet contracts are a superset of Cairo programs. A standard Cairo program requires a `main` function as its entry point. In contrast, contracts deployed on Starknet lack a `main` function but possess one or multiple entry points. For the compiler to recognize a module as a Starknet contract, it must be annotated with the `#[starknet::contract]` attribute. #### Contract Address and State + A contract instance maintains a **nonce**, representing the number of transactions originating from that address plus one. The **Contract Address** is a unique identifier calculated as a chain hash (using `pedersen`) of several components: -* `prefix`: ASCII encoding of the constant string `STARKNET_CONTRACT_ADDRESS`. -* `deployer_address`: Determined by the deployment transaction type (e.g., 0 for `DEPLOY_ACCOUNT`). -* `salt`: A value ensuring unique addresses and thwarting replay attacks. -* `class_hash`. -* `constructor_calldata_hash`: Array hash of constructor inputs. + +- `prefix`: ASCII encoding of the constant string `STARKNET_CONTRACT_ADDRESS`. +- `deployer_address`: Determined by the deployment transaction type (e.g., 0 for `DEPLOY_ACCOUNT`). +- `salt`: A value ensuring unique addresses and thwarting replay attacks. +- `class_hash`. +- `constructor_calldata_hash`: Array hash of constructor inputs. The computation follows: + ``` contract_address = pedersen( “STARKNET_CONTRACT_ADDRESS”, @@ -5807,6 +5923,7 @@ contract_address = pedersen( ``` #### Starknet Types: ContractAddress + The `ContractAddress` type represents the unique address of a deployed contract on Starknet, essential for cross-contract calls and access control checks. ```rust @@ -5873,19 +5990,21 @@ Contract classes are identified uniquely by their **class hash**. To introduce n A contract class is defined by several components whose hashes contribute to the final class hash: -| Name | Notes | -| :--- | :--- | -| Contract class version | Currently supported version is 0.1.0. | -| Array of external functions entry points | Defined by `(_selector_, _function_idx_)`. Selector is `starknet_keccak` hash of the function name. | -| Array of L1 handlers entry points | - | -| Array of constructors entry points | The compiler currently allows only one constructor. | -| ABI | A string supplied by the user declaring the class. The ABI hash is `starknet_keccak(bytes(ABI, "UTF-8"))`. | -| Sierra program | An array of field elements representing the Sierra instructions. | +| Name | Notes | +| :--------------------------------------- | :--------------------------------------------------------------------------------------------------------- | +| Contract class version | Currently supported version is 0.1.0. | +| Array of external functions entry points | Defined by `(_selector_, _function_idx_)`. Selector is `starknet_keccak` hash of the function name. | +| Array of L1 handlers entry points | - | +| Array of constructors entry points | The compiler currently allows only one constructor. | +| ABI | A string supplied by the user declaring the class. The ABI hash is `starknet_keccak(bytes(ABI, "UTF-8"))`. | +| Sierra program | An array of field elements representing the Sierra instructions. | The **class hash** is computed using the Poseidon hash function ($h$): + $$ \text{class\_hash} = h(\text{contract\_class\_version}, \text{external\_entry\_points}, \text{l1\_handler\_entry\_points}, \text{constructor\_entry\_points}, \text{abi\_hash}, \text{sierra\_program\_hash}) $$ + The hash of an entry point array is $h(\text{selector}_1,\text{index}_1,...,\text{selector}_n,\text{index}_n)$. The version hash uses the ASCII encoding of the string `CONTRACT_V0.1.0`. #### Contract Interfaces @@ -5893,21 +6012,23 @@ The hash of an entry point array is $h(\text{selector}_1,\text{index}_1,...,\tex Interfaces define the blueprint of a contract, specifying exposed functions without bodies. They are defined by annotating a trait with `#[starknet::interface]`. All functions in the trait are public. The `self` parameter in function signatures determines state access: -* `ref self: TContractState`: Allows modification of the contract's storage variables. -* `self: @TContractState`: Takes a snapshot, preventing state modification (the compiler enforces this). + +- `ref self: TContractState`: Allows modification of the contract's storage variables. +- `self: @TContractState`: Takes a snapshot, preventing state modification (the compiler enforces this). The implementation must conform to the interface, or a compilation error results. #### Constructors and Public Functions 1. **Constructors**: Run once upon deployment to initialize state. - * Must be named `constructor` and annotated with `#[constructor]`. - * Must take `self` as the first argument, usually with the `ref` keyword to allow state modification. - * A contract can have only one constructor. + + - Must be named `constructor` and annotated with `#[constructor]`. + - Must take `self` as the first argument, usually with the `ref` keyword to allow state modification. + - A contract can have only one constructor. 2. **Public Functions**: Accessible from outside the contract. - * Defined inside an implementation block annotated with `#[abi(embed_v0)]`, which embeds the functions as implementations of the Starknet interface and potential entry points. - * Alternatively, they can be defined independently using the `#[external(v0)]` attribute. + - Defined inside an implementation block annotated with `#[abi(embed_v0)]`, which embeds the functions as implementations of the Starknet interface and potential entry points. + - Alternatively, they can be defined independently using the `#[external(v0)]` attribute. #### Upgradeability @@ -5982,18 +6103,21 @@ Contracts use interfaces, defined with `#[starknet::interface]`, as blueprints f Functions can be classified based on their visibility and how they handle the contract state (`ContractState`): ##### External Functions (State Modifying) + External functions are public functions exposed to the outside world that can mutate state. They must take `self` as the first argument, passed by reference using the `ref` keyword, granting read and write access to storage variables. ##### View Functions (Read-Only by Convention) + View functions are public, typically read-only functions. They must take `self` as the first argument, passed as a snapshot using the `@` modifier. This restricts storage write access via `self` at compile time. ##### State Mutability Warning -The read-only property of view functions is enforced only by the compiler. Starknet does not enforce this limitation; a transaction targeting a view function *can* change the state via direct system calls or calls to other contracts. Developers must not assume view functions are side-effect free. + +The read-only property of view functions is enforced only by the compiler. Starknet does not enforce this limitation; a transaction targeting a view function _can_ change the state via direct system calls or calls to other contracts. Developers must not assume view functions are side-effect free. ##### Other Function Types -* **Standalone Public Functions**: Public functions defined outside an implementation block using the `#[external(v0)]` attribute. They must take `self` as the first parameter. -* **Private (Internal) Functions**: Functions not exposed externally. They can only be called from within the contract. They can be grouped using `#[generate_trait]` or defined as free functions within the module. Private functions do not require `self` as the first argument. +- **Standalone Public Functions**: Public functions defined outside an implementation block using the `#[external(v0)]` attribute. They must take `self` as the first parameter. +- **Private (Internal) Functions**: Functions not exposed externally. They can only be called from within the contract. They can be grouped using `#[generate_trait]` or defined as free functions within the module. Private functions do not require `self` as the first argument. The `NameRegistry` contract demonstrates these patterns, including using `Map` storage types, constructor logic, public functions within an `impl` block, a standalone public function (`get_contract_name`), and internal functions grouped via `#[generate_trait]`. @@ -6086,6 +6210,7 @@ Sources: Events inform the outside world of changes during execution and are stored in transaction receipts. #### Defining Events + Events are defined in an enum annotated with `#[event]`, which must be named `Event`. Each variant represents an event, and its data structure must derive `starknet::Event`. ```rust @@ -6116,9 +6241,11 @@ mod EventExample { ``` #### Event Data Fields and Keys + Each event data field can be annotated with `#[key]`. Key fields are stored separately for efficient filtering by external tools. The variant name is used internally as the first event key. #### Flattening Nested Events with `#[flat]` + The `#[flat]` attribute can be used on an enum variant to flatten serialization. If `FieldUpdated` is annotated with `#[flat]`, emitting `FieldUpdated::Title(...)` results in the event name being `Title` instead of `FieldUpdated`. ```rust @@ -6131,6 +6258,7 @@ pub enum FieldUpdated { ``` #### Emitting Events + Events are emitted by calling `self.emit()` with an event data structure. ```rust @@ -6138,34 +6266,40 @@ self.emit(BookAdded { id, title, author }); ``` #### Contract Attributes Related to Events -| Attribute | Defines | -| :--- | :--- | -| `#[event]` | Defines an event in a smart contract | -| `#[key]` | Defines an indexed `Event` enum field | -| `#[flat]` | Defines an enum variant of the `Event` enum that is not nested, ignoring the variant name in serialization | + +| Attribute | Defines | +| :--------- | :--------------------------------------------------------------------------------------------------------- | +| `#[event]` | Defines an event in a smart contract | +| `#[key]` | Defines an indexed `Event` enum field | +| `#[flat]` | Defines an enum variant of the `Event` enum that is not nested, ignoring the variant name in serialization | ### Contract ABI and Entrypoints The Contract Class ABI is the high-level specification describing callable functions, parameters, and return values, used for external interaction (via JSON representation) or contract-to-contract calls (via dispatcher pattern). #### Entrypoints + Entrypoints are functions callable from outside the contract class: + 1. **Public functions**: Most common, exposed as `view` or `external`. 2. **Constructor**: Called only once during deployment. 3. **L1-Handlers**: Triggered by the sequencer after receiving a message from L1. #### Function Selector -Entrypoints are identified by a *selector*, computed as `sn_keccak(function_name)`. + +Entrypoints are identified by a _selector_, computed as `sn_keccak(function_name)`. #### Encoding + All data must be serialized into `felt252` before execution at the CASM level, as specified by the ABI. #### ABI Definition Attributes -| Attribute | Explanation | -| :--- | :--- | -| `#[abi(embed_v0)]` | Defines an implementation of a trait, exposing its functions as contract entrypoints. | -| `#[abi(per_item)]` | Allows individual definition of the entrypoint type of functions inside an impl. | -| `#[external(v0)]` | Defines an external function when `#[abi(per_item)]` is used. | + +| Attribute | Explanation | +| :------------------ | :------------------------------------------------------------------------------------------------------------------ | +| `#[abi(embed_v0)]` | Defines an implementation of a trait, exposing its functions as contract entrypoints. | +| `#[abi(per_item)]` | Allows individual definition of the entrypoint type of functions inside an impl. | +| `#[external(v0)]` | Defines an external function when `#[abi(per_item)]` is used. | | `#[generate_trait]` | Generates a trait definition for the implementation block, often used for private impls or with `#[abi(per_item)]`. | When using `#[abi(per_item)]`, public functions must be annotated with `#[external(v0)]` to be exposed; otherwise, they are considered private. @@ -6200,9 +6334,11 @@ mod ContractExample { ### System Interaction and Context Access #### Class Hashes + Class hashes have the same value range as addresses `[0, 2^251)`. They uniquely identify a specific version of contract code and are used in deployment, proxy patterns, and upgrades. #### Working with Block and Transaction Information + Starknet provides functions to access the current execution context: ```rust @@ -6239,6 +6375,7 @@ mod BlockInfoExample { } } ``` + `get_block_info()` returns `BlockInfo` (block number, timestamp), and `get_tx_info()` returns `TxInfo` (sender address, transaction hash, fee details). --- @@ -6304,8 +6441,8 @@ When implementing a contract interface defined by a trait, the public functions The `self` parameter in the trait methods **must** be of type `ContractState`, which is generated by the compiler and grants access to storage variables and allows event emission. -* If `self` is passed by reference (`ref self: ContractState`), state modification is allowed. -* If `self` is passed as a snapshot (`self: @ContractState`), only read access is permitted, and attempts to modify state will result in a compilation error. +- If `self` is passed by reference (`ref self: ContractState`), state modification is allowed. +- If `self` is passed as a snapshot (`self: @ContractState`), only read access is permitted, and attempts to modify state will result in a compilation error. For example, in the implementation of `ISimpleStorage`: @@ -6351,9 +6488,9 @@ On Starknet, structs are stored in storage as a sequence of primitive types, sto For example, for a `Person` struct with `name` and `address`: -| Fields | Address | -| --- | --- | -| name | `owner.__base_address__` | +| Fields | Address | +| ------- | ---------------------------- | +| name | `owner.__base_address__` | | address | `owner.__base_address__ + 1` | ### Enums Storage Layout @@ -6364,15 +6501,15 @@ For an enum `Expiration` with `Finite: u64` (index 0) and `Infinite` (index 1): If `Finite` is stored: -| Element | Address | -| --- | --- | -| Variant index (0 for Finite) | `expiration.__base_address__` | -| Associated limit date | `expiration.__base_address__ + 1` | +| Element | Address | +| ---------------------------- | --------------------------------- | +| Variant index (0 for Finite) | `expiration.__base_address__` | +| Associated limit date | `expiration.__base_address__ + 1` | If `Infinite` is stored: -| Element | Address | -| --- | --- | +| Element | Address | +| ------------------------------ | ----------------------------- | | Variant index (1 for Infinite) | `expiration.__base_address__` | Enums used in contract storage **must** define a default variant (using `#[default]`), which is returned when reading an uninitialized storage slot, preventing runtime errors. @@ -6464,8 +6601,9 @@ The calculation mechanism for storage addresses is modeled using `StoragePointer Storage variables in Cairo are not stored contiguously. To manage retrieval, the core library models the contract storage using a system of `StoragePointers` and `StoragePaths`. Each storage variable can be converted into a `StoragePointer`. This pointer comprises two primary fields: -* The base address of the storage variable in the contract's storage. -* The offset, relative to the base address, of the specific storage slot being pointed to. + +- The base address of the storage variable in the contract's storage. +- The offset, relative to the base address, of the specific storage slot being pointed to. #### Example of Storage Access @@ -6631,9 +6769,9 @@ fn get_item_quantity(self: @ContractState, address: ContractAddress, item_id: u6 The storage address for a value associated with key(s) in a mapping is computed based on the mapping variable's name and the key(s): -* **Single Key ($k$):** The address is calculated as $h(\text{sn\_keccak}(\text{variable\_name}), k)$, where $h$ is the Pedersen hash. The result is taken modulo $2^{251} - 256$. -* **Multiple Keys ($k_1, k_2, \dots, k_n$):** The address is computed iteratively: $h(\dots h(h(\text{sn\_keccak}(\text{variable\_name}), k_1), k_2), \dots, k_n)$. -* If a key is a struct, each element of the struct is treated as a sequential key, provided the struct implements the `Hash` trait. +- **Single Key ($k$):** The address is calculated as $h(\text{sn\_keccak}(\text{variable\_name}), k)$, where $h$ is the Pedersen hash. The result is taken modulo $2^{251} - 256$. +- **Multiple Keys ($k_1, k_2, \dots, k_n$):** The address is computed iteratively: $h(\dots h(h(\text{sn\_keccak}(\text{variable\_name}), k_1), k_2), \dots, k_n)$. +- If a key is a struct, each element of the struct is treated as a sequential key, provided the struct implements the `Hash` trait. The base address of the storage variable can be accessed via the `__base_address__` attribute. @@ -6665,8 +6803,9 @@ self.addresses.push(caller); ``` **Retrieving Elements:** -* Use indexing (`vec[index]`) or `get(index)` to obtain a storage pointer to the element. Call `.read()` on the pointer to get the value. -* `get(index)` returns `Option`, returning `None` if the index is out of bounds. Indexing/`at` panics if out of bounds. + +- Use indexing (`vec[index]`) or `get(index)` to obtain a storage pointer to the element. Call `.read()` on the pointer to get the value. +- `get(index)` returns `Option`, returning `None` if the index is out of bounds. Indexing/`at` panics if out of bounds. ```rust // Example of getting the n-th element safely @@ -6698,10 +6837,10 @@ The storage address for elements in a `Vec` is computed as follows: ### Summary of Storage Vec Operations -* Use the `Vec` type for collections in storage. -* Use `push` to add, `pop` to remove the last element, and `get`/indexing to read elements. -* Use `[index].write(value)` to modify an element in place. -* Address calculation involves `sn_keccak` for the base length address and Pedersen hashing for element addresses. +- Use the `Vec` type for collections in storage. +- Use `push` to add, `pop` to remove the last element, and `get`/indexing to read elements. +- Use `[index].write(value)` to modify an element in place. +- Address calculation involves `sn_keccak` for the base length address and Pedersen hashing for element addresses. --- @@ -6722,16 +6861,16 @@ This structure is necessary because Cairo proofs require "dictionary squashing" Example entry list progression for squashing: -| key | previous | new | -| --- | --- | --- | -| Alex | 0 | 150 | -| Maria | 0 | 100 | -| Charles | 0 | 70 | -| Maria | 100 | 250 | -| Alex | 150 | 40 | -| Alex | 40 | 300 | -| Maria | 250 | 190 | -| Alex | 300 | 90 | +| key | previous | new | +| ------- | -------- | --- | +| Alex | 0 | 150 | +| Maria | 0 | 100 | +| Charles | 0 | 70 | +| Maria | 100 | 250 | +| Alex | 150 | 40 | +| Alex | 40 | 300 | +| Maria | 250 | 190 | +| Alex | 300 | 90 | After squashing, the entry list would be reduced to: @@ -6745,9 +6884,9 @@ The storage in a Starknet smart contract consists of a map with $2^{251}$ slots, Packing combines several integers into a single larger integer if the container size accommodates the sum of the required bits (e.g., two `u8` and one `u16` into one `u32`). This requires bitwise operators: -* **Shifting:** Multiplying or dividing an integer by a power of 2 shifts the value left or right, respectively. -* **Masking (AND):** Applying an `AND` operator isolates specific bits. -* **Combining (OR):** Adding (using `OR`) two integers combines their values. +- **Shifting:** Multiplying or dividing an integer by a power of 2 shifts the value left or right, respectively. +- **Masking (AND):** Applying an `AND` operator isolates specific bits. +- **Combining (OR):** Adding (using `OR`) two integers combines their values. Example of combining two `u8` into a `u16` (packing) and reversing it (unpacking): @@ -6758,9 +6897,10 @@ Packing and unpacking integer values Storage optimization can be achieved by implementing the `StorePacking` trait. The compiler uses the `StoreUsingPacking` implementation of the `Store` trait to automatically pack data before writing to storage and unpack it after reading. In this implementation: -* `TWO_POW_8` and `TWO_POW_40` are used for left shifting in `pack` and right shifting in `unpack`. -* `MASK_8` and `MASK_32` are used to isolate variables during `unpack`. -* Variables are converted to `u128` to enable bitwise operations. + +- `TWO_POW_8` and `TWO_POW_40` are used for left shifting in `pack` and right shifting in `unpack`. +- `MASK_8` and `MASK_32` are used to isolate variables during `unpack`. +- Variables are converted to `u128` to enable bitwise operations. This technique applies to any group of fields whose total bit size fits within the packed storage type (e.g., packing fields summing to 256 bits into a `u256`). A crucial requirement is that the type produced by `StorePacking::pack` must also implement the `Store` trait for `StoreUsingPacking` to function correctly. @@ -6780,9 +6920,9 @@ When an `Event` enum variant (like `FieldUpdated`) is annotated with `#[flat]`, The resulting log structure for such an event is defined as follows: -* **First Key:** Calculated using `selector!("Author")`. -* **Second Key:** The value of the field annotated with `#[key]` (e.g., the `id` field). -* **Data Field:** The serialized data of the remaining fields. For the example provided: `0x5374657068656e204b696e67` (representing 'Stephen King'). +- **First Key:** Calculated using `selector!("Author")`. +- **Second Key:** The value of the field annotated with `#[key]` (e.g., the `id` field). +- **Data Field:** The serialized data of the remaining fields. For the example provided: `0x5374657068656e204b696e67` (representing 'Stephen King'). --- @@ -6890,14 +7030,14 @@ The field element (`felt252`), containing 252 bits, is the sole type in the Cair The following Cairo data types fit within 252 bits and are represented by a single felt: -* `ContractAddress` -* `EthAddress` -* `StorageAddress` -* `ClassHash` -* Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`, and `usize` -* `bytes31` -* `felt252` -* Signed integers: `i8`, `i16`, `i32`, `i64`, and `i128` +- `ContractAddress` +- `EthAddress` +- `StorageAddress` +- `ClassHash` +- Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`, and `usize` +- `bytes31` +- `felt252` +- Signed integers: `i8`, `i16`, `i32`, `i64`, and `i128` Note that a negative value, $(-x)$, is serialized as $(P-x)$, where $P = 2^{251} + 17 \cdot 2^{192} + 1$. @@ -6913,16 +7053,21 @@ Sources: ### Fundamentals of Contract Interaction #### Necessity and Scope of Contract Interaction + A smart contract requires an external trigger from an entity (user or another contract) to execute. The ability for contracts to interact facilitates the creation of complex applications where individual contracts handle specific functionalities. Key aspects of interaction include understanding the Application Binary Interface (ABI), how to call contracts, enabling inter-contract communication, and proper usage of classes as libraries. #### Deploying and Interacting via `katana` + Interaction with Starknet contracts begins after deployment. For local development and testing, the `katana` local Starknet node is recommended over using testnets like Goerli. To start the local node: + ```bash katana ``` + This command starts the node with predeployed accounts, which are listed in the output, providing necessary addresses and private keys for testing interactions: + ``` ... PREFUNDED ACCOUNTS @@ -6943,21 +7088,28 @@ PREFUNDED ACCOUNTS ``` #### Differentiating Call and Invoke Operations + Interacting with external functions is categorized based on state modification: -* **Calling Contracts:** Used for external functions that only read from the state. These operations do not alter the network state, thus requiring no fees or signing. -* **Invoking Contracts:** Used for external functions that write to the state. These operations alter the network state and mandate fees and signing. + +- **Calling Contracts:** Used for external functions that only read from the state. These operations do not alter the network state, thus requiring no fees or signing. +- **Invoking Contracts:** Used for external functions that write to the state. These operations alter the network state and mandate fees and signing. #### Reading State with `starkli call` + Read functions are executed using the `starkli call` command. The general structure is: `starkli call [contract_address] [function] [input] --rpc [url]`. The function `voter_can_vote` checks eligibility. If the input address corresponds to a registered voter who hasn't voted, the result is 1 (true): + ```bash starkli call 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 voter_can_vote 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 --rpc http://0.0.0.0:5050 ``` + Checking an unregistered account with `is_voter_registered` yields 0 (false): + ```bash starkli call 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 is_voter_registered 0x44444444444444444 --rpc http://0.0.0.0:5050 ``` + Functions that modify state, such as casting a vote via the `vote` function, necessitate the use of the `starknet invoke` command. --- @@ -6976,9 +7128,9 @@ The dispatcher pattern enables calling functions on another contract by utilizin When a contract interface is defined, the compiler automatically generates several dispatchers. Using `IERC20` as an example: -* **Contract Dispatchers** (e.g., `IERC20Dispatcher`): Wrap a contract address and are used to call functions on other contracts. -* **Library Dispatchers** (e.g., `IERC20LibraryDispatcher`): Wrap a class hash and are used to call functions on classes. -* **'Safe' Dispatchers** (e.g., `IERC20SafeDispatcher`): Allow the caller to handle potential execution errors. +- **Contract Dispatchers** (e.g., `IERC20Dispatcher`): Wrap a contract address and are used to call functions on other contracts. +- **Library Dispatchers** (e.g., `IERC20LibraryDispatcher`): Wrap a class hash and are used to call functions on classes. +- **'Safe' Dispatchers** (e.g., `IERC20SafeDispatcher`): Allow the caller to handle potential execution errors. ### The Dispatcher Pattern Implementation Details @@ -7155,12 +7307,12 @@ While safe dispatchers return a `Result>` allowing for error The following cases are expected to be handled in future Starknet versions: -* Failure in a Cairo Zero contract call. -* Library call with a non-existent class hash. -* Contract call to a non-existent contract address. -* Failure within the `deploy` syscall (e.g., panic in the constructor, deploying to an existing address). -* Using the `deploy` syscall with a non-existent class hash. -* Using the `replace_class` syscall with a non-existent class hash. +- Failure in a Cairo Zero contract call. +- Library call with a non-existent class hash. +- Contract call to a non-existent contract address. +- Failure within the `deploy` syscall (e.g., panic in the constructor, deploying to an existing address). +- Using the `deploy` syscall with a non-existent class hash. +- Using the `replace_class` syscall with a non-existent class hash. #### Using `call_contract_syscall` @@ -7230,12 +7382,12 @@ Library calls allow a contract to execute the logic of another class within its #### Key Differences Between Contract Calls and Library Calls -| Feature | Contract Call (to deployed **contract**) | Library Call (to **class**) -| :--- | :--- | -| Execution Context | That of the called contract (B) | That of the caller (A) -| `get_caller_address()` in called logic | Address of A | Address of A's caller -| `get_contract_address()` in called logic | Address of B | Address of A -| Storage Updates | Update storage of B | Update storage of A +| Feature | Contract Call (to deployed **contract**) | Library Call (to **class**) | +| :--------------------------------------- | :--------------------------------------- | --------------------------- | +| Execution Context | That of the called contract (B) | That of the caller (A) | +| `get_caller_address()` in called logic | Address of A | Address of A's caller | +| `get_contract_address()` in called logic | Address of B | Address of A | +| Storage Updates | Update storage of B | Update storage of A | Library calls are performed using a class hash instead of a contract address when using the dispatcher pattern. @@ -7274,8 +7426,8 @@ To use library calls via the dispatcher, an instance of the library dispatcher ( In the example using `ValueStoreExecutor` calling `ValueStoreLogic`: -* Calling `set_value` updates `ValueStoreExecutor`'s storage variable `value`. -* Calling `get_value` reads `ValueStoreExecutor`'s storage variable `value`. +- Calling `set_value` updates `ValueStoreExecutor`'s storage variable `value`. +- Calling `get_value` reads `ValueStoreExecutor`'s storage variable `value`. ``` #[starknet::interface] @@ -7384,6 +7536,7 @@ mod ValueStore { } } ``` + The call returns serialized values that must be deserialized manually. --- @@ -7402,10 +7555,10 @@ Starknet utilizes an **L1-L2 messaging system** to enable interaction between sm The messaging system is characterized by two key properties: -* **Asynchronous**: Contract code (Solidity or Cairo) cannot await the result of a message sent on the other chain during its execution. -* **Asymmetric**: - * `L1->L2`: Messages are automatically delivered to the target L2 contract by the Starknet sequencer. - * `L2->L1`: Only the hash of the message is sent to L1 by the sequencer; consumption must be done manually via a transaction on L1. +- **Asynchronous**: Contract code (Solidity or Cairo) cannot await the result of a message sent on the other chain during its execution. +- **Asymmetric**: + - `L1->L2`: Messages are automatically delivered to the target L2 contract by the Starknet sequencer. + - `L2->L1`: Only the hash of the message is sent to L1 by the sequencer; consumption must be done manually via a transaction on L1. #### The StarknetMessaging Contract @@ -7525,6 +7678,7 @@ function consumeMessageFelt( require(my_felt > 0, "Invalid value"); } ``` + The `consumeMessageFromL2` call validates the inputs (L2 contract address and payload) against the recorded message hash. #### Cairo Serialization Considerations (Serde) @@ -7588,11 +7742,13 @@ The contract includes external read functions to check eligibility without alter The `invoke` command is used for state-changing operations, such as voting. Voting requires signing the transaction and incurs a fee. The input for the `vote` function is `1` (Yes) or `0` (No). Voting Yes: + ```bash starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 1 --rpc http://0.0.0.0:5050 --account katana-0 ``` Voting No: + ```bash starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 0 --rpc http://0.0.0.0:5050 --account katana-0 ``` @@ -7608,9 +7764,11 @@ The output provides details like transaction hash, max fee, and signature. #### Error Handling Attempting to vote twice with the same signer results in a generic error: + ``` Error: code=ContractError, message="Contract error" ``` + More detailed error reasons, such as `"USER_ALREADY_VOTED"`, can be found by inspecting the output logs of the running `katana` node. --- @@ -7630,6 +7788,7 @@ To understand what happens internally, transaction receipts show emitted events. When invoking `add_book` (e.g., id=42, title='Misery', author='S. King'), the transaction receipt's "events" section contains serialized event data. The structure observed in the receipt is: + ```json "events": [ { @@ -7647,17 +7806,18 @@ The structure observed in the receipt is: ``` Interpretation of the receipt fields for this event: -* `from_address`: The smart contract's address. -* `keys`: Contains serialized `felt252` key fields. - * The first key is the event selector, `selector!("BookAdded")`. - * The second key, `0x532e204b696e67` ('S. King'), corresponds to the `author` field defined with `#[key]`. -* `data`: Contains serialized `felt252` data fields. - * `0x2a` (42) is the `id` data field. - * `0x4d6973657279` ('Misery') is the `title` data field. + +- `from_address`: The smart contract's address. +- `keys`: Contains serialized `felt252` key fields. + - The first key is the event selector, `selector!("BookAdded")`. + - The second key, `0x532e204b696e67` ('S. King'), corresponds to the `author` field defined with `#[key]`. +- `data`: Contains serialized `felt252` data fields. + - `0x2a` (42) is the `id` data field. + - `0x4d6973657279` ('Misery') is the `title` data field. #### Example 2: Update a book author -Invoking `change_book_author` (id=42, new\_author='Stephen King') emits a `FieldUpdated` event. The receipt structure reflects this: +Invoking `change_book_author` (id=42, new_author='Stephen King') emits a `FieldUpdated` event. The receipt structure reflects this: ```json "events": [ @@ -7673,6 +7833,7 @@ Invoking `change_book_author` (id=42, new\_author='Stephen King') emits a `Field } ] ``` + This event structure corresponds to `FieldUpdated::Author(UpdatedAuthorData { id: 42, title: author: 'Stephen King' })`. The key includes the selector and the book ID (0x2a), while the data contains the new author name serialized. --- @@ -7717,9 +7878,9 @@ Unlike a contract, a component cannot be deployed on its own; its logic becomes A component is similar to a contract and can contain: -* Storage variables -* Events -* External and internal functions +- Storage variables +- Events +- External and internal functions ## Creating Components @@ -7828,10 +7989,10 @@ pub mod OwnableComponent { Migration requires minimal changes: -* Add `#[starknet::component]` attribute to the module. -* Add `#[embeddable_as(name)]` attribute to the `impl` block to be embedded. -* Add generic parameters to the `impl` block: `TContractState` and the restriction `+HasComponent`. -* Change the `self` argument type in functions from `ContractState` to `ComponentState`. +- Add `#[starknet::component]` attribute to the module. +- Add `#[embeddable_as(name)]` attribute to the `impl` block to be embedded. +- Add generic parameters to the `impl` block: `TContractState` and the restriction `+HasComponent`. +- Change the `self` argument type in functions from `ContractState` to `ComponentState`. For traits generated via `#[generate_trait]`, the trait is generic over `TContractState` instead of `ComponentState`. @@ -7840,9 +8001,9 @@ For traits generated via `#[generate_trait]`, the trait is generic over `TContra To integrate a component, you must: 1. Declare it using the `component!()` macro, specifying: - * The path to the component (`path::to::component`). - * The name of the storage variable (`ownable`). - * The name of the event enum variant (`OwnableEvent`). + - The path to the component (`path::to::component`). + - The name of the storage variable (`ownable`). + - The name of the event enum variant (`OwnableEvent`). 2. Add the path to the component's storage and events to the contract's storage and event definitions. ### Example: Basic ERC20 Contract Using Components @@ -7904,8 +8065,8 @@ Sources: Cairo compiler provides helpful diagnostics when integrating components: -* `Plugin diagnostic: name is not a substorage member in the contract's Storage. Consider adding to Storage: (...)`: Indicates that the component's storage was forgotten in the contract's `Storage`. Solution: Add the component's storage path annotated with `#[substorage(v0)]`. -* `Plugin diagnostic: name is not a nested event in the contract's Event enum. Consider adding to the Event enum:`: Suggests missing component events. Solution: Add the path to the component's events in the contract's `Event` enum. +- `Plugin diagnostic: name is not a substorage member in the contract's Storage. Consider adding to Storage: (...)`: Indicates that the component's storage was forgotten in the contract's `Storage`. Solution: Add the component's storage path annotated with `#[substorage(v0)]`. +- `Plugin diagnostic: name is not a nested event in the contract's Event enum. Consider adding to the Event enum:`: Suggests missing component events. Solution: Add the path to the component's events in the contract's `Event` enum. ## Component Impl Structure and State Access @@ -8111,12 +8272,12 @@ Pragma provides VRF solutions on Starknet. The process involves: Key inputs for `request_randomness_from_pragma` include: -* `seed`: Unique value to initialize generation. -* `callback_address`: Address of the contract implementing `receive_random_words`. -* `callback_fee_limit`: Max gas willing to spend on the callback execution. -* `publish_delay`: Minimum block delay before fulfillment. -* `num_words`: Number of random `felt252` values requested. -* `calldata`: Additional data passed to the callback. +- `seed`: Unique value to initialize generation. +- `callback_address`: Address of the contract implementing `receive_random_words`. +- `callback_fee_limit`: Max gas willing to spend on the callback execution. +- `publish_delay`: Minimum block delay before fulfillment. +- `num_words`: Number of random `felt252` values requested. +- `calldata`: Additional data passed to the callback. **Dice Game Example using Pragma VRF** @@ -8124,8 +8285,8 @@ This contract uses VRF to determine game winners. It requires defining interface Key entrypoints in `IPragmaVRF`: -* `request_randomness_from_pragma`: Owner calls this to initiate the request. -* `receive_random_words`: Called by the oracle to submit the results, which are stored in `last_random_number`. +- `request_randomness_from_pragma`: Owner calls this to initiate the request. +- `receive_random_words`: Called by the oracle to submit the results, which are stored in `last_random_number`. The full contract implementation (`DiceGame`) demonstrates handling the request, approval of callback fees, and processing the result in `process_game_winners`: @@ -8334,7 +8495,9 @@ Sources: --- ## System-Level Patterns and Messaging + ### Local Messaging System Testing + You can also find a detailed guide here to test the messaging system locally. --- @@ -8398,6 +8561,7 @@ fn main() { ``` Executing this produces output like: + ``` $ scarb execute Compiling no_listing_01_panic v0.1.0 (listings/ch09-error-handling/no_listing_01_panic/Scarb.toml) @@ -8442,6 +8606,7 @@ fn function_never_panic() -> felt252 nopanic { ``` If a function declared as `nopanic` calls a function that may panic (like `assert!`), compilation will fail: + ``` error: Function is declared as nopanic but calls a function that may panic. ``` @@ -8486,14 +8651,14 @@ enum Result { The `ResultTrait` provides methods for working with `Result`: -| Method | Behavior on `Ok(x)` | Behavior on `Err(e)` | -| :--- | :--- | :--- | -| `unwrap()` | Returns `x` (panics with default message otherwise) | Panics | -| `expect(err)` | Returns `x` | Panics with custom message `err` | -| `unwrap_err()` | Panics | Returns `e` | -| `expect_err(err)` | Panics with custom message `err` | Returns `e` | -| `is_ok()` | Returns `true` | Returns `false` | -| `is_err()` | Returns `false` | Returns `true` | +| Method | Behavior on `Ok(x)` | Behavior on `Err(e)` | +| :---------------- | :-------------------------------------------------- | :------------------------------- | +| `unwrap()` | Returns `x` (panics with default message otherwise) | Panics | +| `expect(err)` | Returns `x` | Panics with custom message `err` | +| `unwrap_err()` | Panics | Returns `e` | +| `expect_err(err)` | Panics with custom message `err` | Returns `e` | +| `is_ok()` | Returns `true` | Returns `false` | +| `is_err()` | Returns `false` | Returns `true` | These methods require generic type constraints, such as `<+Drop>` for `unwrap`. @@ -8524,6 +8689,7 @@ fn mutate_byte(input: felt252) -> Result { ``` If used incorrectly (e.g., in a function returning `()`), it results in: + ``` error: `?` can only be used in a function with `Option` or `Result` return type. ``` @@ -8532,18 +8698,19 @@ error: `?` can only be used in a function with `Option` or `Result` return type. Encountering specific error messages can be resolved by checking the following: -* **`Variable not dropped.`**: A variable of a non-`Drop`/non-`Destruct` type is going out of scope without being destroyed. -* **`Variable was previously moved.`**: Attempting to use a variable whose ownership was transferred (if it doesn't implement `Copy`). Use `.clone()` to avoid this. -* **`error: Trait has no implementation in context: core::fmt::Display...`**: Occurs when using `{}` for custom types in `print!`. Implement `Display` or use `derive(Debug)` and print with `{:?}`. -* **`Got an exception while executing a hint: Hint Error: Failed to deserialize param #x.`**: An entrypoint was called without expected arguments. For `u256` (a struct of two `u128`), two values must be passed. -* **`Item path::item is not visible in this context.`**: Visibility issue. Declare modules and items using `pub(crate)` or `pub`. -* **`Identifier not found.`**: May mean a variable is used before declaration (`let`) or the path to an item is incorrect. +- **`Variable not dropped.`**: A variable of a non-`Drop`/non-`Destruct` type is going out of scope without being destroyed. +- **`Variable was previously moved.`**: Attempting to use a variable whose ownership was transferred (if it doesn't implement `Copy`). Use `.clone()` to avoid this. +- **`error: Trait has no implementation in context: core::fmt::Display...`**: Occurs when using `{}` for custom types in `print!`. Implement `Display` or use `derive(Debug)` and print with `{:?}`. +- **`Got an exception while executing a hint: Hint Error: Failed to deserialize param #x.`**: An entrypoint was called without expected arguments. For `u256` (a struct of two `u128`), two values must be passed. +- **`Item path::item is not visible in this context.`**: Visibility issue. Declare modules and items using `pub(crate)` or `pub`. +- **`Identifier not found.`**: May mean a variable is used before declaration (`let`) or the path to an item is incorrect. ## Robustness and Security Considerations Certain programming patterns can lead to vulnerabilities if not handled carefully: ### Operator Precedence + `&&` has higher precedence than `||`. Parentheses must be used to enforce correct precedence in combined boolean expressions: ```cairo @@ -8561,6 +8728,7 @@ assert!( ``` ### Unsigned Loop Underflow + Using an unsigned integer (`u32`) for a loop counter that is decremented past zero causes an underflow panic. Use signed integers (`i32`) for counters that might go below zero: ```cairo @@ -8573,6 +8741,7 @@ while i >= 0 { // This would never trigger if `i` was a u32. ``` ### Bit-Packing into `felt252` + Packing fields into a single `felt252` requires strict bounds checking. The sum of the sizes of packed values should not exceed 251 bits. ```cairo @@ -8616,9 +8785,9 @@ When running tests with `scarb test`, Scarb executes Starknet Foundry's test run Cairo features available for writing tests include: -* `#[test]` attribute: Marks a function as a test. -* Assertion macros: `assert!`, `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!`. -* `#[should_panic]` attribute: Marks a test that is expected to panic. +- `#[test]` attribute: Marks a function as a test. +- Assertion macros: `assert!`, `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!`. +- `#[should_panic]` attribute: Marks a test that is expected to panic. ### The `tests` Module and Configuration @@ -8645,9 +8814,9 @@ mod tests { Tests fail when code inside the function panics. Assertions are used to check conditions: -* `assert!(condition, message)`: Fails if the condition evaluates to `false`. -* `assert_eq!(left, right)` and `assert_ne!(left, right)`: Compare two arguments for equality or inequality, printing the values upon failure. -* Comparison macros: `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!` check relative ordering. +- `assert!(condition, message)`: Fails if the condition evaluates to `false`. +- `assert_eq!(left, right)` and `assert_ne!(left, right)`: Compare two arguments for equality or inequality, printing the values upon failure. +- Comparison macros: `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!` check relative ordering. When assertions fail, custom failure messages can be added as optional arguments to these macros, which are passed to the `format!` macro. @@ -8665,9 +8834,9 @@ Integration tests verify that multiple parts of the library work together correc ### Controlling Test Execution -* **Filtering:** Running `scarb test ` runs only tests whose names match the provided string (or substring). -* **Ignoring:** The `#[ignore]` attribute excludes a test from standard runs. It can be included later using `scarb test --include-ignored`. -* **Gas Limits:** For recursive functions or loops, the execution gas can be overridden using `#[available_gas()]` on the test function. +- **Filtering:** Running `scarb test ` runs only tests whose names match the provided string (or substring). +- **Ignoring:** The `#[ignore]` attribute excludes a test from standard runs. It can be included later using `scarb test --include-ignored`. +- **Gas Limits:** For recursive functions or loops, the execution gas can be overridden using `#[available_gas()]` on the test function. ### Benchmarking @@ -8686,17 +8855,21 @@ Sources: ### Security Best Practices and Contract Testing #### General Recommendations and Focus Areas + Writing secure code is crucial. Focus areas derived from audits include: -* Access control and upgrades. -* Safe ERC20 token integrations. -* Cairo-specific pitfalls. -* Cross-domain/bridging safety. -* Economic/Denial of Service (DoS) considerations. + +- Access control and upgrades. +- Safe ERC20 token integrations. +- Cairo-specific pitfalls. +- Cross-domain/bridging safety. +- Economic/Denial of Service (DoS) considerations. #### Access Control, Upgrades & Initializers + The most common critical issues involve access control ("who can call this?") and re-initialization. ##### Own Your Privileged Paths + Ensure upgrades, pause/resume, bridge handling, and meta-execution are guarded, typically using `OwnableComponent`. ```cairo @@ -8719,6 +8892,7 @@ fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { ``` ##### Initializers Must Be Idempotent + A publicly exposed initializer called post-deploy can lead to vulnerabilities if it can be called multiple times. Ensure initialization logic is idempotent by checking a flag first. ```cairo @@ -8735,29 +8909,39 @@ fn initializer(ref self: ContractState, owner: ContractAddress) { // init the rest… } ``` + Rule: If a function must be external during deployment, ensure it can only be called once; otherwise, keep it internal. ##### Emit Events + Emit events for upgrades, configuration changes, pausing, liquidations, and any privileged action to aid incident response and indexers. Include addresses (e.g., token) to remove ambiguity. #### Safe Token Integrations + ##### Always Check Boolean Returns + Not all ERC20 implementations revert on failure; some return `false`. Verify the boolean flags returned by `transfer` and `transfer_from` to confirm success. ##### Naming Conventions + Most Starknet ERC20 tokens use `snake_case`. Be aware that legacy tokens might use `camelCase` entrypoints, requiring adaptation if interacting with them. #### Cairo-Specific Pitfalls + ##### `deploy_syscall(deploy_from_zero=true)` Collisions + Setting `deploy_from_zero` to `true` enables deterministic deployment, which can cause collisions if two contracts attempt deployment with the same calldata. Set this to `false` unless this behavior is explicitly desired. ##### Useless Zero-Address Checks + Checks like `get_caller_address().is_zero()` are inherited from Solidity but are useless on Starknet, as `get_caller_address()` is never the zero address. #### Cross-Domain / Bridging Safety + L1-L2 interactions require specific validation. ##### L1 Handler Caller Validation + Entrypoints marked with `#[l1_handler]` must validate that the caller address originates from a trusted L1 contract. ```cairo @@ -8776,14 +8960,18 @@ fn handle_deposit( ``` #### Economic/DoS & Griefing + ##### Unbounded Loops + User-controlled iterations (e.g., batch withdrawals, order sweeps) can exceed Starknet's execution step limit if the list size is unbounded. Cap the number of iterations or implement a pagination pattern to split work across multiple transactions. #### Static Analysis Tools (Contract Testing) + Static analysis examines code structure, syntax, and properties without execution to identify vulnerabilities or rule violations. Developers should use these tools to automatically check code against defined security guidelines. Reference tools include: -* Semgrep Cairo 1.0 support -* Caracal, a Starknet static analyzer + +- Semgrep Cairo 1.0 support +- Caracal, a Starknet static analyzer --- @@ -8811,16 +8999,19 @@ Sources: ### Reference Material Overview #### Appendix (Cairo) + The Cairo Programming Language appendix includes: -* Light -* Rust -* Coal -* Navy -* Ayu + +- Light +- Rust +- Coal +- Navy +- Ayu The following sections contain reference material you may find useful in your Cairo journey. #### Appendix (Starknet) + The following sections contain reference material you may find useful in your Starknet journey. ]] @@ -8844,69 +9035,69 @@ Keywords fall into three categories: strict, loose, and reserved. A fourth categ These keywords must only be used in their correct contexts and cannot serve as item names. -* `as` - Rename import -* `break` - Exit a loop immediately -* `const` - Define constant items -* `continue` - Continue to the next loop iteration -* `else` - Fallback for `if` and `if let` control flow constructs -* `enum` - Define an enumeration -* `extern` - Function defined at the compiler level that can be compiled to CASM -* `false` - Boolean false literal -* `fn` - Define a function -* `if` - Branch based on the result of a conditional expression -* `impl` - Implement inherent or trait functionality -* `implicits` - Special kind of function parameters that are required to perform certain actions -* `let` - Bind a variable -* `loop` - Loop unconditionally -* `match` - Match a value to patterns -* `mod` - Define a module -* `mut` - Denote variable mutability -* `nopanic` - Functions marked with this notation mean that the function will never panic. -* `of` - Implement a trait -* `pub` - Denote public visibility in items, such as struct and struct fields, enums, consts, traits and impl blocks, or modules -* `ref` - Parameter passed implicitly returned at the end of a function -* `return` - Return from function -* `struct` - Define a structure -* `trait` - Define a trait -* `true` - Boolean true literal -* `type` - Define a type alias -* `use` - Bring symbols into scope -* `while` - loop conditionally based on the result of an expression +- `as` - Rename import +- `break` - Exit a loop immediately +- `const` - Define constant items +- `continue` - Continue to the next loop iteration +- `else` - Fallback for `if` and `if let` control flow constructs +- `enum` - Define an enumeration +- `extern` - Function defined at the compiler level that can be compiled to CASM +- `false` - Boolean false literal +- `fn` - Define a function +- `if` - Branch based on the result of a conditional expression +- `impl` - Implement inherent or trait functionality +- `implicits` - Special kind of function parameters that are required to perform certain actions +- `let` - Bind a variable +- `loop` - Loop unconditionally +- `match` - Match a value to patterns +- `mod` - Define a module +- `mut` - Denote variable mutability +- `nopanic` - Functions marked with this notation mean that the function will never panic. +- `of` - Implement a trait +- `pub` - Denote public visibility in items, such as struct and struct fields, enums, consts, traits and impl blocks, or modules +- `ref` - Parameter passed implicitly returned at the end of a function +- `return` - Return from function +- `struct` - Define a structure +- `trait` - Define a trait +- `true` - Boolean true literal +- `type` - Define a type alias +- `use` - Bring symbols into scope +- `while` - loop conditionally based on the result of an expression #### Loose Keywords These keywords are associated with specific behavior but can also be used to define items. -* `self` - Method subject -* `super` - Parent module of the current module +- `self` - Method subject +- `super` - Parent module of the current module #### Reserved Keywords These keywords are reserved for future use. While it is currently possible to use them to define items, doing so is highly discouraged to ensure forward compatibility. -* `Self` -* `do` -* `dyn` -* `for` -* `hint` -* `in` -* `macro` -* `move` -* `static_assert` -* `static` -* `try` -* `typeof` -* `unsafe` -* `where` -* `with` -* `yield` +- `Self` +- `do` +- `dyn` +- `for` +- `hint` +- `in` +- `macro` +- `move` +- `static_assert` +- `static` +- `try` +- `typeof` +- `unsafe` +- `where` +- `with` +- `yield` #### Built-in Functions These functions serve a special purpose in Cairo. Using their names for other items is not recommended. -* `assert` - This function checks a boolean expression, and if it evaluates to false, it triggers the panic function. -* `panic` - This function acknowledges the occurrence of an error and terminates the program. +- `assert` - This function checks a boolean expression, and if it evaluates to false, it triggers the panic function. +- `panic` - This function acknowledges the occurrence of an error and terminates the program. --- @@ -8924,43 +9115,43 @@ This section provides a glossary of Cairo's syntax, including operators and othe Table B-1 lists Cairo operators, their context, explanation, and overloadability details. -| Operator | Example | Explanation | Overloadable? | -| --- | --- | --- | --- | -| `!` | `!expr` | Logical complement | `Not` | -| `~` | `~expr` | Bitwise NOT | `BitNot` | -| `!=` | `expr != expr` | Non-equality comparison | `PartialEq` | -| `%` | `expr % expr` | Arithmetic remainder | `Rem` | -| `%=` | `var %= expr` | Arithmetic remainder and assignment | `RemEq` | -| `&` | `expr & expr` | Bitwise AND | `BitAnd` | -| `&&` | `expr && expr` | Short-circuiting logical AND | | -| `*` | `expr * expr` | Arithmetic multiplication | `Mul` | -| `*=` | `var *= expr` | Arithmetic multiplication and assignment | `MulEq` | -| `@` | `@var` | Snapshot | | -| `*` | `*var` | Desnap | | -| `+` | `expr + expr` | Arithmetic addition | `Add` | -| `+=` | `var += expr` | Arithmetic addition and assignment | `AddEq` | -| `,` | `expr, expr` | Argument and element separator | | -| `-` | `-expr` | Arithmetic negation | `Neg` | -| `-` | `expr - expr` | Arithmetic subtraction | `Sub` | -| `-=` | `var -= expr` | Arithmetic subtraction and assignment | `SubEq` | -| `->` | `fn(...) -> type`, `|...| -> type` | Function and closure return type | | -| `.` | `expr.ident` | Member access | | -| `/` | `expr / expr` | Arithmetic division | `Div` | -| `/=` | `var /= expr` | Arithmetic division and assignment | `DivEq` | -| `:` | `pat: type`, `ident: type` | Constraints | | -| `:` | `ident: expr` | Struct field initializer | | -| `;` | `expr;` | Statement and item terminator | | -| `<` | `expr < expr` | Less than comparison | `PartialOrd` | -| `<=` | `expr <= expr` | Less than or equal to comparison | `PartialOrd` | -| `=` | `var = expr` | Assignment | | -| `==` | `expr == expr` | Equality comparison | `PartialEq` | -| `=>` | `pat => expr` | Part of match arm syntax | | -| `>` | `expr > expr` | Greater than comparison | `PartialOrd` | -| `>=` | `expr >= expr` | Greater than or equal to comparison | `PartialOrd` | -| `^` | `expr ^ expr` | Bitwise exclusive OR | `BitXor` | -| `|` | `expr | expr` | Bitwise OR | `BitOr` | -| `||` | `expr || expr` | Short-circuiting logical OR | | -| `?` | `expr?` | Error propagation | | +| Operator | Example | Explanation | Overloadable? | +| -------- | -------------------------- | ---------------------------------------- | ------------- | -------------------------------- | ------- | --------------------------- | --- | +| `!` | `!expr` | Logical complement | `Not` | +| `~` | `~expr` | Bitwise NOT | `BitNot` | +| `!=` | `expr != expr` | Non-equality comparison | `PartialEq` | +| `%` | `expr % expr` | Arithmetic remainder | `Rem` | +| `%=` | `var %= expr` | Arithmetic remainder and assignment | `RemEq` | +| `&` | `expr & expr` | Bitwise AND | `BitAnd` | +| `&&` | `expr && expr` | Short-circuiting logical AND | | +| `*` | `expr * expr` | Arithmetic multiplication | `Mul` | +| `*=` | `var *= expr` | Arithmetic multiplication and assignment | `MulEq` | +| `@` | `@var` | Snapshot | | +| `*` | `*var` | Desnap | | +| `+` | `expr + expr` | Arithmetic addition | `Add` | +| `+=` | `var += expr` | Arithmetic addition and assignment | `AddEq` | +| `,` | `expr, expr` | Argument and element separator | | +| `-` | `-expr` | Arithmetic negation | `Neg` | +| `-` | `expr - expr` | Arithmetic subtraction | `Sub` | +| `-=` | `var -= expr` | Arithmetic subtraction and assignment | `SubEq` | +| `->` | `fn(...) -> type`, ` | ... | -> type` | Function and closure return type | | +| `.` | `expr.ident` | Member access | | +| `/` | `expr / expr` | Arithmetic division | `Div` | +| `/=` | `var /= expr` | Arithmetic division and assignment | `DivEq` | +| `:` | `pat: type`, `ident: type` | Constraints | | +| `:` | `ident: expr` | Struct field initializer | | +| `;` | `expr;` | Statement and item terminator | | +| `<` | `expr < expr` | Less than comparison | `PartialOrd` | +| `<=` | `expr <= expr` | Less than or equal to comparison | `PartialOrd` | +| `=` | `var = expr` | Assignment | | +| `==` | `expr == expr` | Equality comparison | `PartialEq` | +| `=>` | `pat => expr` | Part of match arm syntax | | +| `>` | `expr > expr` | Greater than comparison | `PartialOrd` | +| `>=` | `expr >= expr` | Greater than or equal to comparison | `PartialOrd` | +| `^` | `expr ^ expr` | Bitwise exclusive OR | `BitXor` | +| ` | ` | `expr | expr` | Bitwise OR | `BitOr` | +| ` | | ` | `expr | | expr` | Short-circuiting logical OR | | +| `?` | `expr?` | Error propagation | | ## Non-Operator Symbols @@ -8968,67 +9159,67 @@ These symbols do not behave like a function or method call. ### Stand-Alone Syntax -| Symbol | Explanation | -| --- | --- | -| `..._u8`, `..._usize`, `..._bool`, etc. | Numeric literal of specific type | -| `\"...\"` | String literal | -| `'...'` | Short string, 31 ASCII characters maximum | -| `_` | “Ignored” pattern binding | +| Symbol | Explanation | +| --------------------------------------- | ----------------------------------------- | +| `..._u8`, `..._usize`, `..._bool`, etc. | Numeric literal of specific type | +| `\"...\"` | String literal | +| `'...'` | Short string, 31 ASCII characters maximum | +| `_` | “Ignored” pattern binding | ### Path-Related Syntax Symbols used within the context of a module hierarchy path to access an item. -| Symbol | Explanation | -| --- | --- | -| `ident::ident` | Namespace path | -| `super::path` | Path relative to the parent of the current module | +| Symbol | Explanation | +| -------------------- | ---------------------------------------------------------------- | +| `ident::ident` | Namespace path | +| `super::path` | Path relative to the parent of the current module | | `trait::method(...)` | Disambiguating a method call by naming the trait that defines it | ### Generic Type Parameters Symbols appearing in the context of using generic type parameters. -| Symbol | Explanation | -| --- | --- | +| Symbol | Explanation | +| ----------- | ------------------------------------------------------------------ | | `path<...>` | Specifies parameters to generic type in a type (e.g., `Array`) | ### Macros -| Symbol | Explanation | -| :--- | :--- | +| Symbol | Explanation | +| :----------------------- | :------------------------------------------------------------------------------------- | | `get_dep_component_mut!` | Returns the requested component state from a reference of the state inside a component | -| `component!` | Macro used in Starknet contracts to embed a component inside a contract | +| `component!` | Macro used in Starknet contracts to embed a component inside a contract | ### Comments Symbols that create comments. -| Symbol | Explanation | -| --- | --- | -| `//` | Line comment | +| Symbol | Explanation | +| ------ | ------------ | +| `//` | Line comment | ### Tuples Symbols appearing in the context of using tuples. -| Symbol | Explanation | -| --- | --- | -| `()` | Empty tuple (aka unit), both literal and type | -| `(expr)` | Parenthesized expression | -| `(expr,)` | Single-element tuple expression | -| `(type,)` | Single-element tuple type | -| `(expr, ...)` | Tuple expression | -| `(type, ...)` | Tuple type | +| Symbol | Explanation | +| ----------------- | ------------------------------------------------------------------------------------------- | +| `()` | Empty tuple (aka unit), both literal and type | +| `(expr)` | Parenthesized expression | +| `(expr,)` | Single-element tuple expression | +| `(type,)` | Single-element tuple type | +| `(expr, ...)` | Tuple expression | +| `(type, ...)` | Tuple type | | `expr(expr, ...)` | Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants | ### Curly Braces Contexts in which curly braces are used. -| Context | Explanation | -| --- | --- | -| `{...}` | Block expression | +| Context | Explanation | +| ------------ | ---------------- | +| `{...}` | Block expression | | `Type {...}` | `struct` literal | --- @@ -9045,24 +9236,25 @@ Sources: The Cairo prelude is a collection of commonly used modules, functions, data types, and traits that are automatically brought into scope of every module in a Cairo crate without needing explicit import statements. It provides the basic building blocks for starting Cairo programs and writing smart contracts. -The core library prelude is defined in the *lib.cairo* file of the corelib crate and contains: -* Data types: integers, bools, arrays, dicts, etc. -* Traits: behaviors for arithmetic, comparison, and serialization operations. -* Operators: arithmetic, logical, bitwise. -* Utility functions - helpers for arrays, maps, boxing, etc. +The core library prelude is defined in the _lib.cairo_ file of the corelib crate and contains: + +- Data types: integers, bools, arrays, dicts, etc. +- Traits: behaviors for arithmetic, comparison, and serialization operations. +- Operators: arithmetic, logical, bitwise. +- Utility functions - helpers for arrays, maps, boxing, etc. Since the core library prelude is automatically imported, its contents are available for use in any Cairo crate without explicit imports. This allows usage like `ArrayTrait::append()` or the `Default` trait without explicit scoping. ### Cairo Editions -You can choose which prelude version to use by specifying the edition in the *Scarb.toml* configuration file. For example, adding `edition = "2024_07"` loads the prelude from July 2024. New projects created via `scarb new` automatically include `edition = "2024_07"`. Different prelude versions expose different functions and traits, making the specification of the correct edition important. Generally, new projects should start with the latest edition. +You can choose which prelude version to use by specifying the edition in the _Scarb.toml_ configuration file. For example, adding `edition = "2024_07"` loads the prelude from July 2024. New projects created via `scarb new` automatically include `edition = "2024_07"`. Different prelude versions expose different functions and traits, making the specification of the correct edition important. Generally, new projects should start with the latest edition. Here is the list of available Cairo editions (i.e prelude versions) with their details: -| Version | Details | -| --- | --- | -| `2024-07` | details for 2024-07 | -| `2023-11` | details for 2023-11 | +| Version | Details | +| -------------------- | ------------------- | +| `2024-07` | details for 2024-07 | +| `2023-11` | details for 2023-11 | | `2023-10` / `2023-1` | details for 2023-10 | --- @@ -9085,11 +9277,13 @@ This section covers useful development tools provided by the Cairo project, incl Scarb projects can be formatted using the `scarb fmt` command. If using Cairo binaries directly, run `cairo-format`. This tool ensures consistent style across collaborative projects. To format any Cairo project, run the following inside the project directory: + ``` scarb fmt ``` To skip formatting for specific code sections, use `#[cairofmt::skip]`: + ``` #[cairofmt::skip] let table: Array = array![