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..8fbdbb3 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,7 +97,7 @@ 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, }, }); 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..f8c205a 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,18 @@ 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 +152,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..dddc3cc 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 +--- +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 +--- -Introduction to Cairo +# Introduction to Cairo and The Cairo Book -What is Cairo? +## Book Overview and Resources -# What is Cairo? +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. -## Overview +Additional resources for mastering Cairo include: -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. +- **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. -## Core Technology and Applications +## What is Cairo? -- **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. +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. -## Design Philosophy +- **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. -- **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. +## Applications and Context -## Target Audience +Cairo's primary application today is **Starknet**, a Layer 2 scaling solution for Ethereum. -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. +- **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. -Getting Started with the Cairo Book +## Audience and Prerequisites -# Getting Started with the Cairo Book +This book assumes basic programming knowledge (variables, functions, data structures). Prior Rust experience is helpful but not required. -This section outlines recommended reading paths through the Cairo book based on your background and goals. +The book caters to three main audiences with recommended reading paths: -## For General-Purpose Developers +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+). -Focus on chapters 1-12, which cover core language features and programming concepts without deep dives into smart contract specifics. +## References -## For New Smart Contract Developers +- 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 -Read the book from beginning to end to build a solid foundation in both Cairo language fundamentals and smart contract development principles. +--- -## For Experienced Smart Contract Developers +Sources: -A focused path is recommended: +- https://www.starknet.io/cairo-book/ch01-01-installation.html +- https://www.starknet.io/cairo-book/ch01-00-getting-started.html -- Chapters 1-3: Cairo basics -- Chapter 8: Cairo's trait and generics system -- Chapter 15: Smart contract development -- Reference other chapters as needed. +--- -### Prerequisites +# Setting Up the Development Environment -Basic programming knowledge (variables, functions, data structures) is assumed. Prior experience with Rust is helpful but not required. +### 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's Architecture +**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. -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. +**Starknet Foundry** is a toolchain supporting features like writing and running tests, deploying contracts, and interacting with the Starknet network. -## Components of Cairo - -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: +If successful, the output will show: -```bash +``` 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,260 @@ 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: -- 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. +- 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). -The `main` function: +--- -- 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. +Sources: -Conclusion and Resources +- https://www.starknet.io/cairo-book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html -# Conclusion and Resources +--- -Congratulations on building your first Cairo program! You've successfully: +## Advanced Scarb Features and Project Structure -- 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`. +### Dependency Management in Scarb -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: +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. -```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". +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. + +### 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. -## Keywords and Built-ins +### Programming Languages and Compilation -Cairo has several types of keywords and built-in functions: +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). -### Keywords +### Smart Contract Use Cases -- **`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. +Smart contracts facilitate various applications: -### Reserved Keywords +- **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. -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`. +## The Rise of Starknet and Cairo -### Built-in Functions +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 functions have special purposes and should not be used as names for other items: +### Setting up Starkli and Account Preparation -- **`assert`**: Checks a boolean expression; triggers `panic` if false. -- **`panic`**: Acknowledges an error and terminates the program. +Aside from Scarb, you need **Starkli**, a command-line tool for Starknet interaction. Verify the version matches the required specification: -## Operators and Symbols +> 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. -Cairo uses various operators and symbols for different purposes. +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 +``` +starkli class-hash-at --rpc http://0.0.0.0:5050 +``` -Operators have specific explanations and may be overloadable with associated traits: +### Contract Deployment Workflow -| 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 | | +Contracts must be declared before deployment using the `starkli declare` command: + +``` +starkli declare target/dev/listing_99_12_vote_contract_Vote.contract_class.json --rpc http://0.0.0.0:5050 --account katana-0 +``` -### Non-Operator Symbols +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. -These symbols have specific meanings when used alone or within paths: +--- -| Symbol | Explanation | -| :-------------------------------------- | :---------------------------------------- | -| `..._u8`, `..._usize`, `..._bool`, etc. | Numeric literal of specific type | -| `\"...\"` | String literal | -| `'...'` | Short string, 31 ASCII characters maximum | -| `_` | “Ignored” pattern binding | +Sources: -#### Path-Related Syntax +- 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 -| 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 +--- -| 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 | +Sources: -## Statements and Expressions +- 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 -Cairo functions consist of a series of statements, optionally ending in an expression. +--- -- **Statements**: Instructions that perform an action but do not return a value. Example: `let y = 6;`. -- **Expressions**: Evaluate to a resulting value. +## Cairo Program Structure and Fundamentals -Attempting to assign a statement to a variable results in an error, as statements do not return values. +### Anatomy of a Cairo Program -```cairo -#[executable] +Every executable Cairo program begins execution in the `main` function. + +The `main` function declaration: + +```rust fn main() { - // let x = (let y = 6); // This is an error + } ``` -## Comments +- 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 are ignored by the compiler and used for human readability. +The body of `main` often contains code that performs actions, such as printing to the terminal: -### Single-line Comments +```rust + println!("Hello, World!"); +``` -Start with `//` and continue to the end of the line. +The `println!` syntax calls a Cairo macro; calling a function would omit the exclamation mark (e.g., `println`). -```cairo -// This is a single-line comment +### 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. + +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: +Comments can also appear at the end of lines containing code: -```cairo +```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. +**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. -````cairo +````rust /// Returns the sum of `arg1` and `arg2`. /// `arg1` cannot be zero. /// @@ -623,316 +644,303 @@ fn add(arg1: felt252, arg2: felt252) -> felt252 { } ```` -## Common Programming Patterns and Potential Vulnerabilities +--- -Certain programming patterns can lead to unintended behavior if not handled carefully. +Sources: -### Operator Precedence in Expressions +- https://www.starknet.io/cairo-book/ch02-01-variables-and-mutability.html +- https://www.starknet.io/cairo-book/ch04-01-what-is-ownership.html -Ensure expressions involving `&&` and `||` are properly parenthesized to control precedence. +--- -```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" -); +### Variables, Mutability, and Scoping -// ✅ fixed -assert!( - (mode == Mode::None || mode == Mode::Recovery) && (ctx.coll_ok && ctx.debt_ok), - "EMERGENCY_MODE" -); -``` +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. -### Unsigned Loop Underflow +#### Variables and Immutability -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. +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. -```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; -} -``` +To demonstrate this, if we use the following code: -### Bit-packing into `felt252` +Filename: src/lib.cairo -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. - -```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"); - - 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. - -```cairo -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::pedersen::PedersenTrait; +``` +$ 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 +``` -#[derive(Drop, Hash, Serde, Copy)] -struct StructForHash { - first: felt252, - second: felt252, - third: (u32, u32), - last: bool, -} +#### Constants -#[executable] -fn main() -> (felt252, felt252) { - let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; +_Constants_ are similar to immutable variables but have key differences: - // 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(); +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. +Output: -### 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 +``` +$ 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 +``` -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. +Shadowing allows type changes, unlike `mut`: -### Cells Organization +```rust +#[executable] +fn main() { + 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); +} +``` -The Range Check builtin uses a dedicated memory segment with the following characteristics: +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`): -- **Valid values**: Field elements in the range `[0, 2^128 - 1]`. -- **Error conditions**: Values ≥ 2^128 or relocatable addresses. +```rust +#[executable] +fn main() { + let mut x: u64 = 2; + println!("The value of x is: {}", x); + x = 5_u8; + println!("The value of x is: {}", x); +} +``` -Variables, Scope, and Mutability +Error output: -# Variables, Scope, and Mutability +``` +$ 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; + ^^^^ -Cairo enforces an immutable memory model by default, meaning variables are immutable. However, the language provides mechanisms to handle mutability when needed. +error: could not compile `no_listing_05_mut_cant_change_type` due to previous error +error: `scarb` command exited with error +``` -## Mutability +#### Variable Scope -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. +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,does_not_compile +```rust +//TAG: ignore_fmt #[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); + { // 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 } ``` -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. +Listing 4-1: A variable and the scope in which it is valid -```cairo +##### 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 x = 5; - println!("The value of x is: {}", x); - x = 6; - println!("The value of x is: {}", x); + let mut arr: Array = array![]; + arr.append(1); + arr.append(2); } ``` -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. - -## Constants +--- -Constants are similar to immutable variables but have key differences: +Sources: -- 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. +- https://www.starknet.io/cairo-book/ch02-02-data-types.html -Cairo's naming convention for constants is all uppercase with underscores. +--- -```cairo,noplayground -struct AnyStruct { - a: u256, - b: u32, -} +# Data Types: Scalars and Primitives -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]; -``` +## Felt Type -Constants are useful for values that are known at compile time and used across multiple parts of the program. +... -## Shadowing +### Unsigned Integers -Shadowing occurs when a new variable is declared with the same name as a previous variable. The new variable +... +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; let y: u32 = x.try_into().unwrap(); } ``` +```` -## 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 +961,318 @@ 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"; +} ``` -#### Byte Array Strings +This covers all points concisely using H2/H3 subheadings relative to the H1 section start. -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. +--- -```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; +Sources: -# let long_string: ByteArray = "this is a string which has more than 31 characters"; -# } -``` +- 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 -## Compound Types +--- -### The Tuple Type +## Byte Array Strings -A tuple groups together values of various types into a single compound type. Tuples have a fixed length. +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. + +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 Tuple Type -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. +A _tuple_ groups together values of potentially varying types into one compound type. Tuples have a fixed length. -To implement `Copy` for a custom type, use the `#[derive(Copy)]` annotation. The type and all its components must implement `Copy`. +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,ignore_format -#[derive(Copy, Drop)] -struct Point { - x: u128, - y: u128, +```cairo +#[executable] +fn main() { + let tup: (u32, u64, bool) = (10, 20, true); } +``` +```cairo #[executable] fn main() { - let p1 = Point { x: 5, y: 10 }; - foo(p1); - foo(p1); -} + let tup = (500, 6, true); -fn foo(p: Point) { // do something with p + let (x, y, z) = tup; + + if y == 6 { + println!("y is 6!"); + } } ``` -## 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 +Destructuring can also happen during declaration: -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$. +```cairo +#[executable] +fn main() { + let (x, y): (felt252, felt252) = (2, 3); +} +``` -### Data types using more than 252 bits +### The Unit Type () -Types like `u256`, `u512`, arrays, spans, enums, structs, tuples, and byte arrays have non-trivial serialization. +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. -**Serialization of Structs:** +## The Fixed Size Array Type -Struct serialization is determined by the order and types of its members. +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 -struct MyStruct { - a: u256, - b: felt252, - c: Array +```cairo +#[executable] +fn main() { + let arr1: [u64; 5] = [1, 2, 3, 4, 5]; } ``` -Serialization of `MyStruct { a: 2, b: 5, c: [1,2,3] }` results in `[2,0,5,3,1,2,3]`. +Arrays are efficient because their size is known at compile-time. They can be initialized concisely using `[initial_value; length]`: -**Serialization of Byte Arrays:** +```cairo + let a = [3; 5]; +``` -A `ByteArray` consists of `data` (an array of 31-byte chunks) and `pending_word` (remaining bytes) with its length. +### Accessing Fixed Size Arrays Elements -**Example 1 (short string):** `hello` (0x68656c6c6f) +Elements can be accessed by deconstructing the array: -```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 my_arr = [1, 2, 3, 4, 5]; + + // Accessing elements of a fixed-size array by deconstruction + let [a, b, c, _, _] = my_arr; + println!("c: {}", c); // c: 3 +} ``` -## Type Conversion +## Common Collections -Cairo uses the `try_into` and `into` methods from the `TryInto` and `Into` traits for type conversion. +Cairo provides common collection types, primarily Arrays and Dictionaries. -### Into +### Array Operations -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. +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_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 mut a = ArrayTrait::new(); + a.append(0); + a.append(1); - 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(); + // using the `at()` method + let first = *a.at(0); + assert!(first == 0); + // using the subscripting operator + let second = *a[1]; + assert!(second == 1); } ``` -### TryInto +Use `get()` for graceful handling of out-of-bounds access. To determine size, use `len()` (returns `usize`), and check if empty with `is_empty()`. + +The `array!` macro simplifies compile-time array creation: -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. +Without `array!`: ```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 arr = ArrayTrait::new(); + arr.append(1); + arr.append(2); + arr.append(3); + arr.append(4); + arr.append(5); +``` - let my_large_u16: u16 = 2048; - // This will panic: - // let my_large_u8: u8 = my_large_u16.try_into().unwrap(); +With `array!`: + +```cairo + let arr = array![1, 2, 3, 4, 5]; +``` + +To store multiple types in an array, an `Enum` must be used to define a custom data type: + +```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))); } ``` -## Debugging with `Debug` and `Display` Traits +### Span -### `Debug` for Debugging Purposes +`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 `Debug` trait allows printing instances of a type for debugging. It is required for `assert_xx!` macros in tests. +To create a `Span` from an `Array`, call the `span()` method: ```cairo -#[derive(Copy, Drop, Debug)] -struct Point { - x: u8, - y: u8, +#[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 p = Point { x: 1, y: 3 }; - println!("{:?}", p); + 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"); } ``` -### `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. +Sources: -```cairo -use core::fmt::{Display, Error, Formatter}; +- 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, } -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})") - } -} - #[executable] fn main() { let p = Point { x: 1, y: 3 }; - println!("{}", p); // Output: Point: (1, 3) + println!("{:?}", p); } ``` -### Print in Hexadecimal - -Integer values can be printed in hexadecimal using the `{:x}` notation. The `LowerHex` trait is implemented for common types. +Output from running this example: -### Print Debug Traces +``` +scarb execute +Point { x: 1, y: 3 } +``` -This refers to using the `Debug` trait for printing detailed information, especially during debugging. +#### Default for Default Values -## `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. -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]`. +Example: -```cairo +```rust #[derive(Default, Drop)] struct A { item1: felt252, @@ -1211,15 +1298,198 @@ 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 +1503,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 +1522,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 +1535,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 +1555,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 +1573,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 +1591,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. +--- -Control Flow +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 -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 + +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 +1694,36 @@ 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: -```cairo +``` +$ 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 + +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 +1736,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 +1755,21 @@ 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,13 +1787,28 @@ 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() { + 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; @@ -1471,11 +1826,13 @@ fn main() { } ``` -#### Returning Values from Loops +Executing this program skips printing when `i` equals 5. -A `loop` can return a value by specifying it after the `break` expression. +### Returning Values from Loops -```cairo +A value can be returned from a `loop` by placing the value after the `break` keyword. + +```rust #[executable] fn main() { let mut counter = 0; @@ -1491,11 +1848,13 @@ fn main() { } ``` +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 +1870,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 +1885,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 +1899,28 @@ 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) @@ -1555,458 +1935,301 @@ fn recursive_function(mut x: felt252) -> felt252 { } ``` -Enums and Pattern Matching - -# Enums and Pattern Matching +Both run until `x == 2` is met. -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) +} + +#[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; } ``` -## The `Option` Enum +### `let else` -The `Option` enum represents an optional value, with variants `Some(T)` and `None`. +`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!`). -```cairo,noplayground -enum Option { - Some: T, - None, +```rust +#[derive(Drop)] +enum MyEnum { + A: u32, + B: u32, } -``` -It helps prevent bugs by explicitly handling the absence of a value. +fn foo(a: MyEnum) { + let MyEnum::A(x) = a else { + println!("Called with B"); + return; + }; + println!("Called with A({x})"); +} -### Example: Finding a Value +#[executable] +fn main() { + foo(MyEnum::A(42)); + foo(MyEnum::B(7)); +} +``` -Functions can return `Option` to indicate success or failure. +## Practice Summary -```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; }, - } +To practice these concepts, try building programs to: - find_value_recursive(arr, value, index + 1) -} +- Generate the $n$-th Fibonacci number. +- Compute the factorial of a number $n$. -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 -} -``` +--- -## The `Match` Control Flow Construct +Sources: -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. +- 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 -### Basic `match` Example +--- -```cairo,noplayground -enum Coin { - Penny, - Nickel, - Dime, - Quarter, -} +## Structs and Custom Data Grouping -fn value_in_cents(coin: Coin) -> felt252 { - match coin { - Coin::Penny => 1, - Coin::Nickel => 5, - Coin::Dime => 10, - Coin::Quarter => 25, - } -} -``` +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. -### Patterns That Bind to Values +### Structs Versus Tuples -`match` arms can bind to parts of values, allowing extraction of data from enum variants. +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. -```cairo,noplayground +The tuple approach can lead to less clear code, as seen when calculating the area of a rectangle using indices: -#[derive(Drop, Debug)] // Debug so we can inspect the state in a minute -enum UsState { - Alabama, - Alaska, -} +Filename: src/lib.cairo -#[derive(Drop)] -enum Coin { - Penny, - Nickel, - Dime, - Quarter: UsState, +```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. +### Defining and Instantiating Structs -### Catch-all with the `_` Placeholder +#### Struct Definition -The `_` pattern matches any value without binding it, useful for default actions. +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 => true, - _ => false, - } +```c +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, } ``` -### Multiple Patterns with the `|` Operator - -The `|` operator allows matching multiple patterns in a single arm. - -```cairo,noplayground -fn vending_machine_accept(coin: Coin) -> bool { - match coin { - Coin::Dime | Coin::Quarter => true, - _ => false, - } -} -``` +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 - -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. - -## Trait Bounds - -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. - -### Anonymous Generic Implementation Parameters - -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`. - -## Structs with Generic Types - -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`. - -## Generic Methods - -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. - -## Defining a Trait - -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. - -### Generic Traits - -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. - -## 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. - -### Default Implementations - -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 - -Associated items are definitions tied to a trait, including associated types and associated constants. - -### Associated Types - -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. - -### Associated Constants - -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(); +```c +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, } - #[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 + let mut user1 = User { + active: true, username: "someusername123", email: "[email protected]", sign_in_count: 1, + }; + user1.email = "[email protected]"; } -``` - -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::>. +fn build_user(email: ByteArray, username: ByteArray) -> User { + User { active: true, username: username, email: email, sign_in_count: 1 } +} -error: could not compile `no_listing_02_pass_array_by_value` due to previous error -error: `scarb` command exited with error +fn build_user_short(email: ByteArray, username: ByteArray) -> User { + User { active: true, username, email, sign_in_count: 1 } +} ``` -### Value Destruction +A new instance can be constructed as the last expression in a function body to implicitly return it. -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. +### Struct Construction Shorthands -#### No-op Destruction: The `Drop` Trait +#### Field Init Shorthand -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 {} +When a function parameter name is identical to the corresponding struct field name, you can use a shorthand syntax, omitting the `: field_name` part. -#[executable] -fn main() { - A {}; // No error, 'A' is automatically dropped. +```c +fn build_user_short(email: ByteArray, username: ByteArray) -> User { + User { active: true, username, email, sign_in_count: 1 } } ``` -Without `#[derive(Drop)]`, attempting to let a type go out of scope without explicit destruction would result in a compile error. +This is equivalent to `username: username` and `email: email`. -#### Destruction with Side-effects: The `Destruct` Trait +#### Struct Update Syntax -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. +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. -### References and Snapshots +Filename: src/lib.cairo -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 +```c +use core::byte_array; #[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); +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, } #[executable] fn main() { - let new_struct = Cart { paid: true, items: 1, buyer: "Eli" }; - pass_data(new_struct); + // --snip-- - let new_box = BoxTrait::new(Cart { paid: false, items: 2, buyer: "Uri" }); - pass_pointer(new_box); + let user1 = User { + email: "[email protected]", username: "someusername123", active: true, sign_in_count: 1, + }; + + let user2 = User { email: "[email protected]", ..user1 }; } ``` -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. +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. -**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. +Sources: -When attempting to modify an array passed to a function, a mutable reference (`ref`) is necessary. Snapshots are unsuitable for mutation. +- https://www.starknet.io/cairo-book/ch05-03-method-syntax.html -```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 -} -``` +### Struct Methods and Associated Functions -Structs, Methods, and Associated Functions +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. -# Structs, Methods, and Associated Functions +#### Defining Methods -### Defining Methods +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. -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. +Listing 5-11 shows defining an `area` method: -```cairo, noplayground +``` #[derive(Copy, Drop)] struct Rectangle { width: u64, @@ -2030,13 +2253,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 +2283,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,1327 +2325,1020 @@ 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. +Sources: -```cairo -#[derive(Drop)] -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, -} +- 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 -#[executable] -fn main() { - let user1 = User { - email: "someone@example.com", username: "someusername123", active: true, sign_in_count: 1, - }; +--- - let user2 = User { - email: "another@example.com", - ..user1 // Uses remaining fields from user1 - }; -} -``` +# Enums and Pattern Matching -### Field Init Shorthand +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. -When struct fields and function parameters share the same names, you can use shorthand to initialize the struct. +## Enum Variants and Data Association -```cairo -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, +Enum variants can exist without associated data, or they can store associated values. + +### Variants Without Associated Data + +Variants are named using PascalCase. + +```rust +#[derive(Drop)] +enum Direction { + North, + East, + South, + West, } -fn build_user_short(email: ByteArray, username: ByteArray) -> User { - User { active: true, username, email, sign_in_count: 1 } +#[executable] +fn main() { + let direction = Direction::North; } ``` -### Dictionaries and the `Destruct` Trait +### Variants With Associated Data -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 can be associated with specific data types, which can be the same type for all variants or different types for different variants. -```cairo -use core::dict::Felt252Dict; +Example associating a `u128` value: -#[derive(Destruct)] -struct A { - dict: Felt252Dict, +```rust +#[derive(Drop)] +enum Direction { + North: u128, + East: u128, + South: u128, + West: u128, } #[executable] fn main() { - A { dict: Default::default() }; // Compiles after deriving Destruct + let direction = Direction::North(10); } ``` -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`. +Enums can store more complex, custom data structures: -## 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`. +```rust +#[derive(Drop)] +enum Message { + Quit, + Echo: felt252, + Move: (u128, u128), +} +``` -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. +Here, `Quit` has no associated value, `Echo` holds a `felt252`, and `Move` holds a tuple of two `u128` values. -Here's how to call `panic` and return the error code `2`: +### Trait Implementations for Enums -Filename: src/lib.cairo +Traits can be defined and implemented for custom enums, allowing methods to be associated with the enum type. -```cairo -#[executable] -fn main() { - let mut data = array![2]; +```rust +trait Processing { + fn process(self: Message); +} - if true { - panic(data); +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) }, + } } - println!("This line isn't reached"); } ``` -## Recoverable Errors with `Result` - -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. - -### The `Result` Enum +## The `Option` Enum -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. +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 -enum Result { - Ok: T, - Err: E, +```rust +enum Option { + Some: T, + None, } ``` -### The `ResultTrait` +This is useful when a function might not return a value, such as finding an element's index in an array: + +```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; }, + } -The `ResultTrait` provides methods for interacting with `Result` values: + find_value_recursive(arr, value, index + 1) +} -```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; +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 } ``` -- **`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. - -The `<+Drop>` and `<+Drop>` constraints indicate that these methods require a `Drop` trait implementation for the generic types. +## The `match` Control Flow Construct -Consider a function that handles potential overflow during `u128` addition: +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_overflowing_add(a: u128, b: u128) -> Result; -``` +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. -This function returns `Ok(sum)` if successful or `Err(overflowed_value)` if an overflow occurs. +Example using a simple `Coin` enum: -Example of converting `Result` to `Option`: +```rust +enum Coin { + Penny, + Nickel, + Dime, + Quarter, +} -```cairo,noplayground -fn u128_checked_add(a: u128, b: u128) -> Option { - match u128_overflowing_add(a, b) { - Ok(r) => Some(r), - Err(r) => None, +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') - } +#[derive(Drop)] +enum Coin { + Penny, + Nickel, + Dime, + Quarter: UsState, } -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) +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 + }, + } } ``` -Listing 9-1: A function that returns errors to the calling code using a `match` expression. +When `Coin::Quarter(state)` matches, `state` binds to the inner `UsState` value. -### A Shortcut for Propagating Errors: the `?` Operator +### Matching with `Option` -The `?` operator simplifies error propagation. Listing 9-2 shows the `mutate_byte` function using the `?` operator: +`match` is used to safely handle `Option` variants: -```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) +```rust +fn plus_one(x: Option) -> Option { + match x { + Some(val) => Some(val + 1), + None => None, + } } -# -# #[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') - } + let five: Option = Some(5); + let six: Option = plus_one(five); + let none = plus_one(None); } ``` -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. +### Exhaustiveness and the `_` Placeholder -### 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. +Matches in Cairo must be exhaustive; all possible cases must be covered. Forgetting a case results in a compilation error: -Advanced Features and Patterns - -# Advanced Features and Patterns +```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. +``` -Contract Development +The `_` pattern acts as a catch-all for any value that hasn't matched previous arms, satisfying exhaustiveness without binding the value. -# Contract Development +```rust +fn vending_machine_accept(coin: Coin) -> bool { + match coin { + Coin::Dime => true, + _ => false, + } +} +``` -## Storage Variables +Note: There is no catch-all pattern in Cairo that allows you to use the value of the pattern. -Storage variables are used to store persistent data on the blockchain. They are defined within a special `Storage` struct annotated with the `#[storage]` attribute. +### Multiple Patterns with the `|` Operator -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. +The `|` (or) operator allows matching multiple patterns in a single arm: -## Storage Mappings +```rust +fn vending_machine_accept(coin: Coin) -> bool { + match coin { + Coin::Dime | Coin::Quarter => true, + _ => false, + } +} +``` -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. +### Matching Tuples -The process to access a value within a mapping, for example, a `Map`, involves the following steps: +Tuples can be matched against specific structures: -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. +```rust +#[derive(Drop)] +enum DayType { + Week, + Weekend, + Holiday, +} -Note that types like `ContractAddress` must be converted to a `StoragePointer` before they can be read from or written to. +fn vending_machine_accept(c: (DayType, Coin)) -> bool { + match c { + (DayType::Week, _) => true, + (_, Coin::Dime) | (_, Coin::Quarter) => true, + (_, _) => false, + } +} +``` -Cairo Circuits and Low-Level Operations +The `_` can be used within tuple matching to ignore parts of the tuple. -# Cairo Circuits and Low-Level Operations +### Matching `felt252` and Integer Variables -## Combining Circuit Elements and Gates +Integers fitting into a single `felt252` can be matched, but restrictions apply: -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)`: +1. The first arm must start at 0. +2. Each arm must cover a sequential segment contiguously. -```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(); -# } +```rust +fn roll(value: u8) { + match value { + 0 | 1 | 2 => println!("you won!"), + 3 => println!("you can roll again!"), + _ => println!("you lost..."), + } +} ``` -## Assigning Input Values +## `if let` as an Alternative -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: +`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 -pub enum AddInputResult { - /// All inputs have been filled. - Done: CircuitData, - /// More inputs are needed to fill the circuit instance's data. - More: CircuitInputAccumulator, +```rust +#[derive(Drop)] +enum Coin { + Penny, + Nickel, + Dime, + Quarter, } -``` - -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(); -# } +#[executable] +fn main() { + let coin = Coin::Quarter; + let mut count = 0; + if let Coin::Quarter = coin { + println!("You got a quarter!"); + } else { + count += 1; + } + println!("{}", count); +} ``` -## 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)`. +Sources: -```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, - ); +- https://www.starknet.io/cairo-book/ch09-02-recoverable-errors.html - // 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*); +### Advanced Pattern Matching and Error Handling - // 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 +#### Checking Result Variants Without Consumption - // No offsets needed for MulMod here - mul_offsets: -} -``` +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. -Data Structures and Collections +The implementation of the `ResultTrait` can be found elsewhere. -Arrays +#### Example: Handling Overflowing Addition -# Arrays +A function signature demonstrating error return for overflow might look like this: -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. +```rust +fn u128_overflowing_add(a: u128, b: u128) -> Result; +``` -## Creating an Array +This function returns the sum in the `Ok` variant if addition succeeds, or the overflowed value in the `Err` variant if it overflows. -Arrays are created using the `ArrayTrait::new()` call. You can optionally specify the type of elements the array will hold. +This `Result` can be used to create an `Option`-returning function that avoids panicking on overflow: -```cairo -#[executable] -fn main() { - let mut a = ArrayTrait::new(); - a.append(0); - a.append(1); - a.append(2); +```rust +fn u128_checked_add(a: u128, b: u128) -> Option { + match u128_overflowing_add(a, b) { + Ok(r) => Some(r), + Err(r) => None, + } } ``` -To explicitly define the type of elements: +In `u128_checked_add`, the `match` expression inspects the `Result`. If `Ok(r)`, it returns `Some(r)`; if `Err(r)`, it returns `None`. -```cairo, noplayground -let mut arr = ArrayTrait::::new(); -``` +#### Example: Parsing with Error Handling -Or: +Another common use case involves parsing, where failure results in an error type: -```cairo, noplayground -let mut arr:Array = ArrayTrait::new(); +```rust +fn parse_u8(s: felt252) -> Result { + match s.try_into() { + Some(value) => Ok(value), + None => Err('Invalid integer'), + } +} ``` -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!`: +Sources: -```cairo - let arr = array![1, 2, 3, 4, 5]; -``` +- https://www.starknet.io/cairo-book/ch08-01-generic-data-types.html -## Updating an Array +--- -### Adding Elements +### Generics and References -Elements are added to the end of an array using the `append()` method. +#### Example: Finding the Smallest Element with Generics -```cairo -# #[executable] -# fn main() { -# let mut a = ArrayTrait::new(); -# a.append(0); - a.append(1); -# a.append(2); -# } -``` +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. -### Removing Elements +```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; + } -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. + smallest +} -```cairo #[executable] fn main() { - let mut a = ArrayTrait::new(); - a.append(10); - a.append(1); - a.append(2); + let list: Array = array![5, 3, 10]; - let first_value = a.pop_front().unwrap(); - println!("The first value is {}", first_value); + // We need to specify that we are passing a snapshot of `list` as an argument + let s = smallest_element(@list); + assert!(s == 3); } ``` -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. +#### Trait Requirements for Generic Functions with Desnapping -## Reading Elements from an Array +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. -Elements can be accessed using the `get()` or `at()` methods, or the subscripting operator `[]`. +#### Corrected Function Signature -### `get()` Method +To make the function compile, both `Copy` and `Drop` traits must be explicitly required for the generic type `T`: -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. +```rust +fn smallest_element, impl TCopy: Copy, impl TDrop: Drop>( + list: @Array, +) -> T { + let mut smallest = *list[0]; + let mut index = 1; -```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") }, + while index < list.len() { + if *list[index] < smallest { + smallest = *list[index]; + } + index = index + 1; } -} -``` - -### `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); + smallest } ``` -## 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. +Sources: -## Storing Multiple Types with Enums +- https://www.starknet.io/cairo-book/ch11-01-closures.html +- https://www.starknet.io/cairo-book/ch11-00-functional-features.html -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), -} +# Closures -#[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))); -} -``` +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. -## Span +## Understanding Closures + +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. -`Span` is a struct representing a snapshot of an `Array`, providing safe, read-only access. It supports all `Array` methods except `append()`. +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. -To create a `Span` from an `Array`, use the `span()` method: +The following example demonstrates basic usage, array methods utilizing closures (`map`, `filter`), and capturing an environment variable (`x`): ```cairo -#[executable] -fn main() { - let mut array: Array = ArrayTrait::new(); - array.span(); +#[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 + } } -``` -Dictionaries (Felt252Dict) +#[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 + } +} -# Dictionaries (Felt252Dict) +#[executable] +fn main() { + let double = |value| value * 2; + println!("Double of 2 is {}", double(2_u8)); + println!("Double of 4 is {}", double(4_u8)); -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. + // This won't work because `value` type has been inferred as `u8`. + //println!("Double of 6 is {}", double(6_u16)); -## Basic Use of Dictionaries + let sum = |x: u32, y: u32, z: u16| { + x + y + z.into() + }; + println!("Result: {}", sum(1, 2, 3)); -The `Felt252DictTrait` trait provides core dictionary operations: + let x = 8; + let my_closure = |value| { + x * (value + 3) + }; -1. `insert(felt252, T) -> ()`: Writes values to a dictionary. -2. `get(felt252) -> T`: Reads values from a dictionary. + println!("my_closure(1) = {}", my_closure(1)); -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. + 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 + }); -### Example: Basic Dictionary Operations + println!("double: {:?}", double); + println!("another: {:?}", another); -```cairo -use core::dict::Felt252Dict; + let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); + println!("even: {:?}", even); +} +``` -#[executable] -fn main() { - let mut balances: Felt252Dict = Default::default(); +## Closure Syntax and Type Inference - balances.insert('Alex', 100); - balances.insert('Maria', 200); +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. - let alex_balance = balances.get('Alex'); - assert!(alex_balance == 100, "Balance is not 100"); +Type annotations can be added for clarity: - let maria_balance = balances.get('Maria'); - assert!(maria_balance == 200, "Balance is not 200"); -} +```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 ; ``` -### Example: Updating Dictionary Values - -`Felt252Dict` allows updating values for existing keys: +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 -use core::dict::Felt252Dict; - +//TAG: does_not_compile #[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"); + let example_closure = |x| x; - // 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"); + let s = example_closure(5_u64); + let n = example_closure(5_u32); } ``` -## Dictionaries Underneath +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's memory is immutable. `Felt252Dict` simulates mutability by storing a list of entries. Each entry represents an access (read/write/update) and contains: +## Capturing the Environment -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. +Closures can include bindings from their enclosing scope. For example, `my_closure` in the first code block uses the captured binding `x = 8`. -An `Entry` struct is defined as: +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,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. +Sources: -### Example: Entry List Generation +- 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 -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'); -# } -``` +# Data Serialization and Hashing -This produces the following entry list: +## Data Serialization using Serde -| key | previous | new | -| :---: | -------- | --- | -| Alex | 0 | 100 | -| Maria | 0 | 50 | -| Alex | 100 | 200 | -| Maria | 50 | 50 | +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. -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". +Deriving `Drop` is required when serializing a structure owned by the current scope, as `serialize` takes a snapshot. -## Squashing Dictionaries +### Example: Serializing a Struct -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. +```rust +#[derive(Serde, Drop)] +struct A { + item_one: felt252, + item_two: felt252, +} -### Example: Squashing Reduction +#[executable] +fn main() { + let first_struct = A { item_one: 2, item_two: 99 }; + let mut output_array = array![]; + first_struct.serialize(ref output_array); + panic(output_array); +} +``` -Given this entry list: +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()`. -| 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 | +## Serialization Details for Complex Types -Squashing is automatically called via the `Destruct` trait implementation when a `Felt252Dict` goes out of scope. +### Types with Non-Trivial Serialization -## Entry and Finalize +Data types using more than 252 bits require special serialization handling: -The `entry` and `finalize` methods allow manual interaction with dictionary entries, mimicking internal operations. +- Unsigned integers larger than 252 bits: `u256` and `u512`. +- Arrays and spans. +- Enums. +- Structs and tuples. +- Byte arrays (strings). -- `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. +Basic types are serialized as a single-member list containing one `felt252` value. -### Example: Custom `get` Implementation +### Serialization of `u256` -```cairo,noplayground -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; +A `u256` value is represented by two `felt252` values: -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); +1. The 128 least significant bits (low part). +2. The 128 most significant bits (high part). - // Store the value to return - let return_value = prev_value; +| 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]` | - // Update the entry with `prev_value` and get back ownership of the dictionary - dict = entry.finalize(prev_value); +### Serialization of `u512` - // Return the read value - return_value -} -``` +The `u512` type is a struct containing four `felt252` members, each representing a 128-bit limb. -### Example: Custom `insert` Implementation +### Serialization of Structs and Tuples -```cairo,noplayground -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; +Members are serialized in the order they appear in the definition. -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); +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]` | - // Insert `entry` back in the dictionary with the updated value, - // and receive ownership of the dictionary - dict = entry.finalize(value); -} -``` +Total serialization: `[2,0,5,3,1,2,3]`. -## Dictionaries of Types not Supported Natively +### Serialization of Byte Arrays -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`). +A `ByteArray` (string) is serialized via a struct containing: -To store unsupported types, wrap them in `Nullable`, which uses `Box` to manage memory in a dedicated segment. +- `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: Storing `Span` in a Dictionary +**Example (`hello`, 5 bytes: `0x68656c6c6f`):** -```cairo -use core::dict::Felt252Dict; -use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; +``` +0, // Number of 31-byte words in the data array. +0x68656c6c6f, // Pending word +5 // Length of the pending word, in bytes +``` -#[executable] -fn main() { - // Create the dictionary - let mut d: Felt252Dict>> = Default::default(); +## Hashing in Cairo - // Create the array to insert - let a = array![8, 9, 10]; +Hashing converts input data (message) of any length into a fixed-size hash value deterministically. This is crucial for Merkle trees and data integrity. - // Insert it as a `Span` - d.insert(0, NullableTrait::new(a.span())); +### Hash Functions in Cairo - // Get value back - let val = d.get(0); +The Cairo core library provides two native hash functions: - // 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(), - }; +- **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. - // 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 Hash Functions -## Using Arrays inside Dictionaries +To hash, import relevant traits. Hashing involves initialization, updating the state, and finalizing the result. -Storing and modifying arrays in dictionaries requires careful handling due to `Array` not implementing the `Copy` trait. +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`. -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. +#### Poseidon Hashing Example (Requires `#[derive(Hash)]`) -### Example: Reading an Array Entry +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; -```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 +#[derive(Drop, Hash)] +struct StructForHash { + first: felt252, + second: felt252, + third: (u32, u32), + last: bool, } -``` -### Example: Appending to an Array in a Dictionary +#[executable] +fn main() -> felt252 { + let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; -```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)); + let hash = PoseidonTrait::new().update_with(struct_to_hash).finalize(); + hash } ``` -### Complete Example: Array Manipulation in Dictionary +#### Pedersen Hashing Example -```cairo -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; -use core::nullable::NullableTrait; +Pedersen requires an initial `felt252` base state. Hashing a struct can involve serializing it first, or using `update_with` with an arbitrary base state. -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)); -} +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::pedersen::PedersenTrait; -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 +#[derive(Drop, Hash, Serde, Copy)] +struct StructForHash { + first: felt252, + second: felt252, + third: (u32, u32), + last: bool, } #[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)); +fn main() -> (felt252, felt252) { + let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; - append_value(ref dict, 0, 30); + // 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(); - println!("After insertion: {:?}", get_array_entry(ref dict, 0)); -} -``` + 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); -## Nested Mappings + for value in serialized_struct { + state = state.update(value); + } -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. + // hash2 is the result of hashing only the fields of the struct + let hash2 = state.finalize(); -```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() -# } -# } -# } + (hash1, hash2) +} ``` -## Storage Address Computation for Mappings +### Advanced Hashing: Hashing Arrays with Poseidon -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. +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`. -## Summary +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::{PoseidonTrait, poseidon_hash_span}; -- `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. +#[derive(Drop)] +struct StructForHashArray { + first: felt252, + second: felt252, + third: Array, +} -Structs +#[executable] +fn main() { + let struct_to_hash = StructForHashArray { first: 0, second: 1, third: array![1, 2, 3, 4, 5] }; -# Structs + 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(); +} +``` -## 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. +Sources: -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. +- https://www.starknet.io/cairo-book/ch12-08-printing.html -## 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. +### Printing Standard Data Types -```cairo, noplayground -#[derive(Drop)] -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, -} -``` +Cairo uses two macros for printing standard data types: -You can derive multiple traits on structs, such as `Drop`, `PartialEq` for comparison, and `Debug` for debug-printing. +- `println!`: prints on a new line. +- `print!`: prints inline. -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. +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 -#[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", - }; + let a = 10; + let b = 20; + let c = 30; + + println!("Hello world!"); + println!("{} {} {}", a, b, c); // 10 20 30 + println!("{c} {a} {}", b); // 30 10 20 } ``` -## Accessing, Mutating, and Updating Structs +These macros use the `Display` trait. Attempting to print complex data types without implementing `Display` results in an error. -To get a specific value from a struct, we use dot notation. For example, to access `user1`'s email address, we use `user1.email`. +### Formatting -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. +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). ```cairo -# #[derive(Drop)] -# struct User { -# active: bool, -# username: ByteArray, -# email: ByteArray, -# sign_in_count: u64, -# } +use core::fmt::ByteArray; + #[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 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! + + 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); + + println!("{}", s); } ``` -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. +### Printing Custom Data Types -```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, - }; +Custom data types require manual implementation of the `Display` trait to be used with `{}` placeholders in printing macros. The trait signature is: - let user2 = User { email: "another@example.com", ..user1 }; +```cairo +trait Display { + fn fmt(self: @T, ref f: Formatter) -> Result<(), Error>; } ``` -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 +The `Formatter` struct contains the `buffer: ByteArray` where the formatted output is appended. -Consider a program to calculate the area of a rectangle. Without structs, we might use separate variables. +Implementing `Display` for a custom `Point` struct using `format!`: ```cairo -#[executable] -fn main() { - let width = 30; - let height = 10; - let area = area(width, height); - println!("Area is {}", area); -} +use core::fmt::{Display, Error, Formatter}; -fn area(width: u64, height: u64) -> u64 { - width * height +#[derive(Copy, Drop)] +struct Point { + x: u8, + y: u8, } -``` -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: +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(()) + } +} -```cairo #[executable] fn main() { - let rectangle = (30, 10); - let area = area(rectangle); - println!("Area is {}", area); -} - -fn area(dimension: (u64, u64)) -> u64 { - let (x, y) = dimension; - x * y + let p = Point { x: 1, y: 3 }; + println!("{}", p); // Point: (1, 3) } ``` -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. +The `write!` and `writeln!` macros can also be used within the `fmt` implementation to write formatted strings directly to the `Formatter`'s buffer. -## Enum Variants and Values +```cairo +use core::fmt::Formatter; -Here's a simple example of an enum with variants that do not have associated values: +#[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}"); -```cairo, noplayground -#[derive(Drop)] -enum Direction { - North, - East, - South, - West, + println!("{}", formatter.buffer); // helloworld 10 20 } ``` -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; -# } -``` +### Print in Hexadecimal -Variants can also have associated values. For example, to store the degree of a direction: +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. -```cairo, noplayground -#[derive(Drop)] -enum Direction { - North: u128, - East: u128, - South: u128, - West: u128, -} -# -# #[executable] -# fn main() { -# let direction = Direction::North(10); -# } -``` +### Print Debug Traces -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. +The `Debug` trait allows printing complex data types, especially useful for debugging. It is used by adding `:?` within placeholders (e.g., `println!("{:?}", my_struct)`). -## Recursive Types +- 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. -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. +--- -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: +Sources: -```cairo, noplayground -#[derive(Copy, Drop)] -enum BinaryTree { - Leaf: u32, - Node: (u32, BinaryTree, BinaryTree), -} +- 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 -#[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)); -} -``` +--- -Smart Pointers and Memory Management +--- -# Smart Pointers and Memory Management +Sources: -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. +- 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 -## The `Box` Type for Pointer Manipulation +--- -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. +# Core Data Structures and Serialization -`Box` is useful in two main scenarios: +## Arrays in Cairo -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. +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. -### Storing Data in the Boxed Segment with `Box` +### Creating an Array -The `Box` type facilitates storing data in the boxed segment. +Arrays are instantiated using `ArrayTrait::new()`. The type can be specified during instantiation: -```cairo +```rust #[executable] fn main() { - let b = BoxTrait::new(5_u128); - println!("b = {}", b.unbox()) + let mut a = ArrayTrait::new(); + a.append(0); + a.append(1); + a.append(2); } ``` -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. +Type specification examples: -### 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. +```rust +let mut arr = ArrayTrait::::new(); +``` -Consider the `BinaryTree` enum: +or -```cairo -mod display; -use display::DebugBinaryTree; +```rust +let mut arr:Array = ArrayTrait::new(); +``` -#[derive(Copy, Drop)] -enum BinaryTree { - Leaf: u32, - Node: (u32, Box, Box), -} +### Updating an Array +Elements are added to the end using `append()`: +```rust #[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))); - - println!("{:?}", root); + let mut a = ArrayTrait::new(); + a.append(0); + a.append(1); + a.append(2); } ``` -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`. +Elements are removed only from the front using `pop_front()`, which returns an `Option` containing the removed element or `None` if empty: -### Implementing `Destruct` for Memory Management - -For structs containing types like `Felt252Dict`, the `Destruct` trait must be implemented to define how the struct goes out of scope. +```rust +#[executable] +fn main() { + let mut a = ArrayTrait::new(); + a.append(10); + a.append(1); + a.append(2); -```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(); - } + let first_value = a.pop_front().unwrap(); + println!("The first value is {}", first_value); } ``` -Serialization and Iteration - -# Serialization and Iteration +## Serialization Formats -## Serialization of `u256` +### Array Serialization -A `u256` value in Cairo is serialized as two `felt252` values: +Arrays are serialized in the format: `, ,..., `. -- The first `felt252` contains the 128 least significant bits (low part). -- The second `felt252` contains the 128 most significant bits (high part). +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]`. -Examples: +### Enum Serialization -- `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]`. +An enum is serialized as: `,`. Enum variant indices are 0-based. -## Serialization of `u512` +**Example 1 (`Week` enum):** -The `u512` type is a struct containing four `felt252` members, each representing a 128-bit limb. - -## Serialization of Arrays and Spans - -Arrays and spans are serialized as: -`, ,..., ` - -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]`. - -```cairo, noplayground -let POW_2_128: u256 = 0x100000000000000000000000000000000 -let array: Array = array![10, 20, POW_2_128] -``` - -## Serialization of Enums - -Enums are serialized as: -`,` - -Enum variant indices are 0-based. - -**Example 1:** - -```cairo,noplayground +```rust enum Week { Sunday: (), // Index=0. Monday: u256, // Index=1. @@ -3438,9 +3350,9 @@ enum Week { | `Week::Sunday` | `0` | unit | `[0]` | | `Week::Monday(5)` | `1` | `u256` | `[1,5,0]` | -**Example 2:** +**Example 2 (`MessageType` enum):** -```cairo,noplayground +```rust enum MessageType { A, #[default] @@ -3451,1139 +3363,1105 @@ enum MessageType { | Instance | Index | Type | Serialization | | ------------------- | ----- | ------ | ------------- | -| `MessageType::A` | `1` | unit | `[0]` | -| `MessageType::B(6)` | `0` | `u128` | `[1,6]` | +| `MessageType::A` | `0` | unit | `[0]` | +| `MessageType::B(6)` | `1` | `u128` | `[1,6]` | | `MessageType::C` | `2` | unit | `[2]` | -The `#[default]` attribute does not affect serialization. +### String Serialization (Long Strings) -## Serialization of Structs and Tuples +For strings longer than 31 bytes, serialization involves 31-byte words, followed by a pending word and its length: -Structs and tuples are serialized by serializing their members sequentially in the order they appear in their definition. +For the string `Long string, more than 31 characters.`: -## Iteration Traits (`Iterator` and `IntoIterator`) +``` +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 +``` -The `Iterator` and `IntoIterator` traits facilitate iteration over collections in Cairo. +--- -- **`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. +Sources: -This design guarantees type-safe iteration and improves code ergonomics. +- 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 -```cairo, noplayground -// Collection type that contains a simple array -#[derive(Drop)] -pub struct ArrayIter { - array: Array, -} +--- -// T is the collection type -pub trait Iterator { - type Item; - fn next(ref self: T) -> Option; -} +## Felt252Dict: Key-Value Storage Implementation -impl ArrayIterator of Iterator> { - type Item = T; - fn next(ref self: ArrayIter) -> Option { - self.array.pop_front() - } -} +`Felt252Dict` is a built-in dictionary type in Cairo that overcomes the limitation of immutable memory by simulating mutable key-value storage behavior. -/// Turns a collection of values into an iterator -pub trait IntoIterator { - /// The iterator type that will be created - type IntoIter; - impl Iterator: Iterator; +### Basic Usage and Value Rewriting - fn into_iter(self: T) -> Self::IntoIter; -} +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 ArrayIntoIterator of IntoIterator> { - type IntoIter = ArrayIter; - fn into_iter(self: Array) -> ArrayIter { - ArrayIter { array: self } - } +`Felt252Dict` effectively allows overwriting stored values for a given key. For example: + +``` +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"); } ``` -Advanced Language Features +### Zero Initialization and Immutability Constraints + +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. -Ownership, References, and Snapshots +### Dictionaries Underneath -# Ownership, References, and Snapshots +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: -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. +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. -## References and Snapshots +``` +struct Entry { + key: felt252, + previous_value: T, + new_value: T, +} +``` -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. +Every interaction generates a new `Entry`: -### Snapshots +- `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). -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. +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: -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. +| key | previous | new | +| ----- | -------- | --- | +| Alex | 0 | 100 | +| Maria | 0 | 50 | +| Alex | 100 | 200 | +| Maria | 50 | 50 | -```cairo -#[executable] -fn main() { - let arr1: Array = array![]; +### Dictionary Destruction and Squashing - let (arr2, len) = calculate_length(arr1); -} +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. -fn calculate_length(arr: Array) -> (Array, usize) { - let length = arr.len(); // len() returns the length of an array +Types containing dictionaries cannot derive `Drop`. If a struct contains a `Felt252Dict`, it must implement `Destruct` manually to call `self.balances.squash()`: - (arr, length) +``` +impl UserDatabaseDestruct, +Felt252DictValue> of Destruct> { + fn destruct(self: UserDatabase) nopanic { + self.balances.squash(); + } } ``` -```cairo -let second_length = calculate_length(@arr1); // Calculate the current length of the array -``` +### Interacting with Entries using `entry` and `finalize` -```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. -``` +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. -### Desnap Operator +``` +fn entry(self: Felt252Dict, key: felt252) -> (Felt252DictEntry, T) nopanic +``` -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. +The `finalize` method inserts the entry back and returns ownership of the dictionary: -```cairo -#[derive(Drop)] -struct Rectangle { - height: u64, - width: u64, -} +``` +fn finalize(self: Felt252DictEntry, new_value: T) -> Felt252Dict +``` -#[executable] -fn main() { - let rec = Rectangle { height: 3, width: 10 }; - let area = calculate_area(@rec); - println!("Area: {}", area); -} +Implementing a custom `get` method using these tools involves reading the previous value and finalizing the entry with that same value: -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 -} ``` +use core::dict::{Felt252Dict, Felt252DictEntryTrait}; -Generics +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); -# Generics + // Store the value to return + let return_value = prev_value; -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. + // Update the entry with `prev_value` and get back ownership of the dictionary + dict = entry.finalize(prev_value); -## Generic Functions + // Return the read value + return_value +} +``` -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. +Implementing custom `insert` is similar, but the `finalize` call uses the new `value` instead of the previous one. -For example, a function to find the largest list can be implemented once using generics: +### Dictionaries in Custom Structures -```cairo -// Specify generic type T between the angulars -fn largest_list(l1: Array, l2: Array) -> Array { - if l1.len() > l2.len() { - l1 - } else { - l2 - } -} +`Felt252Dict` is fundamental for creating mutable custom data structures, such as implementing a mutable vector (`MemoryVec`) or a user database (`UserDatabase`). -#[executable] -fn main() { - let mut l1 = array![1, 2]; - let mut l2 = array![3, 4, 5]; +For instance, in `MemoryVec`, `set` overwrites a value at an index by inserting a new entry into the underlying dictionary: - // There is no need to specify the concrete type of T because - // it is inferred by the compiler - let l3 = largest_list(l1, l2); +``` +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)); } ``` -## Generic Methods and Traits +--- -Cairo allows defining generic methods within generic traits. Consider a `Wallet` struct with generic types for balance and address: +Sources: -```cairo,noplayground -struct Wallet { - balance: T, - address: U, -} -``` +- 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 -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: +--- -```cairo,noplayground -// This does not compile! -trait WalletMixTrait { - fn mixup(self: Wallet, other: Wallet) -> Wallet; -} +# Ownership Model and Type Traits -impl WalletMixImpl of WalletMixTrait { - fn mixup(self: Wallet, other: Wallet) -> Wallet { - Wallet { balance: self.balance, address: other.address } - } -} -``` +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: -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: +- **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. + +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 -trait WalletMixTrait { - fn mixup, U2, +Drop>( - self: Wallet, other: Wallet, - ) -> Wallet; +fn foo(mut arr: Array) { + arr.pop_front(); } -impl WalletMixImpl, U1, +Drop> of WalletMixTrait { - fn mixup, U2, +Drop>( - self: Wallet, other: Wallet, - ) -> Wallet { - Wallet { balance: self.balance, address: other.address } - } +#[executable] +fn main() { + let arr: Array = array![]; + foo(arr); + foo(arr); } ``` -Traits and Implementations +## The `Copy` Trait -# Traits and Implementations +The `Copy` trait allows simple types to be duplicated by copying felts without allocating new memory segments, bypassing default move semantics. -## `#[generate_trait]` Attribute +- 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)]`. -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. +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(Copy, Drop)] -struct Rectangle { - width: u64, - height: u64, -} - -#[generate_trait] -impl RectangleImpl of RectangleTrait { - fn area(self: @Rectangle) -> u64 { - (*self.width) * (*self.height) - } +struct Point { + x: u128, + y: u128, } #[executable] fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - println!("Area is {}", rect1.area()); + let p1 = Point { x: 5, y: 10 }; + foo(p1); + foo(p1); +} + +fn foo(p: Point) { // do something with p } ``` -## Snapshots and References +If `Copy` is not derived, attempting to pass `p1` twice results in a compile-time error. -Methods can accept `self` as a snapshot (`@`) if they don't modify the instance, or as a mutable reference (`ref self`) to allow modifications. +## Cloning and Return Values -```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; - } -} +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 mut rect2 = Rectangle { width: 10, height: 20 }; - rect2.scale(2); - println!("The new size is (width: {}, height: {})", rect2.width, rect2.height); + let arr1: Array = array![]; + let arr2 = arr1.clone(); } ``` -## Default Implementations - -Traits can provide default behavior for methods, allowing implementers to either use the default or override it. +Returning a value from a function is equivalent to _moving_ it to the caller. The following example illustrates scope and moves: ```cairo -// In src/lib.cairo -mod aggregator { - pub trait Summary { - fn summarize(self: @T) -> ByteArray { - "(Read more...)" - } - } +#[derive(Drop)] +struct A {} - #[derive(Drop)] - pub struct NewsArticle { - pub headline: ByteArray, - pub location: ByteArray, - pub author: ByteArray, - pub content: ByteArray, - } +#[executable] +fn main() { + let a1 = gives_ownership(); // gives_ownership moves its return + // value into a1 - impl NewsArticleSummary of Summary {} + let a2 = A {}; // a2 comes into scope - #[derive(Drop)] - pub struct Tweet { - pub username: ByteArray, - pub content: ByteArray, - pub reply: bool, - pub retweet: bool, - } + let a3 = takes_and_gives_back(a2); // a2 is moved into + // takes_and_gives_back, which also + // moves its return value into a3 - impl TweetSummary of Summary { - fn summarize(self: @Tweet) -> ByteArray { - format!("{}: {}", self.username, self.content) - } - } -} +} // Here, a3 goes out of scope and is dropped. a2 was moved, so nothing + // happens. a1 goes out of scope and is dropped. -use aggregator::{NewsArticle, Summary}; +fn gives_ownership() -> A { // gives_ownership will move its + // return value into the function + // that calls it -#[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", - }; + let some_a = A {}; // some_a comes into scope - println!("New article available! {}", news.summarize()); + some_a // some_a is returned and + // moves ownership to the calling + // function } -``` -This code prints `New article available! (Read more...)`. +// 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 -## 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 + some_a // some_a is returned and + // moves ownership to the calling + // function +} +``` -Implementations can be aliased upon import, which is useful for instantiating generic implementations with concrete types. +## Passing Variables to Functions -```cairo -trait Two { - fn two() -> T; -} +When passing a variable to a function, ownership rules dictate how the variable can be used afterward: -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() - } - } -} +- **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. -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. +Sources: -## Negative Impls +- https://www.starknet.io/cairo-book/ch04-02-references-and-snapshots.html +- https://www.starknet.io/cairo-book/ch03-01-arrays.html -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. +--- -## Associated Types (Experimental) +## Data Access Mechanisms: Snapshots and References -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`. +### Reading Elements from an Array -```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 - } -} -``` +To access array elements, you can use `get()` or `at()` array methods. `arr.at(index)` is equivalent to using the subscripting operator `arr[index]`. -## Manual `Destruct` Implementation +#### `get()` Method -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. +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. -```cairo -impl UserDatabaseDestruct, +Felt252DictValue> of Destruct> { - fn destruct(self: UserDatabase) nopanic { - self.balances.squash(); - } -} -``` +Here is an example with the `get()` method: -Advanced Type System Features +`\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` -# Negative Implementations +### Snapshots -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. +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`. -To use this feature, you must enable it in your `Scarb.toml` file with `experimental-features = ["negative_impls"]` under the `[package]` section. +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`. -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. +Attempting to modify values associated with snapshots results in a compiler error: -```cairo -#[derive(Drop)] -struct ProducerType {} +`\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 -#[derive(Drop, Debug)] -struct AnotherType {} +#### Desnap Operator -#[derive(Drop, Debug)] -struct AThirdType {} +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. -trait Producer { - fn produce(self: T) -> u32; -} +``\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 -trait Consumer { - fn consume(self: T, input: u32); -} +### Mutable References -impl ProducerImpl of Producer { - fn produce(self: ProducerType) -> u32 { - 42 - } -} +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. -// 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); - } -} +In Listing 4-5, a mutable reference swaps fields: -#[executable] -fn main() { - let producer = ProducerType {}; - let another_type = AnotherType {}; - let third_type = AThirdType {}; - let production = producer.produce(); +`\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` - // producer.consume(production); // Invalid: ProducerType does not implement Consumer - another_type.consume(production); - third_type.consume(production); -} -``` +--- -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. +Sources: -Function Safety and Panics +- https://www.starknet.io/cairo-book/ch12-01-custom-data-structures.html -## Function Safety and Panics +--- -## `nopanic` Notation +# Simulating Dynamic Structures with Dictionaries -The `nopanic` notation indicates that a function will never panic. Only `nopanic` functions can be called within another function annotated as `nopanic`. +### Simulating a Dynamic Array with Dicts -A function guaranteed to never panic: +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: -```cairo,noplayground -fn function_never_panic() -> felt252 nopanic { - 42 +```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; } ``` -This function will always return `42` and is guaranteed not to panic. +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. + +The structure definition and implementation are provided below: -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: +```rust +use core::dict::Felt252Dict; +use core::nullable::NullableTrait; +use core::num::traits::WrappingAdd; -```cairo,noplayground -fn function_never_panic() nopanic { - assert!(1 == 1, "what"); +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; } -``` -Compiling such a function yields an error indicating that a `nopanic` function calls another function that may panic. +struct MemoryVec { + data: Felt252Dict>, + len: usize, +} -## `panic_with` Attribute +impl DestructMemoryVec> of Destruct> { + fn destruct(self: MemoryVec) nopanic { + self.data.squash(); + } +} -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`. +impl MemoryVecImpl, +Copy> of MemoryVecTrait, T> { + fn new() -> MemoryVec { + MemoryVec { data: Default::default(), len: 0 } + } -Example: + fn get(ref self: MemoryVec, index: usize) -> Option { + if index < self.len() { + Some(self.data.get(index.into()).deref()) + } else { + None + } + } -```cairo -#[panic_with('value is 0', wrap_not_zero)] -fn wrap_if_not_zero(value: u128) -> Option { - if value == 0 { - None - } else { - Some(value) + fn at(ref self: MemoryVec, index: usize) -> T { + assert!(index < self.len(), "Index out of bounds"); + self.data.get(index.into()).deref() } -} -#[executable] -fn main() { - wrap_if_not_zero(0); // this returns None - wrap_not_zero(0); // this panics with 'value is 0' + 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 + } } ``` -Storage Optimization and Modularity - -# Storage Optimization and Modularity +### Simulating a Stack with Dicts -## Storage Packing +A Stack is a LIFO (Last-In, First-Out) collection. We define the necessary interface: -The `StorePacking` trait allows for optimizing storage by packing multiple fields into a single storage variable. This is achieved using bitwise operations: +```rust +trait StackTrait { + fn push(ref self: S, value: T); + fn pop(ref self: S) -> Option; + fn is_empty(self: @S) -> bool; +} +``` -- **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. +The stack structure, `NullableStack`, also uses a dictionary for data storage and a length counter: -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. +```rust +struct NullableStack { + data: Felt252Dict>, + len: usize, +} +``` -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`. +The implementation details for the stack trait methods are as follows: ```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; +use core::dict::Felt252Dict; +use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; + +trait StackTrait { + fn push(ref self: S, value: T); + fn pop(ref self: S) -> Option; + fn is_empty(self: @S) -> bool; +} + +struct NullableStack { + data: Felt252Dict>, + len: usize, +} -#[derive(Copy, Drop, Serde)] -struct MyStruct { - field1: u8, - field2: u32, - field3: u8, +impl DestructNullableStack> of Destruct> { + fn destruct(self: NullableStack) nopanic { + self.data.squash(); + } } -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 +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 pop(ref self: NullableStack) -> Option { + if self.is_empty() { + return None; + } + self.len -= 1; + Some(self.data.get(self.len.into()).deref()) } - 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 } + fn is_empty(self: @NullableStack) -> bool { + *self.len == 0 } } ``` -## Components +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. -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. +--- -Functional Programming: Closures and Iterators +Sources: -# Functional Programming: Closures and Iterators +- https://www.starknet.io/cairo-book/ch12-02-smart-pointers.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html -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. +--- -> Note: Closures were introduced in Cairo 2.9 and are still under development. +# Smart Pointers and Recursive Types -## Understanding Closures +## Smart Pointers Overview -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. +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. -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. +## The `Box` Type -## Implementing Your Functional Programming Patterns with Closures +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. -Closures can be passed as function arguments, a mechanism heavily utilized in functional programming through functions like `map`, `filter`, and `reduce`. +### Storing Data in the Boxed Segment -Here's a potential implementation of `map` to apply a function to all items in an array: +Boxes have minimal performance overhead but are useful when: -```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 - } -} +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. -#[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 - } -} +The syntax for creating and accessing a box is shown below: +```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 b = BoxTrait::new(5_u128); + println!("b = {}", b.unbox()) +} +``` - // This won't work because `value` type has been inferred as `u8`. - //println!("Double of 6 is {}", double(6_u16)); +Listing 12-1: Storing a `u128` value in the boxed segment using a box - let sum = |x: u32, y: u32, z: u16| { - x + y + z.into() - }; - println!("Result: {}", sum(1, 2, 3)); +When instantiated, the value is stored in the boxed segment, and `b.unbox()` accesses it. - let x = 8; - let my_closure = |value| { - x * (value + 3) - }; +### Enabling Recursive Types with Boxes - println!("my_closure(1) = {}", my_closure(1)); +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. - 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 - }); +An example is defining a binary tree. An initial attempt fails because the `Node` variant holds another `BinaryTree` directly: - println!("double: {:?}", double); - println!("another: {:?}", another); +```rust +#[derive(Copy, Drop)] +enum BinaryTree { + Leaf: u32, + Node: (u32, BinaryTree, BinaryTree), +} - let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); - println!("even: {:?}", even); +#[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)); } ``` -Resource Management and Memory Safety +This is fixed by replacing the recursive type with `Box`: -# Resource Management and Memory Safety +```rust +mod display; +use display::DebugBinaryTree; -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. +#[derive(Copy, Drop)] +enum BinaryTree { + Leaf: u32, + Node: (u32, Box, Box), +} -## Smart Pointers +#[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))); -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. + println!("{:?}", root); +} +``` -Operator Overloading and Hashing +Listing 12-3: Defining a recursive Binary Tree using Boxes -# Operator Overloading and Hashing +The `Node` variant now holds `(u32, Box, Box)`, allowing the compiler to calculate the size. -## Operator Overloading +### Using Boxes to Improve Performance -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. +Passing pointers via boxes avoids copying large amounts of data when transferring ownership between functions. Only the pointer (a single value) is copied. -For example, combining two `Potion` structs, which have `health` and `mana` fields, can be done using the `+` operator by implementing the `Add` trait: +```rust +#[derive(Drop)] +struct Cart { + paid: bool, + items: u256, + buyer: ByteArray, +} -```cairo -struct Potion { - health: felt252, - mana: felt252, +fn pass_data(cart: Cart) { + println!("{} is shopping today and bought {} items", cart.buyer, cart.items); } -impl PotionAdd of Add { - fn add(lhs: Potion, rhs: Potion) -> Potion { - Potion { health: lhs.health + rhs.health, mana: lhs.mana + rhs.mana } - } +fn pass_pointer(cart: Box) { + let cart = cart.unbox(); + println!("{} is shopping today and bought {} items", cart.buyer, cart.items); } #[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); + 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); } ``` -## Hashing +Listing 12-4: Storing large amounts of data in a box for performance. -### When to Use Them? +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. -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. +## The `Nullable` Type for Dictionaries -### Working with Hashes +`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. -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`. +`Nullable` wraps the value inside a `Box`, allowing complex types to be stored in dictionaries by utilizing the boxed segment. -The `HashStateTrait` and `HashStateExTrait` define basic methods for managing hash states: initializing, updating with values, and finalizing the computation. +For example, to store a `Span` in a dictionary, you must use `Nullable` wrapping a `Box`: -```cairo,noplayground -/// A trait for hash state accumulators. -trait HashStateTrait { - fn update(self: S, value: felt252) -> S; - fn finalize(self: S) -> felt252; -} +```rust +use core::dict::Felt252Dict; +use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; -/// Extension trait for hash state accumulators. -trait HashStateExTrait { - /// Updates the hash state with the given value. - fn update_with(self: S, value: T) -> S; -} +#[executable] +fn main() { + // Create the dictionary + let mut d: Felt252Dict>> = Default::default(); -/// 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; -} + // Create the array to insert + let a = array![8, 9, 10]; + + // Insert it as a `Span` + d.insert(0, NullableTrait::new(a.span())); + +//... ``` -### Advanced Hashing: Hashing Arrays with Poseidon +This approach is necessary because `Array` does not implement the `Copy` trait required for reading from a dictionary, whereas `Span` does. -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: +Sources: -```cairo,noplayground -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::{PoseidonTrait, poseidon_hash_span}; -``` +- 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 -Define the struct. Deriving `Hash` on this struct would fail due to the `Array` field. +--- -```cairo, noplayground -#[derive(Drop)] -struct StructForHashArray { - first: felt252, - second: felt252, - third: Array, -} -``` +--- -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. +Sources: -```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] }; +- 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 - 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(); -} -``` +--- -Function Inlining and Output +# Modules, Paths, and Project Structure -# Function Inlining and Output +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. -## Function Inlining +## Project Organization: Packages, Crates, and Modules -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. +Cairo uses several concepts to manage large codebases: -### How Inlining Works +- **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. -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. +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:** +## Defining Modules and Controlling Scope -Consider a program with an inlined function and a non-inlined function: +Modules let us organize code within a crate for readability and control privacy. Items within a module are private by default. -```cairo -#[executable] -fn main() { - inlined(); - not_inlined(); -} +### Module Documentation -#[inline(always)] -fn inlined() -> felt252 { - 'inlined' -} +Module documentation comments provide an overview of the entire module, prefixed with `//!`, and are placed above the module they describe. -#[inline(never)] -fn not_inlined() -> felt252 { - 'not inlined' +````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... } -``` - -The corresponding Sierra code for this example shows a `function_call` for `not_inlined` but not for `inlined`. +```` -### Benefits and Drawbacks +### Modules Cheat Sheet (Rules for Organization) -- **Benefits:** +When organizing code, the compiler follows these rules: - - Reduces function call overhead. - - Can enable further compiler optimizations by exposing the inlined code to the surrounding context. +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. -- **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. +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. -## Printing +### Separating Modules into Different Files -Cairo provides macros for printing data to the console, useful for program execution and debugging. +When modules grow, their definitions can be moved to separate files. -### Standard Data Types +- 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`. -Two macros are available for printing standard data types: +The `mod` keyword declares modules and tells Cairo where to look; it is not an "include" operation. -- `println!`: Prints output followed by a newline. -- `print!`: Prints output on the same line. +## Paths for Referring to Items -Both macros accept a `ByteArray` string as the first argument. This string can be a simple message or include placeholders for formatting values. +Paths name items in the module tree, similar to filesystem navigation. -#### Placeholders +A path can be: -Placeholders within the string can be used in two ways: +- **Absolute path:** Starts from the crate root, beginning with the crate name. +- **Relative path:** Starts from the current module. -- 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. +Both forms use double colons (`::`) as separators. -These placeholder methods can be mixed. +### Absolute vs. Relative Paths -**Example:** +To call a function like `add_to_waitlist` defined deep within nested modules: ```cairo -#[executable] -fn main() { - let a = 10; - let b = 20; - let c = 30; +mod front_of_house { + mod hosting { + fn add_to_waitlist() {} + } - println!("Hello world!"); - println!("{} {} {}", a, b, c); // Output: 10 20 30 - println!("{c} {a} {}", b); // Output: 30 10 20 -} -``` + mod serving { + fn take_order() {} -Deref Coercion + fn serve_order() {} + + fn take_payment() {} + } +} -# Deref Coercion +pub fn eat_at_restaurant() { + // Absolute path + crate::front_of_house::hosting::add_to_waitlist(); -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. + // Relative path + front_of_house::hosting::add_to_waitlist(); +} +``` -## Understanding Deref Coercion with an Example +### Starting Relative Paths with `super` -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`. +The `super` keyword constructs a relative path starting from the parent module, analogous to `..` in filesystems. ```cairo -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, -} - -#[derive(Drop, Copy)] -struct Wrapper { - value: T, -} +fn deliver_order() {} -impl DerefWrapper of Deref> { - type Target = T; - fn deref(self: Wrapper) -> T { - self.value +mod back_of_house { + fn fix_incorrect_order() { + cook_order(); + super::deliver_order(); } + + fn cook_order() {} } ``` -This implementation of `Deref` for `Wrapper` simply returns the wrapped `value`. +## Privacy and Visibility with `pub` -## Practical Application of Deref Coercion +By default, code within a module is private to its parent modules. Items in child modules can see items in ancestor modules. -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. +To expose items: -```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); -} -``` +- 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`. -## Restricting Deref Coercion to Mutable Variables +For structs and enums: -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. +- `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. -```cairo, noplayground -//TAG: does_not_compile +To allow an item to be visible only within the defining crate, use `pub(crate)`. -use core::ops::DerefMut; +## Bringing Paths into Scope with the `use` Keyword -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, +The `use` keyword creates shortcuts to paths within the current scope, reducing path repetition. + +```cairo +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} + } } +use crate::front_of_house::hosting; -#[derive(Drop, Copy)] -struct Wrapper { - value: T, +pub fn eat_at_restaurant() { + hosting::add_to_waitlist(); // ✅ Shorter path } +``` -impl DerefMutWrapper> of DerefMut> { - type Target = T; - fn deref_mut(ref self: Wrapper) -> T { - self.value +A `use` statement only applies to the scope in which it is declared. + +### Idiomatic use of `use` + +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 +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} } } +use crate::front_of_house::hosting::add_to_waitlist; // Unidiomatic for functions -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); +pub fn eat_at_restaurant() { + add_to_waitlist(); } +``` -#[executable] -fn main() { - let mut wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; +For structs, enums, and traits, it is idiomatic to specify the full path when importing. - println!("Username: {}", wrapped_profile.username); - println!("Current age: {}", wrapped_profile.age); -} -``` +### Providing New Names with the `as` Keyword -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. +If two items with the same name are brought into scope, the `as` keyword can create an alias: -```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); - ^^^^^^^^ +```cairo +use core::array::ArrayTrait as Arr; -error: could not compile `no_listing_09_deref_coercion_example` due to previous error +#[executable] +fn main() { + let mut arr = Arr::new(); // ArrayTrait was renamed to Arr + arr.append(1); +} ``` -To resolve this, the variable `wrapped_profile` must be declared as mutable. - -```cairo, noplayground -//TAG: does_not_compile +### Importing Multiple Items from the Same Module -use core::ops::DerefMut; +Multiple items can be imported using curly braces `{}`: -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, -} +```cairo +// Assuming we have a module called `shapes` with the structures `Square`, `Circle`, and `Triangle`. +mod shapes { + #[derive(Drop)] + pub struct Square { + pub side: u32, + } -#[derive(Drop, Copy)] -struct Wrapper { - value: T, -} + #[derive(Drop)] + pub struct Circle { + pub radius: u32, + } -impl DerefMutWrapper> of DerefMut> { - type Target = T; - fn deref_mut(ref self: Wrapper) -> T { - self.value + #[derive(Drop)] + pub struct Triangle { + pub base: u32, + pub height: u32, } } -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); -} +// 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 mut wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; - - println!("Username: {}", wrapped_profile.username); - println!("Current age: {}", wrapped_profile.age); + let sq = Square { side: 5 }; + let cr = Circle { radius: 3 }; + let tr = Triangle { base: 5, height: 2 }; + // ... } ``` -## Calling Methods via Deref Coercion +### Re-exporting Names with `pub use` -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`. +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. ```cairo -struct MySource { - pub data: u8, -} - -struct MyTarget { - pub data: u8, -} - -#[generate_trait] -impl TargetImpl of TargetTrait { - fn foo(self: MyTarget) -> u8 { - self.data +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} } } -impl SourceDeref of Deref { - type Target = MyTarget; - fn deref(self: MySource) -> MyTarget { - MyTarget { data: self.data } - } -} +pub use crate::front_of_house::hosting; -#[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); +fn eat_at_restaurant() { + hosting::add_to_waitlist(); } ``` -In this example, `MySource` dereferences to `MyTarget`, which has a method `foo`. This allows `foo` to be called directly on `source`. +This allows external code to use `crate::hosting::add_to_waitlist()` instead of the longer internal path. + +--- -## Summary +Sources: -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. +- 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 -Associated Types and Data Packing +--- -# Associated Types and Data Packing +## Generics and Constrained Types -## Constraint Traits on Associated Items +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. -Associated items are an experimental feature. To use them, add `experimental-features = ["associated_item_constraints"]` to your `Scarb.toml`. +### Syntax for Generics -You can constrain associated items of a trait based on a generic parameter's type using the `[AssociatedItem: ConstrainedValue]` syntax after a trait bound. +Generics are defined using angle brackets `<...>` in various declarations: -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. +- **Definition**: `fn ident<...> ...`, `struct ident<...> ...`, `enum ident<...> ...`, `impl<...> ...` +- **Specification**: `path::<...>` or `method::<...>` (turbofish) is used to specify parameters in an expression. + +### Generic Functions + +Functions can operate on generic types, avoiding multiple type-specific implementations. When defining a generic function, generics are placed in the signature. + +For instance, a generic function signature: ```cairo -trait Extend { - fn extend[Item: A], +Destruct>(ref self: T, iterator: I); +// Specify generic type T between the angulars +fn largest_list(l1: Array, l2: Array) -> Array { + if l1.len() > l2.len() { + l1 + } else { + l2 + } } +``` -impl ArrayExtend> of Extend, T> { - fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { - for item in iterator { - self.append(item); - } +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: + +```cairo +fn largest_list>(l1: Array, l2: Array) -> Array { + if l1.len() > l2.len() { + l1 + } else { + l2 } } ``` -## `TypeEqual` Trait for Type Equality Constraints +### Constraints for Generic Types (Trait Bounds) -The `TypeEqual` trait from `core::metaprogramming` allows constraints based on type equality. +Trait bounds constrain generic types to only accept types that implement a particular behavior. We saw this with `Drop`. -### Excluding Specific Types +#### Anonymous Generic Implementation Parameter (`+` Operator) -`TypeEqual` can be used with negative implementations to exclude specific types from a trait implementation. +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`). + +An example using multiple constraints: ```cairo -trait SafeDefault { - fn safe_default() -> T; +fn smallest_element, +Copy, +Drop>(list: @Array) -> T { + let mut smallest = *list[0]; + for element in list { + if *element < smallest { + smallest = *element; + } + } + smallest } +``` -#[derive(Drop, Default)] -struct SensitiveData { - secret: felt252, +### Generic Data Types + +Structs and enums can be defined using generic type parameters. + +#### Generic Structs + +Structs use `<>` syntax after the name to declare type parameters, which are then used as field types. + +```cairo +#[derive(Drop)] +struct Wallet { + balance: T, } +``` -// Implement SafeDefault for all types EXCEPT SensitiveData -impl SafeDefaultImpl< - T, +Default, -core::metaprogramming::TypeEqual, -> of SafeDefault { - fn safe_default() -> T { - Default::default() - } +If implementing a trait for a generic struct, the implementation block must also declare generics and their constraints: + +```cairo +struct Wallet { + balance: T, } -#[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 WalletDrop> of Drop>; +``` + +Structs can have multiple generic types: + +```cairo +#[derive(Drop)] +struct Wallet { + balance: T, + address: U, } ``` -### Ensuring Matching Associated Types +#### Generic Enums -`TypeEqual` is useful for ensuring two types have equal associated types, especially in generic functions. +Enums can hold generic data types in their variants, such as `Option`: ```cairo -trait StateMachine { - type State; - fn transition(ref state: Self::State); +enum Option { + Some: T, + None, } +``` -#[derive(Copy, Drop)] -struct StateCounter { - counter: u8, +Or multiple generic types, like `Result`: + +```cairo +enum Result { + Ok: T, + Err: E, } +``` -impl TA of StateMachine { - type State = StateCounter; - fn transition(ref state: StateCounter) { - state.counter += 1; - } +### Generic Methods + +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. + +If a method involves combining two potentially different generic types, constraints must be applied to all involved types: + +```cairo +trait WalletMixTrait { + fn mixup, U2, +Drop>( + self: Wallet, other: Wallet, + ) -> Wallet; } -impl TB of StateMachine { - type State = StateCounter; - fn transition(ref state: StateCounter) { - state.counter *= 2; +impl WalletMixImpl, U1, +Drop> of WalletMixTrait { + fn mixup, U2, +Drop>( + self: Wallet, other: Wallet, + ) -> Wallet { + Wallet { balance: self.balance, address: other.address } } } +``` + +#### Associated Types and Type Equality + +Generics can constrain types based on associated types using traits like `TypeEqual`. This ensures that different generic implementations share the same associated type. + +```cairo +trait StateMachine { + type State; + fn transition(ref state: Self::State); +} + +// ... TA and TB implementations use StateCounter as State ... fn combine< impl A: StateMachine, @@ -4595,2390 +4473,1599 @@ fn combine< A::transition(ref self); B::transition(ref self); } - -#[executable] -fn main() { - let mut initial = StateCounter { counter: 0 }; - combine::(ref initial); -} ``` -## Data Packing with Associated Types +#### Generics in Closures -Associated types can simplify function signatures compared to explicitly defining generic type parameters for return types. - -Consider a `PackGeneric` trait that requires explicit generic parameters for the input and output types: +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`. ```cairo -fn foo>(self: T, other: T) -> U { - self.pack_generic(other) +#[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 + } } ``` -A `Pack` trait using an associated type `Result` allows for a more concise function signature: +--- -```cairo -trait Pack { - type Result; - fn pack(self: T, other: T) -> Self::Result; -} +Sources: -impl PackU32Impl of Pack { - type Result = u64; +- 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 - fn pack(self: u32, other: u32) -> Self::Result { - let shift: u64 = 0x100000000; // 2^32 - self.into() * shift + other.into() - } -} +--- -fn bar>(self: T, b: T) -> PackImpl::Result { - PackImpl::pack(self, b) -} -``` +## Traits: Defining and Implementing Behavior -Both approaches achieve the same result: +### Defining a Trait -```cairo -trait Pack { - type Result; - fn pack(self: T, other: T) -> Self::Result; -} +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. -impl PackU32Impl of Pack { - type Result = u64; +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 { ... }`. - fn pack(self: u32, other: u32) -> Self::Result { - let shift: u64 = 0x100000000; // 2^32 - self.into() * shift + other.into() - } -} +**Example of a non-generic trait definition:** -fn bar>(self: T, b: T) -> PackImpl::Result { - PackImpl::pack(self, b) +```cairo +#[derive(Drop, Clone)] +struct NewsArticle { + headline: ByteArray, + location: ByteArray, + author: ByteArray, + content: ByteArray, } -trait PackGeneric { - fn pack_generic(self: T, other: T) -> U; +pub trait Summary { + fn summarize(self: @NewsArticle) -> ByteArray; } -impl PackGenericU32 of PackGeneric { - fn pack_generic(self: u32, other: u32) -> u64 { - let shift: u64 = 0x100000000; // 2^32 - self.into() * shift + other.into() +impl NewsArticleSummary of Summary { + fn summarize(self: @NewsArticle) -> ByteArray { + format!("{:?} by {:?} ({:?})", self.headline, self.author, self.location) } } - -fn foo>(self: T, other: T) -> U { - self.pack_generic(other) -} - -#[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); -} ``` -Modules and Project Organization +### Implementing a Trait on a Type -Project Setup with Scarb +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. -# Project Setup with Scarb +**Example of implementing a generic trait:** -## Scarb.toml Configuration - -The `Scarb.toml` file configures your Scarb project. Key sections include: - -- **`[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`. - -### Example Scarb.toml for a Cairo Program +```cairo +mod aggregator { + pub trait Summary { + fn summarize(self: @T) -> ByteArray; + } -```toml -[package] -name = "hello_world" -version = "0.1.0" -edition = "2024_07" - -[cairo] -enable-gas = false - -[dependencies] -cairo_execute = "2.12.0" - -[[target.executable]] -name = "hello_world_main" -function = "hello_world::hello_world::main" -``` - -## Managing Dependencies - -Scarb manages external packages (crates) through Git repositories. - -### Adding Dependencies - -- **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 `. - -### Removing Dependencies - -- Remove the corresponding line from `Scarb.toml` or use the `scarb rm ` command. - -### Building with Dependencies - -Run `scarb build` to fetch and compile all declared dependencies. - -## Using the Glob Operator (`*`) - -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. - -```rust -use core::num::traits::*; -``` - -Core Concepts: Packages, Crates, and Modules - -# Core Concepts: Packages, Crates, and Modules - -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. - -## Key Components of the Module System - -- **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. - -## Packages and Crates - -### 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. - -### What is the Crate Root? - -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: - -- A `Scarb.toml` manifest file with a `[package]` section. -- Associated source code. - -A package can contain other packages, each with its own `Scarb.toml`. - -### Creating a Package with Scarb - -The `scarb new` command creates a new Cairo package: - -```bash -scarb new my_package -``` - -This generates a directory structure like: - -``` -my_package/ -├── Scarb.toml -└── src/ - └── lib.cairo -``` - -- `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. - -Example `Scarb.toml`: - -```toml -[package] -name = "my_package" -version = "0.1.0" -edition = "2024_07" - -[executable] - -[cairo] -enable-gas = false - -[dependencies] -cairo_execute = "2.12.0" -``` - -Additional `.cairo` files can be added to `src/` or its subdirectories to organize code into multiple files. - -Module Paths and Navigation - -# Module Paths and Navigation - -When organizing code, Cairo follows specific rules for module declaration and navigation. - -## Declaring Modules and Submodules - -- **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 + #[derive(Drop)] + pub struct NewsArticle { + pub headline: ByteArray, + pub location: ByteArray, + pub author: ByteArray, + pub content: ByteArray, } - ``` - - In the file `src//.cairo`. - -## Paths for Referring to Items - -To reference code within modules, Cairo uses paths, similar to filesystem navigation. Paths consist of identifiers separated by double colons (`::`). - -There are two forms of paths: - -- **Absolute Path**: Starts from the crate root, beginning with the crate name. -- **Relative Path**: Starts from the current module. -### Example: Absolute and Relative Paths - -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: - -Filename: src/lib.cairo - -```cairo,noplayground -mod front_of_house { - mod hosting { - fn add_to_waitlist() {} - fn seat_at_table() {} + impl NewsArticleSummary of Summary { + fn summarize(self: @NewsArticle) -> ByteArray { + format!("{} by {} ({})", self.headline, self.author, self.location) + } } - mod serving { - fn take_order() {} - fn serve_order() {} - fn take_payment() {} + #[derive(Drop)] + pub struct Tweet { + pub username: ByteArray, + pub content: ByteArray, + pub reply: bool, + pub retweet: bool, } -} - -pub fn eat_at_restaurant() { - // Absolute path - crate::front_of_house::hosting::add_to_waitlist(); - // Relative path - front_of_house::hosting::add_to_waitlist(); + impl TweetSummary of Summary { + fn summarize(self: @Tweet) -> ByteArray { + format!("{}: {}", self.username, self.content) + } + } } -``` - -- 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. +use aggregator::{NewsArticle, Summary, Tweet}; -### Example: Using `super` - -In the following example, `fix_incorrect_order` within the `back_of_house` module calls `deliver_order` from its parent module: - -Filename: src/lib.cairo - -```cairo,noplayground -fn deliver_order() {} +#[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", + }; -mod back_of_house { - fn fix_incorrect_order() { - cook_order(); - super::deliver_order(); - } + 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 - fn cook_order() {} + println!("New article available! {}", news.summarize()); + println!("New tweet! {}", tweet.summarize()); } ``` -Here, `super::deliver_order()` references the `deliver_order` function defined in the module containing `back_of_house`. - -Visibility and Privacy Rules +### Methods and `self` Parameter -# Visibility and Privacy Rules +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`). -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. +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`). -## Modules and Privacy +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. -- **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. +### Avoiding Trait Definition with `#[generate_trait]` -For example, consider a crate structure: +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. -```text -backyard/ -├── Scarb.toml -└── src - ├── garden - │ └── vegetables.cairo - ├── garden.cairo - └── lib.cairo -``` - -The crate root `src/lib.cairo` might contain: +**Example using `#[generate_trait]`:** ```cairo -pub mod garden; -use crate::garden::vegetables::Asparagus; +#[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 plant = Asparagus {}; - println!("I'm growing {:?}!", plant); + let rect1 = Rectangle { width: 30, height: 50 }; + println!("Area is {}", rect1.area()); } ``` -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 {} -``` +### Default Implementations -The `use crate::garden::vegetables::Asparagus;` line in `lib.cairo` brings the `Asparagus` type into scope. +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. -## Exposing Paths with the `pub` Keyword +**Example with default implementation:** -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`. +```cairo +mod aggregator { + pub trait Summary { + fn summarize(self: @T) -> ByteArray { + "(Read more...)" + } + fn summarize_author(self: @T) -> ByteArray; // Required implementation + } -Consider a scenario where `eat_at_restaurant` needs to call `add_to_waitlist` from a nested module: + #[derive(Drop)] + pub struct Tweet { + pub username: ByteArray, + pub content: ByteArray, + pub reply: bool, + pub retweet: bool, + } -```cairo,noplayground -mod front_of_house { - pub mod hosting { - pub fn add_to_waitlist() {} + impl TweetSummary of Summary { + fn summarize_author(self: @Tweet) -> ByteArray { + format!("@{}", self.username) + } } } -pub fn eat_at_restaurant() { - // Absolute path - crate::front_of_house::hosting::add_to_waitlist(); // Compiles +use aggregator::{Summary, Tweet}; - // Relative path - front_of_house::hosting::add_to_waitlist(); // Compiles +#[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, + }; + + println!("1 new tweet: {}", tweet.summarize()); } ``` -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 +In this case, `summarize` uses its default implementation because only `summarize_author` was implemented. -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. +### Derivable Traits -## Example: Private Struct Fields +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). -While a struct can be public, its fields remain private by default. +**Example deriving `Clone` and `Drop`:** ```cairo -pub mod rectangle { - #[derive(Copy, Drop)] - pub struct Rectangle { - width: u64, // Private field - height: u64 // Private field - } +#[derive(Clone, Drop)] +struct A { + item: felt252, } +#[executable] fn main() { - // This would not compile because width and height are private: - // let r = rectangle::Rectangle { width: 10, height: 20 }; - // println!("{}", r.width); + let first_struct = A { item: 2 }; + let second_struct = first_struct.clone(); + assert!(second_struct.item == 2, "Not equal"); } ``` -To make the fields accessible, they would also need to be marked with `pub`. +--- -Using the `use` Keyword for Imports and Re-exports +Sources: -# Using the `use` Keyword for Imports and Re-exports +- 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 -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 +## Associated Items and Operator Overloading -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. +### Operator Overloading -```cairo -// section "Defining Modules to Control Scope" +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. -mod front_of_house { - pub mod hosting { - pub fn add_to_waitlist() {} - } -} -use crate::front_of_house::hosting; +For example, combining two `Potion` structs (with `health` and `mana` fields) can be achieved by implementing the `Add` trait: -pub fn eat_at_restaurant() { - hosting::add_to_waitlist(); // ✅ Shorter path -} ``` - -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) +trait Extend { + fn extend[Item: A], +Destruct>(ref self: T, iterator: I); } -``` -### 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. - -```cairo -use core::array::ArrayTrait as Arr; - -#[executable] -fn main() { - let mut arr = Arr::new(); // ArrayTrait was renamed to Arr - arr.append(1); +impl ArrayExtend> of Extend, T> { + fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { + for item in iterator { + self.append(item); + } + } } ``` -### Importing Multiple Items from the Same Module - -To import several items from the same module cleanly, you can use curly braces `{}` to list them. - -```cairo -// Assuming we have a module called `shapes` with the structures `Square`, `Circle`, and `Triangle`. -mod shapes { - #[derive(Drop)] - pub struct Square { - pub side: u32, - } +``` +struct Potion { + health: felt252, + mana: felt252, +} - #[derive(Drop)] - pub struct Circle { - pub radius: u32, +impl PotionAdd of Add { + fn add(lhs: Potion, rhs: Potion) -> Potion { + Potion { health: lhs.health + rhs.health, mana: lhs.mana + rhs.mana } } +} - #[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 - -As modules grow, it's beneficial to move their definitions into separate files to maintain code clarity and organization. - -### Extracting a Module to a New File - -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. - -**Example:** Moving `front_of_house` from `src/lib.cairo`: - -Filename: src/lib.cairo - -```cairo,noplayground -mod front_of_house; -use crate::front_of_house::hosting; - -fn eat_at_restaurant() { - hosting::add_to_waitlist(); -} -``` - -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 - -```cairo,noplayground -pub mod hosting { - pub fn add_to_waitlist() {} -} -``` - -Listing 7-15: Definitions inside the `front_of_house` module in `src/front_of_house.cairo` - -### Extracting a Child Module - -To extract a child module (e.g., `hosting` within `front_of_house`): - -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. - -**Example:** Moving `hosting` into its own file: - -Filename: src/front_of_house.cairo - -```cairo,noplayground -pub mod hosting; -``` - -Filename: src/front_of_house/hosting.cairo - -```cairo,noplayground -pub fn add_to_waitlist() {} -``` - -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. - -Advanced Topics: Traits and Component Integration - -# Advanced Topics: Traits and Component Integration - -## Module Organization and Trait Implementation - -When organizing code into modules, if a trait's implementation resides in a different module than the trait itself, explicit imports are necessary. - -```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; -} - -mod rectangle { - // Importing ShapeGeometry is required to implement this trait for Rectangle - use super::ShapeGeometry; - - #[derive(Copy, Drop)] - pub struct Rectangle { - pub height: u64, - pub width: u64, - } - - // 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 - } - } -} - -mod circle { - // Importing ShapeGeometry is required to implement this trait for Circle - use super::ShapeGeometry; - - #[derive(Copy, Drop)] - pub struct Circle { - pub radius: u64, - } - - // 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; - -#[executable] -fn main() { - let rect = Rectangle { height: 5, width: 7 }; - println!("Rectangle area: {}", ShapeGeometry::area(rect)); //35 - println!("Rectangle boundary: {}", ShapeGeometry::boundary(rect)); //24 - - let circ = Circle { radius: 5 }; - println!("Circle area: {}", ShapeGeometry::area(circ)); //78 - println!("Circle boundary: {}", ShapeGeometry::boundary(circ)); //31 -} -``` - -## Contract Integration - -Contracts integrate components using **impl aliases** to instantiate a component's generic impl with the contract's concrete state. - -```cairo,noplayground - #[abi(embed_v0)] - impl OwnableImpl = OwnableComponent::OwnableImpl; - - impl OwnableInternalImpl = OwnableComponent::InternalImpl; -``` - -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. - -Macros, Attributes, and Compiler Internals - -Cairo Attributes - -# Cairo Attributes - -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. - -## Core Attributes - -- **`#[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. - -## Contract ABI Attributes - -- **`#[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. - - ```cairo - #[starknet::contract] - mod ContractExample { - #[storage] - struct Storage {} - - #[abi(per_item)] - #[generate_trait] - impl SomeImpl of SomeTrait { - #[constructor] - // this is a constructor function - fn constructor(ref self: ContractState) {} - - #[external(v0)] - // this is a public function - fn external_function(ref self: ContractState, arg1: felt252) {} - - #[l1_handler] - // this is a l1_handler function - fn handle_message(ref self: ContractState, from_address: felt252, arg: felt252) {} - - // this is an internal function - fn internal_function(self: @ContractState) {} - } - } - ``` - -- **`#[external(v0)]`**: Used to explicitly define a function as external when the `#[abi(per_item)]` attribute is applied to the implementation block. - -## Event Attributes - -- **`#[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. - -Cairo Macros - -# Cairo Macros - -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. - -## The Difference Between Macros and Functions - -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. - -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. - -## 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. - -### Defining and Using Declarative Macros - -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 common example is an array-building macro: - -```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() }; -# } -# -# // 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!` - -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::`). - -Macros are expected to expand to a single expression. If a macro defines multiple statements, they should be wrapped in a `{}` block. - -An end-to-end example demonstrating these concepts: - -```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() }; - } - - // 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); -# } -# -# -``` - -### Enabling Inline Macros - -To use user-defined inline macros, enable the experimental feature in your `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"] -``` - -## Procedural Macros - -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]`. - -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]`. - -### Expression Macros - -Expression macros transform Cairo expressions. An example is a compile-time power function (`pow!`) implemented using Rust crates like `cairo-lang-macro` and `bigdecimal`. - -### Derive Macros - -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. - -### Attribute Macros - -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. - -## Common Macros - -Several built-in macros are available for common tasks: - -| 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. | - -Comments are also supported using `//` for line comments. - -Compiler Internals and Optimizations - -# Inlining in Cairo - -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. - -## 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. - -> Inlining is often a tradeoff between the number of steps and code length. Use the `inline` attribute cautiously where it is appropriate. - -## 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. - -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. - -## Inlining Example - -Consider the following program demonstrating inlining: - -```cairo -#[executable] -fn main() -> felt252 { - inlined() + not_inlined() -} - -#[inline(always)] -fn inlined() -> felt252 { - 1 -} - -#[inline(never)] -fn not_inlined() -> felt252 { - 2 + 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 - -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. - -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. - -## Summary +### Associated Items -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. +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. -Testing and Debugging +#### Associated Types -Introduction to Cairo Testing and Debugging +Associated types are type aliases within traits, allowing trait implementers to choose the actual types. This keeps trait definitions flexible and clean. -# Introduction to Cairo Testing and Debugging +Consider the `Pack` trait, where `Result` is an associated type placeholder: -## Executing the Program - -To test a Cairo program, you can use the `scarb execute` command. This command runs the program and displays its output. - -**Example:** Executing a primality test program with the input `17`. - -```bash -scarb execute -p prime_prover --print-program-output --arguments 17 ``` +trait Pack { + type Result; -- `-p prime_prover`: Specifies the package name. -- `--print-program-output`: Displays the program's result. -- `--arguments 17`: Passes `17` as input to the program. - -The output indicates success (0 for no panic) and the program's result (1 for true, meaning 17 is prime). + fn pack(self: T, other: T) -> Self::Result; +} -```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 -``` +impl PackU32Impl of Pack { + type Result = u64; -Try with other numbers: + fn pack(self: u32, other: u32) -> Self::Result { + let shift: u64 = 0x100000000; // 2^32 + self.into() * shift + other.into() + } +} -```bash -$ scarb execute -p prime_prover --print-program-output --arguments 4 -[0, 0] # 4 is not prime +fn bar>(self: T, b: T) -> PackImpl::Result { + PackImpl::pack(self, b) +} -$ scarb execute -p prime_prover --print-program-output --arguments 23 -[0, 1] # 23 is prime -``` +trait PackGeneric { + fn pack_generic(self: T, other: T) -> U; +} -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 PackGenericU32 of PackGeneric { + fn pack_generic(self: u32, other: u32) -> u64 { + let shift: u64 = 0x100000000; // 2^32 + self.into() * shift + other.into() + } +} -## Generating a Zero-Knowledge Proof +fn foo>(self: T, other: T) -> U { + self.pack_generic(other) +} -Cairo 2.10 integrates the Stwo prover via Scarb, enabling direct generation of zero-knowledge proofs. +#[executable] +fn main() { + let a: u32 = 1; + let b: u32 = 1; -To generate a proof for a specific execution (e.g., `execution1`), use the `scarb prove` command: + let x = foo(a, b); + let y = bar(a, b); -```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 + // result is 2^32 + 1 + println!("x: {}", x); + println!("y: {}", y); +} ``` -This command generates a proof that the primality check was computed correctly without revealing the input. - -Understanding and Resolving Cairo Errors - -# Understanding and Resolving Cairo Errors - -This section details common error messages encountered in Cairo development and provides guidance on how to resolve them. - -## Common Cairo Errors - -### `Variable not dropped.` - -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.` +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`). -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. +#### Associated Constants -### `error: Trait has no implementation in context: core::fmt::Display::` +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`: -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. +``` +trait Shape { + const SIDES: u32; + fn describe() -> ByteArray; +} -### `Got an exception while executing a hint: Hint Error: Failed to deserialize param #x.` +struct Triangle {} -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. +impl TriangleShape of Shape { + const SIDES: u32 = 3; + fn describe() -> ByteArray { + "I am a triangle." + } +} -### `Item path::item is not visible in this context.` +struct Square {} -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. +impl SquareShape of Shape { + const SIDES: u32 = 4; + fn describe() -> ByteArray { + "I am a square." + } +} -### `Identifier not found.` +fn print_shape_info>() { + println!("I have {} sides. {}", ShapeImpl::SIDES, ShapeImpl::describe()); +} -This is a general error that can indicate: +#[executable] +fn main() { + print_shape_info::(); + print_shape_info::(); +} +``` -- 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. +This allows generic functions like `print_shape_info` to access type-specific constants. -## Starknet Components Related Error Messages +#### Constraint Traits on Associated Items -### `Trait not found. Not a trait.` +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 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: +An example is constraining an iterator's `Item` associated type within an `extend` method implementation to match the collection's element type: -```cairo,noplayground -#[abi(embed_v0)] -impl IMPL_NAME = PATH_TO_COMPONENT::EMBEDDED_NAME ``` +trait Extend { + fn extend[Item: A], +Destruct>(ref self: T, iterator: I); +} -Testing Fundamentals: Writing and Running Tests - -# Testing Fundamentals: Writing and Running Tests - -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. +impl ArrayExtend> of Extend, T> { + fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { + for item in iterator { + self.append(item); + } + } +} +``` -## The Purpose of Tests +--- -Tests are Cairo functions designed to verify that other code functions as intended. A typical test function involves three steps: +Sources: -1. **Set up**: Prepare any necessary data or state. -2. **Run**: Execute the code being tested. -3. **Assert**: Verify that the results match expectations. +- 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 -## Anatomy of a Test Function +--- -Cairo offers several features for writing tests: +## Metaprogramming: Macros and Code Transformation -- `#[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. +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. -### The `#[test]` Attribute +### Macros vs. Functions -A function annotated with `#[test]` is recognized by the test runner. The `scarb test` command executes these functions. +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. -```cairo,noplayground -#[cfg(test)] -mod tests { - use super::*; +### Declarative Inline Macros - #[test] - fn it_works() { - let result = add(2, 2); // Assuming 'add' is a function in the outer scope - assert_eq!(result, 4); - } -} -``` +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. -### The `#[cfg(test)]` Attribute +#### Defining Declarative Macros -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. +Declarative macros allow matching code structure to associated code blocks that replace the macro invocation. -```cairo,noplayground -#[cfg(test)] -mod tests { - use super::*; +The following example shows a simplified definition of an array-building macro: - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +```c +macro make_array { + ($($x:expr), *) => { + { + let mut arr = $defsite::ArrayTrait::new(); + $(arr.append($x);)* + arr + } + }; } ``` -### Testing Private Functions - -Cairo allows testing private functions. Items within child modules (like the `tests` module) can access items in their ancestor modules. +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 -// Filename: src/lib.cairo -pub fn add(a: u32, b: u32) -> u32 { - internal_adder(a, 2) +```c +{ + let mut arr = ArrayTrait::new(); + arr.append(1); + arr.append(2); + arr.append(3); + arr } +``` -fn internal_adder(a: u32, b: u32) -> u32 { - a + b -} +#### Macro Pattern Syntax -#[cfg(test)] -mod tests { - use super::*; // Brings internal_adder into scope +Macro patterns are matched against Cairo source code structure. Key components include: - #[test] - fn test_private_function() { - // The test can call the private function directly - assert_eq!(4, internal_adder(2, 2)); - } -} -``` +- 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. -## Running Tests +#### Hygiene: `$defsite`, `$callsite`, and `expose!` -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. +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::`. -Example output: +To use user-defined inline macros, you must enable the experimental feature in `Scarb.toml`: -```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 +```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"] ``` -## Examples +Macros are expected to expand to a single expression; wrapping multiple statements in `{}` achieves this. -### Testing Conversions +The following example demonstrates hygiene and name resolution: -This example tests a `parse_u8` function that converts `felt252` to `u8`. - -```cairo,noplayground -fn parse_u8(s: felt252) -> Result { - match s.try_into() { - Some(value) => Ok(value), - None => Err('Invalid integer'), - } +```c +macro make_array { + ($($x:expr), *) => { + { + let mut arr = $defsite::ArrayTrait::new(); + $(arr.append($x);)* + arr + } + }; } #[cfg(test)] -mod tests { - use super::*; +#[test] +fn test_make_array() { + let a = make_array![1, 2, 3]; + let expected = array![1, 2, 3]; + assert_eq!(a, expected); +} - #[test] - fn test_felt252_to_u8() { - let number: felt252 = 5; - // should not panic - let res = parse_u8(number).unwrap(); +mod hygiene_demo { + // A helper available at the macro definition site + fn def_bonus() -> u8 { + 10 } - #[test] - #[should_panic] - fn test_felt252_to_u8_panic() { - let number: felt252 = 256; - // should panic - let res = parse_u8(number).unwrap(); + // Adds the defsite bonus, regardless of what exists at the callsite + pub macro add_defsite_bonus { + ($x: expr) => { $x + $defsite::def_bonus() }; } -} -``` - -### Testing Struct Methods - -This tests a `can_hold` method on a `Rectangle` struct. - -```cairo,noplayground -#[derive(Drop)] -struct Rectangle { - width: u64, - height: u64, -} - -trait RectangleTrait { - fn can_hold(self: @Rectangle, other: @Rectangle) -> bool; -} -impl RectangleImpl of RectangleTrait { - fn can_hold(self: @Rectangle, other: @Rectangle) -> bool { - *self.width > *other.width && *self.height > *other.height + // Adds the callsite bonus, resolved where the macro is invoked + pub macro add_callsite_bonus { + ($x: expr) => { $x + $callsite::bonus() }; } -} -#[cfg(test)] -mod tests { - use super::*; + // 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;); + }; + } - #[test] - fn larger_can_hold_smaller() { - let larger = Rectangle { height: 7, width: 8 }; - let smaller = Rectangle { height: 1, width: 5 }; + // A helper macro that reads a callsite-exposed variable + pub macro read_exposed_total { + () => { $callsite::exposed_total }; + } - assert!(larger.can_hold(@smaller), "rectangle cannot hold"); + // 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)] -mod tests2 { - use super::*; +#[test] +fn test_hygiene_e2e() { - #[test] - fn smaller_cannot_hold_larger() { - let larger = Rectangle { height: 7, width: 8 }; - let smaller = Rectangle { height: 1, width: 5 }; + // 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 - assert!(!smaller.can_hold(@larger), "rectangle cannot hold"); - } + // 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); } ``` -### Testing Starknet Contracts +This demonstrates: -Tests for Starknet contracts can be written using `snforge_std` and can either deploy the contract or interact with its internal state for testing. +- `$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`. -```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}; +### Procedural Macros -fn owner() -> ContractAddress { - contract_address_const::<'owner'>() -} +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`. -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) -} +#### Procedural Macro Types and Signatures -#[test] -fn test_constructor() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); +Macros are defined using one of three attributes: - 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()); -} +- `#[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 {} + ``` -#[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); -} +#### Project Setup and Dependencies -#[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); -} +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: -#[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(); -} +`Cargo.toml` example: -#[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(); +```toml +[package] +name = "pow" +version = "0.1.0" +edition = "2021" +publish = false - // When - pizza_factory.make_pizza(); +[lib] +crate-type = ["cdylib"] - // 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)]); -} +[dependencies] +bigdecimal = "0.4.5" +cairo-lang-macro = "0.1.1" +cairo-lang-parser = "2.12.0" +cairo-lang-syntax = "2.12.0" -#[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); -} +[workspace] ``` -Assertions and Test Verification +`Scarb.toml` example for the macro package: + +```toml +[package] +name = "pow" +version = "0.1.0" -# Assertions and Test Verification +[cairo-plugin] +``` -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. +To use the macro, the consuming project adds the macro package to its `[dependencies]` in `Scarb.toml`, e.g., `pow = { path = "../path/to/pow" }`. -## `assert_eq!` and `assert_ne!` Macros +#### Expression 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. +If the goal is expression transformation, declarative inline macros are simpler. Procedural expression macros are used when Rust-powered parsing is needed. -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)]`. +#### Derive Macros -**Example using `assert_eq!` and `assert_ne!`:** +Derive macros generate custom trait implementations automatically. The macro receives the type's structure, generates the implementation logic, and outputs the code. -```cairo, noplayground -pub fn add_two(a: u32) -> u32 { - a + 2 -} +To implement the `Hello` trait using `HelloMacro`: -#[cfg(test)] -mod tests { - use super::*; +```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); - #[test] - fn it_adds_two() { - assert_eq!(4, add_two(2)); + 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; + } } - #[test] - fn wrong_check() { - assert_ne!(0, add_two(2)); - } + ProcMacroResult::new(TokenStream::new(indoc::formatdoc! {r#" + impl SomeHelloImpl of Hello<{0}> { + fn hello(self: @{0}) { + println!("Hello {0}!"); + } + } + "#, struct_name})) } ``` -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 +The trait `Hello` must be defined or imported in the consuming code. -These macros are used for comparison tests: +#### Attribute Macros -- `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. +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)]`). -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. +An example creating a macro to rename a struct: -**Example using comparison macros:** +```c +use cairo_lang_macro::attribute_macro; +use cairo_lang_macro::{ProcMacroResult, TokenStream}; -```cairo, noplayground -#[derive(Drop, Copy, Debug, PartialEq)] -struct Dice { - number: u8, +#[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"), + )) } +``` -impl DicePartialOrd of PartialOrd { - fn lt(lhs: Dice, rhs: Dice) -> bool { - lhs.number < rhs.number - } +Usage example in a Cairo file: - fn le(lhs: Dice, rhs: Dice) -> bool { - lhs.number <= rhs.number - } +```c +#[executable] +fn main() { + let a = SomeType {}; + a.hello(); - fn gt(lhs: Dice, rhs: Dice) -> bool { - lhs.number > rhs.number - } + let res = pow!(10, 2); + println!("res : {}", res); - fn ge(lhs: Dice, rhs: Dice) -> bool { - lhs.number >= rhs.number - } + let _a = RenamedType {}; } -#[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, - ); +#[derive(HelloMacro, Drop, Destruct)] +struct SomeType {} + +#[rename] +struct OldType {} + +trait Hello { + fn hello(self: @T); } ``` -## Adding Custom Failure Messages +### Advanced Constraints: TypeEqual Trait -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. +The `core::metaprogramming::TypeEqual` trait allows creating constraints based on type equality, useful in advanced scenarios like excluding specific types from a trait implementation. -**Example with a custom message:** +To implement a trait for all types that implement `Default`, except for `SensitiveData`: -```cairo, noplayground - #[test] - fn it_adds_two() { - assert_eq!(4, add_two(2), "Expected {}, got add_two(2)={}", 4, add_two(2)); +```c +trait SafeDefault { + fn safe_default() -> T; +} + +#[derive(Drop, Default)] +struct SensitiveData { + secret: felt252, +} + +// Implement SafeDefault for all types EXCEPT SensitiveData +impl SafeDefaultImpl< + T, +Default, -core::metaprogramming::TypeEqual, +> of SafeDefault { + fn safe_default() -> T { + Default::default() } +} + +#[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(); +} ``` -This results in a more informative error message upon failure, such as "Expected 4, got add_two(2)=5". +--- + +Sources: + +- 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 -Advanced Testing Techniques: Panics and Ignoring Tests +--- -# Advanced Testing Techniques: Panics and Ignoring Tests +# Advanced Features: Deref Coercion, Closures, and Circuits -## Handling Panics in Tests +This section covers advanced language features including implicit type coercion via traits, the mechanics of closures, and the use of arithmetic circuits. -Cairo provides mechanisms to handle and test for panics, which are runtime errors that halt program execution. +### Deref Coercion -### `panic!` Macro +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`. -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`. +The traits are defined as: ```cairo -#[executable] -fn main() { - if true { - panic!("2"); - } - println!("This line isn't reached"); +pub trait Deref { + type Target; + fn deref(self: T) -> Self::Target; } -``` -### `panic_with_felt252` Function +pub trait DerefMut { + type Target; + fn deref_mut(ref self: T) -> Self::Target; +} +``` -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. +Implementing `Deref` for a wrapper type allows direct access to the wrapped type's members: ```cairo -use core::panic_with_felt252; +#[derive(Drop, Copy)] +struct UserProfile { + username: felt252, + email: felt252, + age: u16, +} + +#[derive(Drop, Copy)] +struct Wrapper { + value: T, +} + +impl DerefWrapper of Deref> { + type Target = T; + fn deref(self: Wrapper) -> T { + self.value + } +} #[executable] fn main() { - panic_with_felt252(2); + 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); } ``` -## 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. - -Consider a `Guess` struct where the `new` method panics if the input value is out of range: +`DerefMut` only applies to mutable variables. Deref coercion also permits calling methods defined on the target type directly on the source instance: ```cairo -#[derive(Drop)] -struct Guess { - value: u64, +struct MySource { + pub data: u8, } -pub trait GuessTrait { - fn new(value: u64) -> Guess; +struct MyTarget { + pub data: u8, } -impl GuessImpl of GuessTrait { - fn new(value: u64) -> Guess { - if value < 1 || value > 100 { - panic!("Guess must be >= 1 and <= 100"); - } - - Guess { value } +#[generate_trait] +impl TargetImpl of TargetTrait { + fn foo(self: MyTarget) -> u8 { + self.data } } -``` - -A test to verify this panic behavior would look like: - -```cairo -#[cfg(test)] -mod tests { - use super::*; - #[test] - #[should_panic] - fn greater_than_100() { - GuessTrait::new(200); +impl SourceDeref of Deref { + type Target = MyTarget; + fn deref(self: MySource) -> MyTarget { + MyTarget { data: self.data } } } + +#[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); +} ``` -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". +### Closures -### Precise `should_panic` Tests +The way a closure handles captured values determines which `Fn` traits it implements: -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. +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). -```cairo -#[cfg(test)] -mod tests { - use super::*; +The `unwrap_or_else` method on `OptionTrait` uses `FnOnce` to constrain the provided closure `f`, as it is called at most once: - #[test] - #[should_panic(expected: "Guess must be <= 100")] - fn greater_than_100() { - GuessTrait::new(200); +```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(), + } } } ``` -If the panic message does not match the `expected` string, the test will fail, indicating the mismatch. - -## Ignoring Tests - -Tests that are time-consuming or not relevant for regular runs can be ignored using the `#[ignore]` attribute. +Closures are extensively used for functional programming patterns like `map` and `filter` on arrays: ```cairo -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - // ... +#[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 } +} - #[test] - #[ignore] - fn expensive_test() { // code that takes an hour to run +#[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 } } -``` -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. +#[executable] +fn main() { + let double = |value| value * 2; + println!("Double of 2 is {}", double(2_u8)); + println!("Double of 4 is {}", double(4_u8)); + + // This won't work because `value` type has been inferred as `u8`. + //println!("Double of 6 is {}", double(6_u16)); + + let sum = |x: u32, y: u32, z: u16| { + x + y + z.into() + }; + println!("Result: {}", sum(1, 2, 3)); + + let x = 8; + let my_closure = |value| { + x * (value + 3) + }; + + println!("my_closure(1) = {}", my_closure(1)); -## Running Single Tests + 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 + }); -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. + println!("double: {:?}", double); + println!("another: {:?}", another); -```shell -$ scarb test add_two_and_two + let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); + println!("even: {:?}", even); +} ``` -This command will execute only the `add_two_and_two` test. The output will indicate tests that were filtered out. +### Arithmetic Circuits -Test Organization: Unit vs. Integration Tests +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. -# Test Organization: Unit vs. Integration Tests +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$: -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. +```cairo +use core::circuit::{ + AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, + CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, +}; -## Unit 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::> {}; -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 add = circuit_add(a, b); + let mul = circuit_mul(a, add); -### Location and Structure + let output = (mul,); -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 mut inputs = output.new_inputs(); + inputs = inputs.next([10, 0, 0, 0]); + inputs = inputs.next([20, 0, 0, 0]); -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 instance = inputs.done(); -**Example Unit Test:** + let bn254_modulus = TryInto::< + _, CircuitModulus, + >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) + .unwrap(); -```cairo -pub fn add(left: usize, right: usize) -> usize { - left + right -} + let res = instance.eval(bn254_modulus).unwrap(); -#[cfg(test)] -mod tests { - use super::*; + let add_output = res.get_output(add); + let circuit_output = res.get_output(mul); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } + 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(); } ``` -## 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 -``` +Sources: -**Example Integration Test:** +- https://www.starknet.io/cairo-book/ch103-02-01-under-the-hood.html -To test a function `add_two` from the `adder` crate: +--- -```cairo -// tests/integration_tests.cairo -use adder::add_two; +## Compiler Internals and Component Structure -#[test] -fn it_adds_two() { - assert_eq!(4, add_two(2)); -} -``` +### Components: Under the Hood + +Components provide powerful modularity to Starknet contracts. This section dives deep into the compiler internals to explain the mechanisms that enable component composability. + +### A Primer on Embeddable Impls -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. +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. -### Running and Filtering Tests +Example demonstrating embeddable impls: + +```rust +#[starknet::interface] +trait SimpleTrait { + fn ret_4(self: @TContractState) -> u8; +} -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. +#[starknet::embeddable] +impl SimpleImpl of SimpleTrait { + fn ret_4(self: @TContractState) -> u8 { + 4 + } +} -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. +#[starknet::contract] +mod simple_contract { + #[storage] + struct Storage {} -### Organizing Integration Tests with Submodules + #[abi(embed_v0)] + impl MySimpleImpl = super::SimpleImpl; +} +``` -As integration tests grow, you can organize them into multiple files within the `tests` directory. Each file is compiled as a separate crate. +By embedding `SimpleImpl`, we externally expose `ret4` in the contract's ABI. -**Example with a common helper:** +### Inside Components: Generic Impls -If you create `tests/common.cairo` with a `setup` function: +Components build upon this embedding mechanism using generic impls, as seen in the component impl block syntax: -```cairo -// tests/common.cairo -pub fn setup() { - println!("Setting up tests..."); -} +```rust + #[embeddable_as(OwnableImpl)] + impl Ownable< + TContractState, +HasComponent, + > of super::IOwnable> { ``` -And `tests/integration_tests.cairo` calls it: +The key points are: -```cairo -// tests/integration_tests.cairo -use tests::common::setup; -use adder::it_adds_two; +- 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`. -#[test] -fn internal() { - setup(); // Call helper function - assert!(it_adds_two(2, 2) == 4, "internal_adder failed"); -} -``` +--- -Running `scarb test` would produce a section for `common.cairo` even if it contains no tests. +Sources: -To treat the entire `tests` directory as a single crate, you can add a `tests/lib.cairo` file: +- https://www.starknet.io/cairo-book/ch12-06-inlining-in-cairo.html -```cairo -// tests/lib.cairo -mod common; -mod integration_tests; -``` +--- -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. +# Function Inlining Mechanics and Performance Tuning -## Summary +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. -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. +### The `inline` Attribute Hints -Testing Cairo Components +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. -# Testing Cairo Components +The variants are: -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. +- `#[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. -## Testing the Component by Deploying a Mock Contract +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. -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. +### Compiler Heuristics for Default Inlining -### Counter Component Example +For functions without explicit inline directives, the compiler uses a heuristic approach. The decision relies mostly on the threshold `DEFAULT_INLINE_SMALL_FUNCTIONS_THRESHOLD`. -Consider a simple `CounterComponent` that allows incrementing a counter: +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. -```cairo, noplayground -#[starknet::component] -pub mod CounterComponent { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; +Special cases are handled: - #[storage] - pub struct Storage { - value: u32, - } +- 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. - #[embeddable_as(CounterImpl)] - pub impl Counter> of super::ICounter> { - fn get_counter(self: @ComponentState) -> u32 { - self.value.read() - } +### Performance Tradeoffs and Code Size - fn increment(ref self: ComponentState) { - self.value.write(self.value.read() + 1); - } - } -} -``` +Inlining presents a trade-off between the number of execution steps and code length. -### Mock Contract Definition +- **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. -A mock contract for testing the `CounterComponent` can be defined as follows: +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. -```cairo, noplayground -#[starknet::contract] -mod MockContract { - use super::counter::CounterComponent; +### Inlining Mechanics Illustrated (Sierra and Casm) - component!(path: CounterComponent, storage: counter, event: CounterEvent); +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. - #[storage] - struct Storage { - #[substorage(v0)] - counter: CounterComponent::Storage, - } +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. - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - CounterEvent: CounterComponent::Event, - } +**Example Code (Listing 12-5):** - #[abi(embed_v0)] - impl CounterImpl = CounterComponent::CounterImpl; +```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}; +**Example Code (Listing 12-6):** -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 } +```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 +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`. -## Testing Smart Contracts with Frameworks +#### Modulus Definition -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. +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: -### Configuring Scarb Project with Starknet Foundry +```rust + let bn254_modulus = TryInto::<\n _, CircuitModulus,\n >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0])\n .unwrap(); +``` -Modify your `Scarb.toml` file to include Starknet Foundry: +#### Circuit Evaluation -```toml,noplayground -[dev-dependencies] -snforge_std = "0.48.0" # Use the latest version +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: -[scripts] -test = "snforge test" +```rust + let res = instance.eval(bn254_modulus).unwrap(); +``` -[tool.scarb] -allow-pre-built-plugins = ["snforge_std"] +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`: + +```rust + let add_output = res.get_output(add); + let circuit_output = res.get_output(mul); ``` -This configuration ensures that running `scarb test` will execute `snforge test`. After configuring `Scarb.toml`, install Starknet Foundry following the official documentation. +#### Context on Arithmetic Circuits in Cairo -### Testing Flow with Starknet Foundry +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. -The typical workflow for testing a contract with Starknet Foundry involves these steps: +### Circuit Verification and Proof System Comparison -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. +--- -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. +Sources: -Profiling and Performance Analysis +- https://www.starknet.io/cairo-book/ch12-10-procedural-macros.html -### Profiling and Performance Analysis +--- -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. +### Macro Implementation Details -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. +The core code for macro implementation is written in Rust and relies on three primary Rust crates: -Consider the following `sum_n` function and its test: +- `cairo_lang_macro`: Specific to macro implementation. +- `cairo_lang_parser`: Used for compiler parser functions. +- `cairo_lang_syntax`: Related to the compiler syntax. -```cairo -fn sum_n(n: usize) -> usize { - let mut i = 0; - let mut sum = 0; - while i <= n { - sum += i; - i += 1; - } - sum -} +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. -#[cfg(test)] -mod tests { - use super::*; +#### Example: `pow` Macro Implementation - #[test] - #[available_gas(2000000)] - fn test_sum_n() { - let result = sum_n(10); - assert!(result == 55, "result is not 55"); - } +The `pow` function example demonstrates processing input to extract the base and exponent arguments to calculate $base^{exponent}$. + +```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()); + } + }; + + // 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()); + } + }; + + // base^exp + let result: BigDecimal = pow(base, exp); + + ProcMacroResult::new(TokenStream::new(result.to_string())) } ``` -After generating the trace and profile output, the `go tool pprof` web server provides valuable information: +--- -- **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. +Sources: -
- pprof number of steps -
+- 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 -The Cairo Profiler also offers insights into memory holes and builtins usage, with ongoing development for additional features. +--- -[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/ +--- -Quizzes and Summaries +Sources: -# Quizzes and Summaries +- 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 -### How to Write Tests +--- -1. **What is the annotation you add to a function to indicate that it's a test?** - `#[test]` +### Introduction to Cairo and Starknet Architecture -2. **Let's say you have a function with the type signature:** +#### Smart Contract Fundamentals - ```cairo - fn f(x: usize) -> Result; - ``` +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. - **And you want to test that `f(0)` should return `Err(_)`. Which of the following is _NOT_ a valid way to test that?** +#### Cairo and Starknet Architecture - ```cairo - #[test] - #[should_err] - fn test() -> Result { - f(0) - } - ``` +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**. - _Note: `should_err` does not exist in Cairo — tests that return `Result` will pass even if the result is an `Err`._ +#### Cairo Programs vs. Starknet Smart Contracts -3. **Does the test pass?** +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. - ```cairo - fn division_operation(number1: u16, number2: u16) -> u16 { - if number2 == 0 { - panic!("ZeroDivisionError not allowed!"); - } - let result = number1 / number2; - result - } +#### Contract Address and State - #[cfg(test)] - mod tests { - use super::{division_operation}; +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: - #[test] - #[should_panic(expected: ("Zerodivisionerror not allowed!",))] - fn test_division_operation() { - division_operation(10, 0); - } - } - ``` +- `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. - **No**. The expected string `"Zerodivisionerror not allowed!"` should be exactly the same as the panic string `"ZeroDivisionError not allowed!"`. +The computation follows: -4. **What is the output when these tests are run with the command `scarb cairo-test -f test_`?** +``` +contract_address = pedersen( + “STARKNET_CONTRACT_ADDRESS”, + deployer_address, + salt, + class_hash, + constructor_calldata_hash) +``` - ```cairo - #[cfg(test)] - mod tests { - #[test] - #[ignore] - fn test_addition() { - assert_ne!((5 + 4), 5); - } +#### Starknet Types: ContractAddress - #[test] - fn division_function() { - assert_eq!((10_u8 / 5), 2); - } +The `ContractAddress` type represents the unique address of a deployed contract on Starknet, essential for cross-contract calls and access control checks. + +```rust +use starknet::{ContractAddress, get_caller_address}; + +#[starknet::interface] +pub trait IAddressExample { + fn get_owner(self: @TContractState) -> ContractAddress; + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); +} + +#[starknet::contract] +mod AddressExample { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use super::{ContractAddress, get_caller_address}; + + #[storage] + struct Storage { + owner: ContractAddress, + } - #[test] - fn test_multiplication() { - assert_ne!((3 * 2), 8); - assert_eq!((5 * 5), 25); + #[constructor] + fn constructor(ref self: ContractState, initial_owner: ContractAddress) { + self.owner.write(initial_owner); + } + + #[abi(embed_v0)] + impl AddressExampleImpl of super::IAddressExample { + fn get_owner(self: @ContractState) -> ContractAddress { + self.owner.read() } - #[test] - fn test_subtraction() { - assert!((12 - 11) == 1, "The first argument was false"); + 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); } } - ``` - - `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 Smart Contracts +--- -Introduction to Starknet and Smart Contracts +Sources: -# Introduction to Starknet and Smart Contracts +- 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 -## What are Smart Contracts? +--- -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. +### Contract Class Definition and Lifecycle -### Programming Languages and Compilation +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. -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). +#### Contract Classes and Instances -### Characteristics of Smart Contracts +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`). -- **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. +#### Components of a Cairo Class Definition -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. +A contract class is defined by several components whose hashes contribute to the final class hash: -## Use Cases for Smart Contracts +| 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. | -Smart contracts enable a wide range of applications: +The **class hash** is computed using the Poseidon hash function ($h$): -### Decentralized Finance (DeFi) +$$ +\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}) +$$ -Enables financial applications like lending/borrowing, decentralized exchanges (DEXs), stablecoins, and more, without traditional intermediaries. +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`. -### Tokenization +#### Contract Interfaces -Facilitates the creation and trading of digital tokens representing real-world assets (e.g., real estate, art), enabling fractional ownership and increased liquidity. +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. -### Voting +The `self` parameter in function signatures determines state access: -Creates secure, transparent, and immutable voting systems where votes are tallied automatically on the blockchain. +- `ref self: TContractState`: Allows modification of the contract's storage variables. +- `self: @TContractState`: Takes a snapshot, preventing state modification (the compiler enforces this). -### Royalties +The implementation must conform to the interface, or a compilation error results. -Automates the distribution of royalties to content creators based on consumption or sales. +#### Constructors and Public Functions -### Decentralized Identities (DIDs) +1. **Constructors**: Run once upon deployment to initialize state. -Allows individuals to manage their digital identities securely and control the sharing of personal information. + - 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 +6094,131 @@ 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. + +##### 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. -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). +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]`. -```cairo,noplayground +```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 inform the outside world about contract changes. They are defined in an enum annotated with `#[event]` and emitted using `self.emit()`. +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 +#[starknet::contract] +mod EventExample { + #[storage] + struct Storage {} -```cairo,noplayground #[event] #[derive(Drop, starknet::Event)] pub enum Event { @@ -7090,1169 +6227,1346 @@ 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 -Starknet provides specialized types for blockchain interactions: +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. -- `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. +#### Flattening Nested Events with `#[flat]` -## Contract Classes and Instances +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`. -- **Contract Class**: The definition of a contract's code. -- **Contract Instance**: A deployed contract with its own storage. +```rust +// Example of #[flat] usage +#[derive(Drop, starknet::Event)] +pub enum FieldUpdated { + Title: UpdatedTitleData, + Author: UpdatedAuthorData, +} +``` -## Contract Address Computation +#### Emitting Events -A contract address is computed using a hash of prefix, deployer address, salt, class hash, and constructor calldata hash. +Events are emitted by calling `self.emit()` with an event data structure. -## Class Hash Computation +```rust +self.emit(BookAdded { id, title, author }); +``` -A class hash is the chain hash of its components: version, entry points, ABI hash, and Sierra program hash. +#### Contract Attributes Related to Events -Starknet System Calls +| 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 | -# Starknet System Calls +### Contract ABI and Entrypoints -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: +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). -- `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` +#### Entrypoints -## `get_block_hash` +Entrypoints are functions callable from outside the contract class: -### Syntax +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. -```cairo,noplayground -pub extern fn get_block_hash_syscall( - block_number: u64, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; -``` +#### Function Selector -### Description +Entrypoints are identified by a _selector_, computed as `sn_keccak(function_name)`. -Retrieves the hash of a specific Starknet block within the range of `[first_v0_12_0_block, current_block - 10]`. +#### Encoding -### Return Values +All data must be serialized into `felt252` before execution at the CASM level, as specified by the ABI. -Returns the hash of the specified block. +#### ABI Definition Attributes -### Error Messages +| 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)]`. | -- `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. +When using `#[abi(per_item)]`, public functions must be annotated with `#[external(v0)]` to be exposed; otherwise, they are considered private. -## `get_execution_info` +```rust +#[starknet::contract] +mod ContractExample { + #[storage] + struct Storage {} -### Syntax + #[abi(per_item)] + #[generate_trait] + impl SomeImpl of SomeTrait { + #[constructor] + // this is a constructor function + fn constructor(ref self: ContractState) {} -```cairo,noplayground -pub extern fn get_execution_info_syscall() -> SyscallResult< - Box, -> implicits(GasBuiltin, System) nopanic; -``` + #[external(v0)] + // this is a public function + fn external_function(ref self: ContractState, arg1: felt252) {} -### Description + #[l1_handler] + // this is a l1_handler function + fn handle_message(ref self: ContractState, from_address: felt252, arg: felt252) {} -Fetches information about the original transaction. In Cairo 1.0, all block, transaction, and execution context getters are consolidated into this single system call. + // this is an internal function + fn internal_function(self: @ContractState) {} + } +} +``` -### Arguments +### System Interaction and Context Access -None. +#### Class Hashes -### Return Values +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. -Returns a struct containing the execution info. +#### Working with Block and Transaction Information -## `call_contract` +Starknet provides functions to access the current execution context: -### Syntax +```rust +#[starknet::interface] +pub trait IBlockInfo { + fn get_block_info(self: @TContractState) -> (u64, u64); + fn get_tx_info(self: @TContractState) -> (ContractAddress, felt252); +} -```cairo,noplayground -pub extern fn call_contract_syscall( - address: ContractAddress, entry_point_selector: felt252, calldata: Span, -) -> SyscallResult> implicits(GasBuiltin, System) nopanic; -``` +#[starknet::contract] +mod BlockInfoExample { + use starknet::{get_block_info, get_tx_info}; + use super::ContractAddress; -### Description + #[storage] + struct Storage {} -Calls a specified contract with the given address, entry point selector, and call arguments. + #[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) + } -_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.* + fn get_tx_info(self: @ContractState) -> (ContractAddress, felt252) { + let tx_info = get_tx_info(); -### Arguments + // Access transaction details + let sender = tx_info.account_contract_address; + let tx_hash = tx_info.transaction_hash; -- `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. + (sender, tx_hash) + } + } +} +``` -### Return Values +`get_block_info()` returns `BlockInfo` (block number, timestamp), and `get_tx_info()` returns `TxInfo` (sender address, transaction hash, fee details). -The call response, of type `SyscallResult>`. +--- -## `deploy` +Sources: -### Syntax +- 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 -```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; -``` +--- -### Description +--- -Deploys a new instance of a previously declared class. +Sources: -### Arguments +- 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 -- `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. +--- -### Return Values +## Defining and Interacting with Contract Storage -A tuple containing the deployed contract's address and the constructor's response array. +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. -## `emit_event` +### Declaring Storage Variables -### Syntax +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. -```cairo,noplayground -pub extern fn emit_event_syscall( - keys: Span, data: Span, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; +```rust +#[storage] +struct Storage { + owner: Person, + expiration: Expiration, +} ``` -### Description +### Accessing and Modifying State -Emits an event with specified keys and data. Keys are analogous to Ethereum event topics, and data contains the event payload. +Interaction with contract storage is primarily done through high-level storage variables using two methods: -### 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); + ``` -- `keys`: The event's keys. -- `data`: The event's data. +When accessing fields within a storage struct (e.g., `self.owner.name.read()`), the compiler transparently translates these accesses into underlying `StoragePointer` manipulations. -### Return Values +### Interacting via Contract Interface Implementation -None. +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. -### Example +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. -```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(); -``` +- 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. -## `library_call` +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 library_call_syscall( - class_hash: ClassHash, function_selector: felt252, calldata: Span, -) -> SyscallResult> 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 -Executes the logic of another class within the context of the caller. Requires the class hash, function selector, and serialized calldata. +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`. -## `get_class_hash_at` +--- -### Syntax +Sources: -```cairo,noplayground -pub extern fn get_class_hash_at_syscall( - contract_address: ContractAddress, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; -``` +- 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 -### Description +--- -Retrieves the class hash of the contract at the given address. +## Storing Custom Types with the `Store` Trait -### Arguments +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. -- `contract_address`: The address of the deployed contract. +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`. -### Return Values +### Structs Storage Layout -The class hash of the contract's originating code. +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__`). -## `replace_class` +For example, for a `Person` struct with `name` and `address`: -### Syntax +| Fields | Address | +| ------- | ---------------------------- | +| name | `owner.__base_address__` | +| address | `owner.__base_address__ + 1` | -```cairo,noplayground -pub extern fn replace_class_syscall( - class_hash: ClassHash, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` +### Enums Storage Layout -### Description +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. -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. +For an enum `Expiration` with `Finite: u64` (index 0) and `Infinite` (index 1): -### Arguments +If `Finite` is stored: -- `class_hash`: The hash of the class to use as a replacement. +| Element | Address | +| ---------------------------- | --------------------------------- | +| Variant index (0 for Finite) | `expiration.__base_address__` | +| Associated limit date | `expiration.__base_address__ + 1` | -### Return Values +If `Infinite` is stored: -None. +| Element | Address | +| ------------------------------ | ----------------------------- | +| Variant index (1 for Infinite) | `expiration.__base_address__` | -## `storage_read` +Enums used in contract storage **must** define a default variant (using `#[default]`), which is returned when reading an uninitialized storage slot, preventing runtime errors. -### Syntax +Both `Drop` and `Serde` derivations are required for properly serializing arguments passed to entrypoints and deserializing their outputs. -```cairo,noplayground -pub extern fn storage_read_syscall( - address_domain: u32, address: StorageAddress, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; -``` +### Accessing Members of Stored Custom Types -### Description +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()`. -Reads a value from the contract's storage at the specified address domain and storage address. +### Starknet Storage with `starknet::Store` -### Return Values +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. -The value read from storage as a `felt252`. +### Storage Nodes -Contract Storage Management +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. -# Contract Storage Management +When accessing a storage node, you cannot `read` or `write` it directly; you must access its individual members. -Starknet contracts manage their state through contract storage, which can be accessed in two primary ways: +Example of defining and using a storage node: -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. +``` +#[starknet::contract] +mod VotingSystem { + use starknet::storage::{ + Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, + }; + use starknet::{ContractAddress, get_caller_address}; -## Declaring and Using Storage Variables + #[storage] + struct Storage { + proposals: Map, + proposal_count: u32, + } -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. + #[starknet::storage_node] + struct ProposalNode { + title: felt252, + description: felt252, + yes_votes: u32, + no_votes: u32, + voters: Map, + } -```cairo -#[storage] -struct Storage { - owner: Person, - expiration: Expiration, + #[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; + + 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); + + self.proposal_count.write(new_proposal_id); + + new_proposal_id + } + + #[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); + } } ``` -### 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. +Sources: -To read a variable: +- https://www.starknet.io/cairo-book/ch101-01-00-contract-storage.html -```cairo -let owner_data = self.owner.read(); -``` +--- -To write a variable: +### Storage Layout and Address Computation -```cairo -self.owner.write(new_owner_data); -``` +The calculation mechanism for storage addresses is modeled using `StoragePointers` and `StoragePaths`. -When working with struct members, you can read/write them directly: +#### Modeling of the Contract Storage in the Core Library -```cairo -// Reading a specific member of a struct -fn get_owner_name(self: @ContractState) -> felt252 { - self.owner.name.read() -} -``` +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`. -## Storing Custom Types with the `Store` Trait +Each storage variable can be converted into a `StoragePointer`. This pointer comprises two primary fields: -To store custom types (structs, enums) in storage, they must implement the `Store` trait. This can be achieved by deriving it: +- 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. -```cairo -#[derive(Drop, Serde, starknet::Store)] -pub struct Person { - address: ContractAddress, - name: felt252, -} -``` +#### Example of Storage Access -Enums used in storage must also implement `Store` and define a default variant using `#[default]`: +The following example demonstrates a contract structure and how storage addresses can be accessed programmatically: -```cairo -#[derive(Copy, Drop, Serde, starknet::Store)] -pub enum Expiration { - Finite: u64, - #[default] - Infinite, +```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); } -``` -Both `Drop` and `Serde` are required for proper serialization/deserialization. +#[starknet::contract] +mod SimpleStorage { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + + #[storage] + struct Storage { + owner: Person, + expiration: Expiration, + } -### Structs Storage Layout + #[derive(Drop, Serde, starknet::Store)] + pub struct Person { + address: ContractAddress, + name: felt252, + } -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. + #[derive(Copy, Drop, Serde, starknet::Store)] + pub enum Expiration { + Finite: u64, + #[default] + Infinite, + } -| Fields | Address | -| --------- | --------------------------- | -| `owner` | `owner.__base_address__` | -| `address` | `owner.__base_address__ +1` | + #[constructor] + fn constructor(ref self: ContractState, owner: Person) { + self.owner.write(owner); + } -### Enums Storage Layout + #[abi(embed_v0)] + impl SimpleCounterImpl of super::ISimpleStorage { + fn get_owner(self: @ContractState) -> Person { + self.owner.read() + } -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. + fn get_owner_name(self: @ContractState) -> felt252 { + self.owner.name.read() + } -For `Expiration`: + fn get_expiration(self: @ContractState) -> Expiration { + self.expiration.read() + } -- `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__` | + 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); + } + } -## Storage Nodes + fn get_owner_storage_address(self: @ContractState) -> felt252 { + self.owner.__base_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. + fn get_owner_name_storage_address(self: @ContractState) -> felt252 { + self.owner.name.__storage_pointer_address__.into() + } -```cairo -#[starknet::storage_node] -struct ProposalNode { - title: felt252, - description: felt252, - yes_votes: u32, - no_votes: u32, - voters: Map, } + +#[cfg(test)] +mod tests; ``` -Accessing members of a storage node involves navigating through its fields: +--- -```cairo -let mut proposal = self.proposals.entry(proposal_id); -proposal.title.write(title); -``` +Sources: -## Low-Level Storage Access (Syscalls) +- 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 -Direct access to storage can be done via system calls: +--- -### `storage_read_syscall` +### Persistent Key-Value Storage: Mappings -Reads a value from a specified storage address. +#### Storage Mappings Fundamentals -- **Arguments**: `address_domain` (currently `0` for onchain mode), `address` (the storage address). -- **Return**: `SyscallResult`. +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. -```cairo -use starknet::storage_access::storage_base_address_from_felt252; +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. -let storage_address = storage_base_address_from_felt252(3534535754756246375475423547453); -storage_read_syscall(0, storage_address).unwrap_syscall(); -``` +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_write_syscall` +#### Declaring and Using Storage Mappings -Writes a value to a specified storage address. +Mappings are declared in the `#[storage]` struct: -- **Arguments**: `address_domain` (currently `0`), `address` (storage address), `value` (`felt252`). -- **Return**: `SyscallResult<()>`. +```rust +#[storage] +struct Storage { + user_values: Map, +} +``` -```cairo -pub extern fn storage_write_syscall( - address_domain: u32, address: StorageAddress, value: felt252, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; +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() +} ``` -## Storing Key-Value Pairs with Mappings +To write a value, retrieve the storage pointer and call `write(value)`: -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. +```rust +fn set(ref self: ContractState, amount: u64) { + let caller = get_caller_address(); + self.user_values.entry(caller).write(amount); +} +``` -To declare a mapping: +Mappings can also be members of storage nodes, such as tracking voters in a proposal structure: -```cairo -user_values: Map, +```rust +#[starknet::storage_node] +struct ProposalNode { + // ... other fields + voters: Map, +} ``` -To access or modify entries: +#### Nested Mappings -```cairo -// Writing to a mapping -self.user_values.entry(caller).write(amount); +Mappings can contain other mappings, allowing for complex key structures, such as mapping a user address to their warehouse inventory: `Map>`. -// Reading from a mapping -let value = self.user_values.entry(address).read(); -``` +Accessing nested mappings requires traversing the keys sequentially using chained `entry()` calls: + +```rust +#[storage] +struct Storage { + user_warehouse: Map>, +} -Nested mappings are supported: +// 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); +} -```cairo -user_warehouse: Map>, +// 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() +} ``` -Access requires chaining `entry` calls: `self.user_warehouse.entry(caller).entry(item_id).write(quantity);` +#### Storage Address Computation -## Storing Ordered Collections with Vectors +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): -Vectors (`Vec`) store ordered collections in storage. Unlike memory arrays (`Array`), `Vec` is a phantom type for storage. +- **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 declare a vector: +The base address of the storage variable can be accessed via the `__base_address__` attribute. -```cairo -addresses: Vec, -``` +--- -Operations include: +Sources: -- `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). +- https://www.starknet.io/cairo-book/ch101-01-02-storage-vecs.html -```cairo -// Appending to a vector +--- + +# Dynamic Collections: Storage Vectors + +## Storing Collections with Vectors + +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`. + +To use `Vec` operations, import `Vec`, `VecTrait`, `MutableVecTrait`, `StoragePointerReadAccess`, and `StoragePointerWriteAccess` from `starknet::storage`. + +### Declaring and Using Storage Vectors + +Storage Vectors are declared using the `Vec` type with angle brackets specifying the element type, e.g., `addresses: Vec` within the `Storage` struct. + +**Adding Elements:** Use the `push` method to add an element to the end. + +```rust +// Example of adding an element self.addresses.push(caller); +``` -// Getting an element by index -self.addresses.get(index).map(|ptr| ptr.read()); +**Retrieving Elements:** -// Modifying an element -self.addresses[index].write(new_address); +- 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 +self.addresses.get(index).map(|ptr| ptr.read()) ``` -To retrieve all elements, iterate and append to a memory `Array`. +**Retrieving All Elements:** Iterate over the indices of the storage `Vec`, read each element, and append it to a memory `Array`. + +**Modifying Elements:** Get a mutable pointer to the storage pointer at the desired index and use the `write` method. + +```rust +// Example of modifying the element at index +self.addresses[index].write(new_address); +``` -## Addresses of Storage Variables +**Removing Elements:** The `pop` method removes and returns the last element as `Some(value)`, or `None` if empty, updating the stored length. -Storage variable addresses are computed using `sn_keccak` hashes of variable names. +```rust +// Example of popping the last element +self.addresses.pop() +``` -- **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. +### Storage Address Computation for Vecs -The base address of a storage variable can be accessed via `__base_address__`. +The storage address for elements in a `Vec` is computed as follows: -## Optimizing Storage Costs with Bit-packing +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. -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. +### Summary of Storage Vec Operations -For example, packing `u8`, `u32`, and `u64` (total 104 bits) into a single `u128` slot: +- 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. -```cairo -struct Sizes { - tiny: u8, - small: u32, - medium: u64, -} -``` +--- -The storage slot would store these packed values, requiring bitwise operations for access. +Sources: -Contract Interaction and Communication +- https://www.starknet.io/cairo-book/ch103-01-optimizing-storage-costs.html +- https://www.starknet.io/cairo-book/ch03-02-dictionaries.html -# Contract Interaction and Communication +--- -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. +### Advanced Storage Structures and Optimization -## Contract Class ABI +#### Felt252Dict Implementation and Dictionary Squashing -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. +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. -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. +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. -### Encoding and Decoding +Example entry list progression for squashing: -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. +| 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 | -## The Dispatcher Pattern +After squashing, the entry list would be reduced to: -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 Optimization via Bit-Packing -The compiler automatically generates dispatchers for defined interfaces. For example, an `IERC20` interface might yield `IERC20Dispatcher` and `IERC20SafeDispatcher`. +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. -- **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. +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. -Under the hood, dispatchers utilize system calls like `starknet::syscalls::call_contract_syscall`. +##### Bitwise Operations for Packing -### Contract Dispatcher Example +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: -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. +- **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. -```cairo -#[starknet::contract] -mod TokenWrapper { - use starknet::{ContractAddress, get_caller_address}; - use super::ITokenWrapper; // Assuming ITokenWrapper is defined elsewhere - use super::{IERC20Dispatcher, IERC20DispatcherTrait}; +Example of combining two `u8` into a `u16` (packing) and reversing it (unpacking): - #[storage] - struct Storage {} +Packing and unpacking integer values - #[abi(embed_v0)] - impl TokenWrapper of ITokenWrapper { - fn token_name(self: @ContractState, contract_address: ContractAddress) -> felt252 { - IERC20Dispatcher { contract_address }.name() - } +##### Implementing StorePacking Trait - 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) - } - } -} -``` +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. -### Handling Errors with Safe Dispatchers +In this implementation: -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. +- `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. -```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 +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. - // 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 - }, - } -} -``` +--- -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. +Sources: -## Calling Contracts using Low-Level Calls +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html -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. +### State Reading and Event Logging Layout -```cairo -#[starknet::contract] -mod TokenWrapper { - use starknet::{ContractAddress, SyscallResultTrait, get_caller_address, syscalls}; - use super::ITokenWrapper; // Assuming ITokenWrapper is defined elsewhere +#### Event Structure When Using `#[flat]` - #[storage] - struct Storage {} +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. - 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); +The resulting log structure for such an event is defined as follows: - let mut res = syscalls::call_contract_syscall( - address, selector!("transfer_from"), call_data.span(), - ) - .unwrap_syscall(); +- **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'). - Serde::::deserialize(ref res).unwrap() - } - } -} -``` +--- -## Executing Code from Another Class (Library Calls) +Sources: -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. +- 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 -When Contract A uses a library call to execute logic from Class B: +--- -- `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. +--- -### Library Dispatchers +Sources: -Similar to contract dispatchers, library dispatchers wrap a `ClassHash` and use `starknet::syscalls::library_call_syscall`. +- https://www.starknet.io/cairo-book/ch101-01-starknet-types.html +- https://www.starknet.io/cairo-book/ch102-04-serialization-of-cairo-types.html -```cairo -// Assuming an interface IERC20 is defined -trait IERC20DispatcherTrait { - fn name(self: T) -> felt252; - fn transfer(self: T, recipient: ContractAddress, amount: u256); -} +--- -#[derive(Copy, Drop, starknet::Store, Serde)] -struct IERC20LibraryDispatcher { - class_hash: starknet::ClassHash, -} +# Address Types and Data Serialization -impl IERC20LibraryDispatcherImpl of IERC20DispatcherTrait { - fn name(self: IERC20LibraryDispatcher) -> felt252 { - // starknet::syscalls::library_call_syscall is called here - // ... implementation details ... - unimplemented!() +## Contract and Storage Addresses + +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. + +### Storage Address + +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. + +```rust +#[starknet::contract] +mod StorageExample { + use starknet::storage_access::StorageAddress; + + #[storage] + struct Storage { + value: u256, } - fn transfer(self: IERC20LibraryDispatcher, recipient: ContractAddress, amount: u256) { - // starknet::syscalls::library_call_syscall is called here - // ... implementation details ... - unimplemented!() + + // 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() } } ``` -### Using the Library Dispatcher +## Ethereum Addresses -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. +The `EthAddress` type represents a 20-byte Ethereum address, primarily used for building cross-chain applications, L1-L2 messaging, and token bridges. + +```rust +use starknet::EthAddress; -```cairo #[starknet::interface] -trait IValueStore { - fn set_value(ref self: TContractState, value: u128); - fn get_value(self: @TContractState) -> u128; +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); } #[starknet::contract] -mod ValueStoreExecutor { +mod EthAddressExample { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ClassHash, ContractAddress}; - use super::{IValueStoreDispatcherTrait, IValueStoreLibraryDispatcher}; + use starknet::syscalls::send_message_to_l1_syscall; + use super::EthAddress; #[storage] struct Storage { - logic_library: ClassHash, - value: u128, // This contract's storage - } - - #[constructor] - fn constructor(ref self: ContractState, logic_library: ClassHash) { - self.logic_library.write(logic_library); + l1_contract: EthAddress, } #[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); + impl EthAddressExampleImpl of super::IEthAddressExample { + fn set_l1_contract(ref self: ContractState, l1_contract: EthAddress) { + self.l1_contract.write(l1_contract); } - 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() + 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(); } } - #[external(v0)] - fn get_value_local(self: @ContractState) -> u128 { - self.value.read() // Reads the local storage directly + #[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... } } ``` -### Calling Classes using Low-Level Calls +## Data Serialization Overview -The `starknet::syscalls::library_call_syscall` can be used directly for more granular control. It requires the class hash, function selector, and serialized arguments. +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. -```cairo -#[starknet::contract] -mod ValueStore { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ClassHash, SyscallResultTrait, syscalls}; +### Data types using at most 252 bits - #[storage] - struct Storage { - logic_library: ClassHash, - value: u128, - } +The following Cairo data types fit within 252 bits and are represented by a single felt: - #[constructor] - fn constructor(ref self: ContractState, logic_library: ClassHash) { - self.logic_library.write(logic_library); - } +- `ContractAddress` +- `EthAddress` +- `StorageAddress` +- `ClassHash` +- Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`, and `usize` +- `bytes31` +- `felt252` +- Signed integers: `i8`, `i16`, `i32`, `i64`, and `i128` - #[external(v0)] - fn set_value(ref self: ContractState, value: u128) -> bool { - let mut call_data: Array = array![]; - Serde::serialize(@value, 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::library_call_syscall( - self.logic_library.read(), selector!("set_value"), call_data.span(), - ) - .unwrap_syscall(); +--- - Serde::::deserialize(ref res).unwrap() - } +Sources: - #[external(v0)] - fn get_value(self: @ContractState) -> u128 { - self.value.read() - } -} +- 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 + +--- + +### 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 ``` -Starknet Components +This command starts the node with predeployed accounts, which are listed in the output, providing necessary addresses and private keys for testing interactions: -# Starknet Components +``` +... +PREFUNDED ACCOUNTS +================== -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. +| Account address | 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 +| Private key | 0x0300001800000000300000180000000000030000000000003006001800006600 +| Public key | 0x01b7b37a580d91bc3ad4f9933ed61f3a395e0e51c9dd5553323b8ca3942bb44e -## What's in a Component? +| Account address | 0x033c627a3e5213790e246a917770ce23d7e562baa5b4d2917c23b1be6d91961c +| Private key | 0x0333803103001800039980190300d206608b0070db0012135bd1fb5f6282170b +| Public key | 0x04486e2308ef3513531042acb8ead377b887af16bd4cdd8149812dfef1ba924d -Similar to contracts, components can contain: +| Account address | 0x01d98d835e43b032254ffbef0f150c5606fa9c5c9310b1fae370ab956a7919f5 +| Private key | 0x07ca856005bee0329def368d34a6711b2d95b09ef9740ebf2c7c7e3b16c1ca9c +| Public key | 0x07006c42b1cfc8bd45710646a0bb3534b182e83c313c7bc88ecf33b53ba4bcbc +... +``` -- Storage variables -- Events -- External and internal functions +#### Differentiating Call and Invoke Operations -## Creating Components +Interacting with external functions is categorized based on state modification: -To create a component: +- **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. -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. +#### Reading State with `starkli call` -```cairo -// Example of an embeddable impl for a component -#[embeddable_as(name)] -impl ComponentName< - TContractState, +HasComponent, -> of super::InterfaceName> { - // Component functions implementation -} +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 ``` -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. +Checking an unregistered account with `is_voter_registered` yields 0 (false): -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. +```bash +starkli call 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 is_voter_registered 0x44444444444444444 --rpc http://0.0.0.0:5050 +``` -## Migrating a Contract to a Component +Functions that modify state, such as casting a vote via the `vote` function, necessitate the use of the `starknet invoke` command. -To migrate a contract to a component, make the following changes: +--- -- 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`. +Sources: -## Using Components Inside a Contract +- https://www.starknet.io/cairo-book/ch102-02-interacting-with-another-contract.html -To integrate a component into a contract: +--- -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)]`. +# High-Level Dispatchers and Type-Safe Calling -```cairo -#[starknet::contract] -mod MyContract { - // Declare the component - component!(path: MyComponent, storage: my_comp_storage, event: MyComponentEvent); +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`. - // Embed the component's logic - #[abi(embed_v0)] - impl MyComponentImpl = MyComponent::MyComponentImpl; +### Types of Generated Dispatchers - #[storage] - struct Storage { - // Contract's own storage - my_data: u128, - // Component's storage, annotated with substorage - #[substorage(v0)] - my_comp_storage: MyComponent::Storage, - } +When a contract interface is defined, the compiler automatically generates several dispatchers. Using `IERC20` as an example: - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - // Contract's own events - MyDataUpdated: MyDataUpdated, - // Component's events - MyComponentEvent: MyComponent::Event, +- **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 + +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. + +The following listing shows the generated items for an `IERC20` interface: + +```rust +use starknet::ContractAddress; + +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__); - // ... rest of the contract + 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__, + ); + () + } } ``` -The component's functions can then be called externally using a dispatcher instantiated with the contract's address. +### Calling Contracts Using the Contract Dispatcher + +A wrapper contract can use the imported dispatcher struct to interact with another contract by wrapping the target contract's address. -## Component Dependencies +The following example shows a `TokenWrapper` contract calling `name` and `transfer_from` on an ERC20 contract: -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. +```rust +use starknet::ContractAddress; -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. +#[starknet::interface] +trait IERC20 { + fn name(self: @TContractState) -> felt252; -```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 + fn symbol(self: @TContractState) -> felt252; + + fn decimals(self: @TContractState) -> u8; + + fn total_supply(self: @TContractState) -> u256; + + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; + + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait ITokenWrapper { + fn token_name(self: @TContractState, contract_address: ContractAddress) -> felt252; + + fn transfer_token( + ref self: TContractState, + address: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) -> bool; +} + +//**** Specify interface here ****// +#[starknet::contract] +mod TokenWrapper { + use starknet::{ContractAddress, get_caller_address}; + use super::ITokenWrapper; + use super::{IERC20Dispatcher, IERC20DispatcherTrait}; #[storage] - pub struct Storage { - value: u32, - } + struct Storage {} - #[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() + #[abi(embed_v0)] + impl TokenWrapper of ITokenWrapper { + fn token_name(self: @ContractState, contract_address: ContractAddress) -> felt252 { + IERC20Dispatcher { contract_address }.name() } - 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); + 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) } } } + +#[cfg(test)] +mod tests; ``` -## Example: An Ownable Component +### Handling Errors with Safe Dispatchers -This example demonstrates an `Ownable` component that manages contract ownership. +Safe dispatchers (e.g., `IERC20SafeDispatcher`) return a `Result::Err` containing the panic reason if the called function fails, allowing for graceful error handling. -### Interface (`IOwnable`) +For a hypothetical `IFailableContract`: -```cairo +```rust #[starknet::interface] -trait IOwnable { - fn owner(self: @TContractState) -> ContractAddress; - fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); - fn renounce_ownership(ref self: TContractState); +pub trait IFailableContract { + fn can_fail(self: @TState) -> u32; +} + +#[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(); + + // 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 + }, + } } ``` -### Component Implementation (`OwnableComponent`) +--- -```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; +Sources: - #[storage] - pub struct Storage { - owner: ContractAddress, - } +- https://www.starknet.io/cairo-book/ch102-02-interacting-with-another-contract.html - #[event] - #[derive(Drop, starknet::Event)] - pub enum Event { - OwnershipTransferred: OwnershipTransferred, - } +--- - #[derive(Drop, starknet::Event)] - struct OwnershipTransferred { - previous_owner: ContractAddress, - new_owner: ContractAddress, - } +### Low-Level Contract Invocation via Syscalls - // Embeddable implementation of the interface - #[embeddable_as(OwnableImpl)] - impl Ownable< - TContractState, +HasComponent, - > of super::IOwnable> { - fn owner(self: @ComponentState) -> ContractAddress { - self.owner.read() - } +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. - 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); - } +#### Scenarios Leading to Immediate Revert - fn renounce_ownership(ref self: ComponentState) { - self.assert_only_owner(); - self._transfer_ownership(Zero::zero()); - } - } +The following cases are expected to be handled in future Starknet versions: - // Internal functions implementation - #[generate_trait] - pub impl InternalImpl< - TContractState, +HasComponent, - > of InternalTrait { - fn initializer(ref self: ComponentState, owner: ContractAddress) { - self._transfer_ownership(owner); - } +- 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` + +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. + +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. + +Listing 16-4 demonstrates calling the `transfer_from` function of an `ERC20` contract: + +```rust +use starknet::ContractAddress; + +#[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); - 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 - } + let mut res = syscalls::call_contract_syscall( + address, selector!("transfer_from"), call_data.span(), + ) + .unwrap_syscall(); - 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 }, - ); + 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() + } } ``` -L1-L2 Messaging +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 -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. +--- -Key characteristics of Starknet's messaging system: +### Starknet L1-L2 Messaging System -- **Asynchronous**: Results of messages sent to the other chain cannot be awaited within the contract code execution. +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. + +#### Overview and Properties + +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; it must be consumed manually via an L1 transaction. + - `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 -## 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 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. +The interface (`IStarknetMessaging`) includes: -```js +```solidity interface IStarknetMessaging is IStarknetMessagingEvents { function sendMessageToL2( @@ -8281,34 +7595,18 @@ interface IStarknetMessaging is IStarknetMessagingEvents { } ``` - Starknet messaging contract interface +For `L1->L2` messages, the sequencer listens to logs emitted by `StarknetMessaging` and executes an `L1HandlerTransaction` to call the target L2 function. -### Sending Messages from Ethereum to Starknet (L1->L2) +#### 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. +To send a message from L1, Solidity contracts must call `sendMessageToL2` on the `StarknetMessaging` contract. -**Fees**: +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. -- 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`. +Example of sending a message with a single felt value from Solidity: -The `sendMessageToL2` function signature is: - -```js -function sendMessageToL2( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload -) external override returns (bytes32); -``` - -- `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). - -**Example (Solidity)**: - -```js +```solidity // Sends a message on Starknet with a single felt. function sendMessageFelt( uint256 contractAddress, @@ -8331,27 +7629,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 (Cairo)**: +Example of the corresponding Cairo handler function: -```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 +7656,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. +Solidity example for consumption: -**Example (Solidity)**: - -```js +```solidity function consumeMessageFelt( uint256 fromAddress, uint256[] calldata payload @@ -8399,1221 +7679,1628 @@ function consumeMessageFelt( } ``` -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`. +The `consumeMessageFromL2` call validates the inputs (L2 contract address and payload) against the recorded message hash. -## Cairo Serde +#### Cairo Serialization Considerations (Serde) -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. +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. -For example, a `u256` type in Cairo is represented as: +A Cairo `u256` struct (composed of `low: u128` and `high: u128`) serializes into **two** felts: -```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 +Sources: -Oracles and Randomness +- https://www.starknet.io/cairo-book/ch103-06-01-deploying-and-interacting-with-a-voting-contract.html -# 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. +### Practical Deployment and CLI Interaction -## Oracles +The class hash for the contract is: `0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52`. This hash can be used for declaration on Sepolia testnet. -Oracles act as secure intermediaries, transmitting external data to blockchains and smart contracts. This section details interactions with the Pragma oracle on Starknet. +#### Deployment using starkli -### Pragma Oracle for Price Feeds +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. -Pragma is a zero-knowledge oracle providing verifiable off-chain data on Starknet. +The command deploys the contract and registers initial voters using constructor arguments: -#### Setting Up Your Contract for Price Feeds +```bash +starkli deploy --rpc http://0.0.0.0:5050 --account katana-0 +``` -1. **Add Pragma as a Project Dependency**: - Edit your `Scarb.toml` file: - ```toml - [dependencies] - pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" - ``` +An example deployment command: -Development Tools and Best Practices +```bash +starkli deploy 0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 0x033c627a3e5213790e246a917770ce23d7e562baa5b4d2917c23b1be6d91961c 0x01d98d835e43b032254ffbef0f150c5606fa9c5c9310b1fae370ab956a7919f5 --rpc http://0.0.0.0:5050 --account katana-0 +``` + +The deployed contract address will be specific to your deployment, for example: `0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349`. + +#### Reading Voter Eligibility -# Development Tools and Best Practices +The contract includes external read functions to check eligibility without altering state: `voter_can_vote` and `is_voter_registered`. -### Starkli Tooling +#### Invoking Contract Functions (Voting) -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`. +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). -To retrieve a smart wallet's class hash using `starkli`, use: +Voting Yes: ```bash -starkli class-hash-at --rpc http://0.0.0.0:5050 +starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 1 --rpc http://0.0.0.0:5050 --account katana-0 ``` -### Contract Deployment +Voting No: + +```bash +starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 0 --rpc http://0.0.0.0:5050 --account katana-0 +``` -Declare contracts using the `starkli declare` command: +After submission, you receive a transaction hash, which can be queried for details: ```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 transaction --rpc http://0.0.0.0:5050 ``` -If you encounter `compiler-version` errors, ensure `starkli` is updated or specify the compiler version using `--compiler-version x.y.z`. +The output provides details like transaction hash, max fee, and signature. -The class hash for a contract can be verified. For example, `0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52` is a known class hash. +#### Error Handling -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. +Attempting to vote twice with the same signer results in a generic error: -### Smart Contract Best Practices +``` +Error: code=ContractError, message="Contract error" +``` -#### Deployment Safety +More detailed error reasons, such as `"USER_ALREADY_VOTED"`, can be found by inspecting the output logs of the running `katana` node. -- **`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. +--- -#### Bridging Safety (L1-L2 Interactions) +Sources: -- **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. +- https://www.starknet.io/cairo-book/ch101-03-contract-events.html - ```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… - } - ``` +--- + +### Transaction Receipts and Event Handling + +To understand what happens internally, transaction receipts show emitted events. + +#### Example 1: Add a book + +When invoking `add_book` (e.g., id=42, title='Misery', author='S. King'), the transaction receipt's "events" section contains serialized event data. -#### Economic/DoS & Griefing Vulnerabilities +The structure observed in the receipt is: -- **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. +```json +"events": [ + { + "from_address": "0x27d07155a12554d4fd785d0b6d80c03e433313df03bb57939ec8fb0652dbe79", + "keys": [ + "0x2d00090ebd741d3a4883f2218bd731a3aaa913083e84fcf363af3db06f235bc", + "0x532e204b696e67" + ], + "data": [ + "0x2a", + "0x4d6973657279" + ] + } + ] +``` + +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. + +#### 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: + +```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. + +--- + +Sources: + +- 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 + +--- + +--- + +Sources: -Cairo Virtual Machine (VM) and Execution +- 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 -Introduction to Cairo and its Virtual Machine (VM) +--- -# Introduction to Cairo and its Virtual Machine (VM) +# Cairo Components: Fundamentals and Implementation -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. +## Components: Lego-Like Building Blocks -## Virtual Machine +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. + +Unlike a contract, a component cannot be deployed on its own; its logic becomes part of the contract's bytecode when embedded. + +### What's in a Component? + +A component is similar to a contract and can contain: + +- Storage variables +- Events +- External and internal functions + +## Creating Components + +To create a component, define it in a module decorated with `#[starknet::component]`. Within this module, declare a `Storage` struct and an `Event` enum. + +### Defining the Interface and Implementation + +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. + +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). + +### Example: Ownable Component Interface + +``` +#[starknet::interface] +trait IOwnable { + fn owner(self: @TContractState) -> ContractAddress; + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TContractState); +} +``` -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). +### Example: Ownable Component Implementation -There are two types of VMs: +``` +#[starknet::component] +pub mod OwnableComponent { + use core::num::traits::Zero; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + use super::Errors; -- **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. + #[storage] + pub struct Storage { + owner: ContractAddress, + } -Cairo VM Architecture and Execution Flow + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + OwnershipTransferred: OwnershipTransferred, + } -# Cairo VM Architecture and Execution Flow + #[derive(Drop, starknet::Event)] + struct OwnershipTransferred { + previous_owner: ContractAddress, + new_owner: ContractAddress, + } -## Execution Model + #[embeddable_as(OwnableImpl)] + impl Ownable< + TContractState, +HasComponent, + > of super::IOwnable> { + fn owner(self: @ComponentState) -> ContractAddress { + self.owner.read() + } -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. + 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); + } -### Registers + fn renounce_ownership(ref self: ComponentState) { + self.assert_only_owner(); + self._transfer_ownership(Zero::zero()); + } + } -The Cairo VM has three dedicated registers: + #[generate_trait] + pub impl InternalImpl< + TContractState, +HasComponent, + > of InternalTrait { + fn initializer(ref self: ComponentState, owner: ContractAddress) { + self._transfer_ownership(owner); + } -- **`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. + 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); + } -The state of the VM at any moment is defined by the values of these three registers. + 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 }, + ); + } + } +} +``` -## Instructions and Opcodes +> 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<...>`). -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. +## Migrating a Contract to a Component -The VM supports three core opcodes: +Migration requires minimal changes: -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. +- 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`. -### Cairo Assembly (CASM) +For traits generated via `#[generate_trait]`, the trait is generic over `TContractState` instead of `ComponentState`. -CASM is the human-readable assembly language for Cairo, representing machine instructions textually. Each line of CASM corresponds to a specific instruction. +## Using Components Inside a Contract -## State Transition +To integrate a component, you must: -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. +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. -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. +### Example: Basic ERC20 Contract Using Components -### Deterministic and Non-deterministic Cairo Machine +``` +#[starknet::contract] +pub mod BasicERC20 { + use openzeppelin_token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; -- **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. + component!(path: ERC20Component, storage: erc20, event: ERC20Event); -The Cairo Runner is the entrypoint for running a Cairo program and generating the AIR inputs needed for proof. + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; -## Circuit Evaluation + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + } -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. + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + } -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)`. + #[constructor] + fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { + let name = "MyToken"; + let symbol = "MTK"; -```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(); -# } + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + } +} ``` -## Program Execution Flow - -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. +--- -Each instruction and its arguments increment the Program Counter (PC) by 1. Instructions like `call` and `ret` manage a function stack. +Sources: -- **`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`. +- 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 -For example: +--- -- `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. +## Plugin Diagnostics for Component Integration -## Memory Model +Cairo compiler provides helpful diagnostics when integrating components: -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. +- `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. -### Relocatable Values and Memory Space +## Component Impl Structure and State Access -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. +Components use generic `impl` blocks marked with `#[embeddable_as()]` to define logic that can be embedded. -Example memory and relocation table: +### The `#[embeddable_as]` Attribute -``` -Addr Value ------------ -// Segment 0 -0:0 5189976364521848832 -0:1 10 -... -// Segment 1 -1:0 2:0 -... -// Segment 2 -2:0 110 -``` +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`. -**From relocation value to one contiguous memory address space:** +### Genericity and State Access -``` -Addr Value ------------ -1 5189976364521848832 -2 10 -... -13 22 -14 23 -... -22 110 -``` +The implementation is typically generic over `ComponentState`, restricted by traits like `+HasComponent`. -**Relocation table:** +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`. -``` -segment_id starting_index ----------------------------- -0 1 -1 13 -2 22 -``` +## Contract Integration Steps -Cairo VM Memory Model +Integrating a component involves several steps within the host contract: -# Cairo VM Memory Model +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. -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. +Internal methods meant only for internal use should be implemented in a separate impl block without `#[abi(embed_v0)]`. -## Overview +### Example: Embedding the Ownable Component -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. +The following example demonstrates embedding the `Ownable` component into an `OwnableCounter` contract: -## Non-Deterministic Read-only Memory +```cairo +#[starknet::contract] +mod OwnableCounter { + use listing_01_ownable::component::OwnableComponent; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; -Cairo employs a non-deterministic, read-only memory model, which effectively behaves as a write-once memory: + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); -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. + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; -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. + impl OwnableInternalImpl = OwnableComponent::InternalImpl; -## Memory Segments + #[storage] + struct Storage { + counter: u128, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } -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**. + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + OwnableEvent: OwnableComponent::Event, + } -Cairo's memory model includes the following segments: + #[abi(embed_v0)] + fn foo(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.counter.write(self.counter.read() + 1); + } +} +``` -- **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. +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. -Segments 1 onwards (Execution, Builtin, User) have dynamic address spaces whose lengths are unknown until program completion. +--- -The standard layout of memory segments is: +Sources: -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 +- 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 -The number of builtin and user segments is dynamic. +--- -## Relocation +# Cairo Contracts Integrations -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. +### Oracles: Integrating External Data and Randomness -Consider the following Cairo Zero program example: +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). -```cairo -%builtins output +#### Price Feeds -func main(output_ptr: felt*) -> (output_ptr: felt*) { +Price feeds provide real-time pricing data aggregated from trusted external sources. To integrate Pragma for price feeds: - // We are allocating three different values to segment 1. - [ap] = 10, ap++; - [ap] = 100, ap++; - [ap] = [ap - 2] + [ap - 1], ap++; +1. **Dependency**: Add `pragma_lib` to `Scarb.toml`: - // 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++; +```toml +[dependencies] +pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } +``` - // Asserts that output_ptr equals to 110. - assert [output_ptr] = 110; +2. **Interface Definition**: Define an interface including the necessary view function, such as `get_asset_price`: - // Returns the output_ptr + 1 as the next unused memory address. - return (output_ptr=output_ptr + 1); +```cairo +#[starknet::interface] +pub trait IPriceFeedExample { + fn buy_item(ref self: TContractState); + fn get_asset_price(self: @TContractState, asset_id: felt252) -> u128; } ``` -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 - -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. +3. **Implementation**: The `get_asset_price` function interacts with the oracle: -Its role in STARK proofs includes: +```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(), + }; -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. + // Call the Oracle contract, for a spot entry + let output: PragmaPricesResponse = oracle_dispatcher + .get_data_median(DataType::SpotEntry(asset_id)); -### Segment Arena Builtin + return output.price; + } +``` -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. +Pragma may return values with decimal factors of 6 or 8, requiring conversion by dividing by \({10^{n}}\). -Key validation rules for the Segment Arena include: +An example application consuming this feed (`PriceFeedExample`) calculates required ETH based on the retrieved price: -- 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. +```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}; -Cairo Builtins: Cryptographic and Arithmetic Operations + const ETH_USD: felt252 = 19514442401534788; + const EIGHT_DECIMAL_FACTOR: u256 = 100000000; -### Cairo Builtins: Cryptographic and Arithmetic Operations + #[storage] + struct Storage { + pragma_contract: ContractAddress, + product_price_in_usd: u256, + } -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. + #[constructor] + fn constructor(ref self: ContractState, pragma_contract: ContractAddress) { + self.pragma_contract.write(pragma_contract); + self.product_price_in_usd.write(100); + } -#### Keccak Builtin + #[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, + ); + } -The Keccak builtin computes the Keccak-256 hash of a given input. + fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 { + // Retrieve the oracle dispatcher + let oracle_dispatcher = IPragmaABIDispatcher { + contract_address: self.pragma_contract.read(), + }; -##### Syscall Signature + // Call the Oracle contract, for a spot entry + let output: PragmaPricesResponse = oracle_dispatcher + .get_data_median(DataType::SpotEntry(asset_id)); -```cairo,noplayground -pub extern fn keccak_syscall( - input: Span, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; + return output.price; + } + } +} ``` -##### Description - -Computes the Keccak-256 hash of a given input. - -##### Arguments - -- `input`: A `Span` representing the Keccak-256 input. +#### Verifiable Random Functions (VRFs) -##### Return Values +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. -- `SyscallResult`: The computed hash result. +Pragma provides VRF solutions on Starknet. The process involves: -##### Deduction Property +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. -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. +Key inputs for `request_randomness_from_pragma` include: -##### Error Conditions +- `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. -- 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. +**Dice Game Example using Pragma VRF** -#### ECDSA Builtin +This contract uses VRF to determine game winners. It requires defining interfaces for both the game logic (`IDiceGame`) and the oracle (`IPragmaVRF`). -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. +Key entrypoints in `IPragmaVRF`: -##### Memory Organization +- `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 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. +The full contract implementation (`DiceGame`) demonstrates handling the request, approval of callback fees, and processing the result in `process_game_winners`: -##### Cell Layout in the Memory Segment +```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, + }; -The ECDSA segment arranges cells in pairs: + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); -- **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`. + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl InternalImpl = OwnableComponent::InternalImpl; -##### Signature Verification Process + #[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, + } -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: + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + GameWinner: ResultAnnouncement, + GameLost: ResultAnnouncement, + #[flat] + OwnableEvent: OwnableComponent::Event, + } -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. + #[derive(Drop, starknet::Event)] + struct ResultAnnouncement { + caller: ContractAddress, + guess: u8, + random_number: u256, + } -##### Error Conditions + #[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 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. + #[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"); -#### Poseidon Builtin + let caller = get_caller_address(); + self.user_guesses.entry(caller).write(guess); + } -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. + fn toggle_play_window(ref self: ContractState) { + self.ownable.assert_only_owner(); -##### Cells Organization + let current: bool = self.game_window.read(); + self.game_window.write(!current); + } -The Poseidon builtin operates with a dedicated memory segment and follows a deduction property pattern with 6 consecutive cells: + fn get_game_window(self: @ContractState) -> bool { + self.game_window.read() + } -- **Input cells [0-2]**: Store input state for the Hades permutation. -- **Output cells [3-5]**: Store computed permutation results. + 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, + }, + ), + ); + } + } + } -##### How it Works + #[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 + } -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. + 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(); -##### Single Value Hashing Example + 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(), + ); -For hashing a single value (e.g., 42): + // Request the randomness + randomness_dispatcher + .request_random( + seed, callback_address, callback_fee_limit, publish_delay, num_words, calldata, + ); -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. + let current_block_number = get_block_number(); + self.min_block_number_storage.write(current_block_number + publish_delay); + } -##### Sequence Hashing Example + 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"); -For hashing a sequence of values (e.g., 73, 91): + let random_word = *random_words.at(0); + self.last_random_number.write(random_word); + } -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). + 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); + } + } +} +``` -##### Error Condition +**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. -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. +### External Computation and Off-Chain Interaction -#### Mod Builtin (AddMod, MulMod) +--- -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. +Sources: -##### Under the Hood +- https://www.starknet.io/cairo-book/ch103-04-L1-L2-messaging.html -- **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`. +--- -##### Error Conditions +## System-Level Patterns and Messaging -- **`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`. +### Local Messaging System Testing -#### Pedersen Builtin +You can also find a detailed guide here to test the messaging system locally. -The Pedersen builtin is dedicated to computing the Pedersen hash of two field elements (felts). +--- -##### Cells Organization +Sources: -The Pedersen builtin has its own dedicated memory segment and is organized in triplets of cells: +- 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 -- **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. +--- -##### Error Conditions +--- -- 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. +Sources: -#### Other Builtins +- 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 Cairo VM implements a variety of other built-ins, each serving specific purposes in computation and proof generation. The following table lists them: +--- -| 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. | +#### Error Handling and Program Robustness -[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 +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. -Cairo Builtins: Specialized Functions +## Unrecoverable Errors with `panic` -# Cairo Builtins: Specialized Functions +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. -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. +### Calling `panic` -## How Builtins Work +The `panic` function takes an array as an argument: -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. +```cairo +#[executable] +fn main() { + let mut data = array![2]; -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. + if true { + panic(data); + } + println!("This line isn't reached"); +} +``` -### Validation Property +Executing this produces output like: -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. +``` +$ 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 Range Check builtin validates values immediately upon writing to a cell, enabling early detection of out-of-range values. +### Alternatives to `panic` -#### Valid Operation Example +1. **`panic_with_felt252`**: A more idiomatic one-liner abstraction that takes a `felt252` error message: -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]`. + ```cairo + use core::panic_with_felt252; -![Range Check builtin segment with valid values](range-check-builtin-valid.png) + #[executable] + fn main() { + panic_with_felt252(2); + } + ``` -#### Out-of-Range Error Example +2. **`panic!` Macro**: More convenient than `panic`, as it takes a string literal, allowing error messages longer than 31 bytes: -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. + ```cairo + #[executable] + fn main() { + if true { + panic!("2"); + } + println!("This line isn't reached"); + } + ``` -![Range Check error: Value exceeds maximum range](range-check-builtin-error1.png) +### The `nopanic` Notation -#### Invalid Type Error Example +The `nopanic` notation indicates a function will never panic, allowing it to be called only from other `nopanic` functions. -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. +```cairo +fn function_never_panic() -> felt252 nopanic { + 42 +} +``` -![Range Check error: Value is a relocatable address](range-check-builtin-error2.png) +If a function declared as `nopanic` calls a function that may panic (like `assert!`), compilation will fail: -## Bitwise Builtin +``` +error: Function is declared as nopanic but calls a function that may panic. +``` -The Bitwise Builtin facilitates bitwise operations—AND (`&`), XOR (`^`), and OR (`|`)—on field elements. It supports tasks requiring bit-level manipulation. +### `panic_with` Attribute -### How It Works +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`. -The Bitwise builtin uses a dedicated memory segment. Each operation involves a block of 5 cells: +```cairo +#[panic_with('value is 0', wrap_not_zero)] +fn wrap_if_not_zero(value: u128) -> Option { + if value == 0 { + None + } else { + Some(value) + } +} -| 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 | +#[executable] +fn main() { + wrap_if_not_zero(0); // this returns None + wrap_not_zero(0); // this panics with 'value is 0' +} +``` -For example, if `x = 5` (binary `101`) and `y = 3` (binary `011`): +## Recoverable Errors with `Result` and Propagation -- `5 & 3 = 1` (binary `001`) -- `5 ^ 3 = 6` (binary `110`) -- `5 | 3 = 7` (binary `111`) +Most errors are recoverable, allowing functions to return an error value instead of terminating. -### Example Usage +### The `Result` Enum -This Cairo function demonstrates the use of the Bitwise Builtin: +The `Result` enum signifies success or failure: ```cairo -from starkware.cairo.common.cairo_builtins import BitwiseBuiltin - -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); +enum Result { + Ok: T, + Err: E, } ``` -## EC OP Builtin +### `ResultTrait` Methods -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. +The `ResultTrait` provides methods for working with `Result`: -### Cells Organization +| 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` | -The EC OP builtin uses a dedicated memory segment. Each operation is defined by a block of 7 cells: +These methods require generic type constraints, such as `<+Drop>` for `unwrap`. -| 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 | +### Propagating Errors with the `?` Operator -The first five cells are inputs provided by the program, and the last two cells are outputs computed by the VM upon reading. +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. -#### Valid Operation Example +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. -This example shows a correctly configured EC OP builtin operation with all input values set. +The `?` operator can only be used in functions whose return type is compatible (e.g., returns `Result` or `Option`). -![EC OP builtin segment with complete input values](ecop-segment.png) +```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') + } +} -When the program reads cells at offsets 5 and 6, the VM computes `R = P + mQ` and returns the resulting coordinates. +fn mutate_byte(input: felt252) -> Result { + let input_to_u8: u8 = parse_u8(input)?; + let res = input_to_u8 - 1; + Ok(res) +} +``` -#### Error Condition Example +If used incorrectly (e.g., in a function returning `()`), it results in: -This example illustrates an error condition where the program attempts to read the output cells with incomplete inputs. +``` +error: `?` can only be used in a function with `Option` or `Result` return type. +``` -![Incomplete input values in EC OP builtin segment](ecop-invalid-inputs.png) +## Common Error Messages -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. +Encountering specific error messages can be resolved by checking the following: -## Keccak Builtin +- **`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. -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. +## Robustness and Security Considerations -### Cells Organization +Certain programming patterns can lead to vulnerabilities if not handled carefully: -The Keccak builtin uses a dedicated memory segment organized in blocks of 16 cells: +### Operator Precedence -| 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 | +`&&` has higher precedence than `||`. Parentheses must be used to enforce correct precedence in combined boolean expressions: -The builtin processes each block independently with the following rules: +```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" +); -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. +// ✅ fixed +assert!( + (mode == Mode::None || mode == Mode::Recovery) && (ctx.coll_ok && ctx.debt_ok), + "EMERGENCY_MODE" +); +``` -#### Example Operation +### Unsigned Loop Underflow -![Keccak builtin segment with a complete operation](keccak-segment.png) +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: -## Mod Builtin +```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; +} +``` -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)`. +### Bit-Packing into `felt252` -### How It Works +Packing fields into a single `felt252` requires strict bounds checking. The sum of the sizes of packed values should not exceed 251 bits. -The Mod builtin operates on a dedicated memory segment. For each modular arithmetic operation, it uses a block of 3 cells: +```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"); -| Offset | Description | Role | -| ------ | ----------- | ------ | -| 0 | `a` value | Input | -| 1 | `b` value | Input | -| 2 | `a % b` | Output | + let packed: u256 = + (book_id * POW_2_64) + (tick_u24 * POW_2_40) + index_u40; + packed.try_into().expect("PACK_OVERFLOW") +} +``` -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. +--- -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. +Sources: -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. +- 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 -## Segment Arena Builtin +--- -The Segment Arena builtin enhances Cairo VM's memory management by tracking segment endpoints, simplifying memory operations involving segment allocation and finalization. +## Cairo Testing Mechanics and Organization -### Cells Organization +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. -Each Segment Arena builtin instance manages state using blocks of 3 cells: +### Anatomy of a Test Function -- First cell: Base address of the info pointer. -- Second cell: Current number of allocated segments. -- Third cell: Current number of finalized segments. +Tests are Cairo functions annotated with the `#[test]` attribute. A typical test function performs three actions: -This structure works in conjunction with an Info segment, also organized in blocks of 3 cells: +1. Set up any needed data or state. +2. Run the code you want to test. +3. Assert the results are what you expect. -- 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). +When running tests with `scarb test`, Scarb executes Starknet Foundry's test runner binary against these annotated functions. -![Segment Arena builtin segment](segment-arena.png) +Cairo features available for writing tests include: -Cairo Compilation: Sierra and Casm +- `#[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. -# Cairo Compilation: Sierra and Casm +### The `tests` Module and Configuration -## Sierra and Casm +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. -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. +```cairo +pub fn add(left: usize, right: usize) -> usize { + left + right +} -## Why Casm is Needed +#[cfg(test)] +mod tests { + use super::*; -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. + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} +``` -## Safe Casm +### Assertion Macros -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. +Tests fail when code inside the function panics. Assertions are used to check conditions: -## Compilation of Loops and Recursion +- `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. -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`. +When assertions fail, custom failure messages can be added as optional arguments to these macros, which are passed to the `format!` macro. -## Sierra Code Structure +### Test Organization -Sierra files are structured into three main parts: +Tests are categorized into two main types: -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. +#### Unit Tests -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. +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. -### Example: Inlining in Sierra +#### Integration Tests -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. +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. -#### Casm Code Example +### Controlling Test Execution -Here's a corresponding Casm code snippet for the described program: +- **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. -```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 -``` +### Benchmarking -This Casm code involves instructions like `call rel`, `ret`, and memory operations (`[ap + offset] = value, ap++`). +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`. -Security and Provability in Cairo +--- -### Security and Provability in Cairo +Sources: -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. +- 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 -#### Provability and Malicious Provers +--- -- **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. +### Security Best Practices and Contract Testing -#### Gas Metering Complications +#### General Recommendations and Focus Areas -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. +Writing secure code is crucial. Focus areas derived from audits include: -To address this, the plan is to require users to have sufficient gas for the unhappy flow before initiating operations like `find_element`. +- Access control and upgrades. +- Safe ERC20 token integrations. +- Cairo-specific pitfalls. +- Cross-domain/bridging safety. +- Economic/Denial of Service (DoS) considerations. -#### Hints in Cairo +#### Access Control, Upgrades & Initializers -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. +The most common critical issues involve access control ("who can call this?") and re-initialization. -Cairo Runner and Proof Generation +##### Own Your Privileged Paths -# Cairo Runner and Proof Generation +Ensure upgrades, pause/resume, bridge handling, and meta-execution are guarded, typically using `OwnableComponent`. -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. +```cairo +// components +component!(path: OwnableComponent, storage: ownable); +component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); -## Runner Modes +#[abi(embed_v0)] +impl OwnableImpl = OwnableComponent::OwnableImpl; +impl InternalUpgradeableImpl = UpgradeableComponent::InternalImpl; -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. +#[event] +fn Upgraded(new_class_hash: felt252) {} -### Execution Mode +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 +} +``` -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. +##### Initializers Must Be Idempotent -### Proof Mode +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. -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. +```cairo +#[storage] +struct Storage { + _initialized: u8, + // ... +} -Advanced Topics and Utilities +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… +} +``` -Development Tools and Utilities +Rule: If a function must be external during deployment, ensure it can only be called once; otherwise, keep it internal. -# Development Tools and Utilities +##### Emit Events -This section covers useful development tools provided by the Cairo project, including automatic formatting, quick warning fixes, a linter, and IDE integration. +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. -## Automatic Formatting with `scarb fmt` +#### Safe Token Integrations -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. +##### Always Check Boolean Returns -To format a Cairo project, navigate to the project directory and run: +Not all ERC20 implementations revert on failure; some return `false`. Verify the boolean flags returned by `transfer` and `transfer_from` to confirm success. -```bash -scarb fmt -``` +##### Naming Conventions -Code sections that should not be formatted can be marked with `#[cairofmt::skip]`: +Most Starknet ERC20 tokens use `snake_case`. Be aware that legacy tokens might use `camelCase` entrypoints, requiring adaptation if interacting with them. -```cairo, noplayground -#[cairofmt::skip] -let table: Array = array![ - "oxo", - "xox", - "oxo", -]; -``` +#### Cairo-Specific Pitfalls -## IDE Integration Using `cairo-language-server` +##### `deploy_syscall(deploy_from_zero=true)` Collisions -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). +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. -Cryptography and Zero-Knowledge Proofs +##### Useless Zero-Address Checks -# Cryptography and Zero-Knowledge Proofs +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. -## Hash Functions in Cairo +#### Cross-Domain / Bridging Safety -Cairo's core library offers two hash functions: Pedersen and Poseidon. +L1-L2 interactions require specific validation. -### Pedersen Hash +##### L1 Handler Caller Validation -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). +Entrypoints marked with `#[l1_handler]` must validate that the caller address originates from a trusted L1 contract. -### Poseidon Hash +```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… +} +``` -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. +#### Economic/DoS & Griefing -## Arithmetic Circuits in Zero-Knowledge Proof Systems +##### Unbounded Loops -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. +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. -### zk-SNARKs Approach +#### Static Analysis Tools (Contract Testing) -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. +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: -### zk-STARKs Approach +- Semgrep Cairo 1.0 support +- Caracal, a Starknet static analyzer -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. +--- -## Implementing Arithmetic Circuits in Cairo +Sources: -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\). +- 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 -### Basic Arithmetic Gates +--- -- `AddMod` builtin for addition modulo \(p\) -- `MulMod` builtin for multiplication modulo \(p\) +--- -These enable the construction of gates such as `AddModGate`, `SubModGate`, `MulModGate`, and `InvModGate`. +Sources: -### Example Circuit: `a * (a + b)` +- https://www.starknet.io/cairo-book/appendix-00.html +- https://www.starknet.io/cairo-book/appendix-000.html -The following code demonstrates the creation and evaluation of a circuit that computes \(a \cdot (a + b)\\) over the BN254 prime field: +--- -```cairo, noplayground -use core::circuit::{ - AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, - CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, -}; +### Reference Material Overview -// 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::> {}; +#### Appendix (Cairo) - let add = circuit_add(a, b); - let mul = circuit_mul(a, add); +The Cairo Programming Language appendix includes: - let output = (mul,); +- Light +- Rust +- Coal +- Navy +- Ayu - let mut inputs = output.new_inputs(); - inputs = inputs.next([10, 0, 0, 0]); - inputs = inputs.next([20, 0, 0, 0]); +The following sections contain reference material you may find useful in your Cairo journey. - let instance = inputs.done(); +#### Appendix (Starknet) - let bn254_modulus = TryInto::< - _, CircuitModulus, - >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) - .unwrap(); +The following sections contain reference material you may find useful in your Starknet journey. +]] - let res = instance.eval(bn254_modulus).unwrap(); +--- - let add_output = res.get_output(add); - let circuit_output = res.get_output(mul); +Sources: - 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"); +- https://www.starknet.io/cairo-book/appendix-01-keywords.html - (add_output, circuit_output) -} +--- -#[executable] -fn main() { - eval_circuit(); -} -``` +### Cairo Keywords and Built-ins -The process involves defining inputs, describing the circuit, specifying outputs, assigning values, defining the modulus, evaluating the circuit, and retrieving output values. +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. -## Modular Arithmetic Builtin +#### Keyword Categories -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. +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. -### Structure and Operation +#### Strict Keywords -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`. +These keywords must only be used in their correct contexts and cannot serve as item names. -### Modular Addition Example +- `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 -The `AddMod` builtin can be used to compute `x + y (mod p)` for `UInt384` values, as illustrated in Cairo Zero code. +#### Loose Keywords -Oracles +These keywords are associated with specific behavior but can also be used to define items. -# Oracles +- `self` - Method subject +- `super` - Parent module of the current module -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. +#### Reserved Keywords -> 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. +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. -## Why use Oracles? +- `Self` +- `do` +- `dyn` +- `for` +- `hint` +- `in` +- `macro` +- `move` +- `static_assert` +- `static` +- `try` +- `typeof` +- `unsafe` +- `where` +- `with` +- `yield` -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`). +#### Built-in Functions -## What We’ll Build +These functions serve a special purpose in Cairo. Using their names for other items is not recommended. -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. +- `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. -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-02-operators-and-symbols.html -Filename: listing_oracles/Scarb.toml +--- -```toml -[package] -name = "example" -version = "0.1.0" -edition = "2024_07" -publish = false +# Syntax Glossary and Symbols -[dependencies] -cairo_execute = "2.12.0" -oracle = "0.1.0-dev.4" +This section provides a glossary of Cairo's syntax, including operators and other symbols used in paths, generics, macros, attributes, comments, tuples, and brackets. -[executable] +## Operators -[cairo] -enable-gas = false +Table B-1 lists Cairo operators, their context, explanation, and overloadability details. -[dev-dependencies] -cairo_test = "2" -``` +| 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 | | -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. +## Non-Operator Symbols -Filename: listing_oracles/src/lib.cairo +These symbols do not behave like a function or method call. -```cairo -use core::num::traits::Pow; +### Stand-Alone Syntax -// 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,)) -} +| Symbol | Explanation | +| --------------------------------------- | ----------------------------------------- | +| `..._u8`, `..._usize`, `..._bool`, etc. | Numeric literal of specific type | +| `\"...\"` | String literal | +| `'...'` | Short string, 31 ASCII characters maximum | +| `_` | “Ignored” pattern binding | -// 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,), - ) -} +### Path-Related Syntax -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}"); +Symbols used within the context of a module hierarchy path to access an item. - 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()); +| 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 | - Ok(()) -} +### Generic Type Parameters -#[executable] -fn main(x: u64) -> bool { - match oracle_calls(x) { - Ok(()) => true, - Err(e) => panic!("Oracle call failed: {e:?}"), - } -} -``` +Symbols appearing in the context of using generic type parameters. -### Key Concepts in Cairo Oracle Interaction +| Symbol | Explanation | +| ----------- | ------------------------------------------------------------------ | +| `path<...>` | Specifies parameters to generic type in a type (e.g., `Array`) | -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. +### Macros -## The Rust Oracle +| 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 | -The Rust side implements the oracle endpoints. The `cairo_oracle_server` crate handles input decoding and output encoding back to Cairo. +### Comments -Filename: listing_oracles/src/my_oracle/Cargo.toml +Symbols that create comments. -```toml -[package] -name = "my_oracle" -version = "0.1.0" -edition = "2021" -publish = false +| Symbol | Explanation | +| ------ | ------------ | +| `//` | Line comment | -[dependencies] -anyhow = "1" -cairo-oracle-server = "0.1" -starknet-core = "0.11" -``` +### Tuples -Filename: listing_oracles/src/my_oracle/src/main.rs +Symbols appearing in the context of using tuples. -```rust, noplayground -use anyhow::ensure; -use cairo_oracle_server::Oracle; -use std::process::ExitCode; +| 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 | -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() -} -``` +### Curly Braces -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`. +Contexts in which curly braces are used. -## Running the Example +| Context | Explanation | +| ------------ | ---------------- | +| `{...}` | Block expression | +| `Type {...}` | `struct` literal | -To run the example, navigate to the example directory and execute the following command: +--- -```bash -scarb execute --experimental-oracles --print-program-output --arguments 25000000 -``` +Sources: -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. +- https://www.starknet.io/cairo-book/appendix-04-cairo-prelude.html -To observe an error, try a non-perfect square like `27`: +--- -```bash -scarb execute --experimental-oracles --print-program-output --arguments 27 -``` +## Cairo Prelude and Editions -The `sqrt` endpoint will return an error, which propagates to Cairo, causing the program to panic. +### The Cairo Prelude -## Summary +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. -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. +The core library prelude is defined in the _lib.cairo_ file of the corelib crate and contains: -Appendices and References +- 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. -# Appendices and References +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. -## Appendix A - Keywords +### Cairo Editions -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. +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. -### Strict Keywords +Here is the list of available Cairo editions (i.e prelude versions) with their details: -These keywords can only be used in their correct contexts and cannot be used as names for items. +| Version | Details | +| -------------------- | ------------------- | +| `2024-07` | details for 2024-07 | +| `2023-11` | details for 2023-11 | +| `2023-10` / `2023-1` | details for 2023-10 | -- `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 +--- -## Appendix B - Syntax +Sources: -This section details the usage of specific syntax elements in Cairo. +- https://www.starknet.io/cairo-book/appendix-06-useful-development-tools.html +- https://www.starknet.io/cairo-book/ch103-06-00-other-examples.html -### Tuples +--- -Tuples are used for grouping multiple values. +## Development Tools and Examples -| 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. | +### Useful Development Tools -### Curly Braces +This section covers useful development tools provided by the Cairo project, including automatic formatting, quick warning fixes, a linter, and IDE integration. -Curly braces `{}` have specific contexts in Cairo. +#### Automatic Formatting with `scarb fmt` -| Context | Explanation | -| :----------- | :--------------- | -| `{...}` | Block expression | -| `Type {...}` | `struct` literal | +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. -## Appendix C - Derivable Traits +To format any Cairo project, run the following inside the project directory: -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 fmt +``` -### Hashing with `Hash` +To skip formatting for specific code sections, use `#[cairofmt::skip]`: -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. +``` +#[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.